UNPKG

drizzle-zero

Version:

Generate Zero schemas from Drizzle ORM schemas

1,004 lines (989 loc) 37.6 kB
#!/usr/bin/env node var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/cli/index.ts import { Command } from "commander"; import * as fs3 from "fs/promises"; import * as path3 from "path"; import { pathToFileURL as pathToFileURL3 } from "url"; import { Project as Project4 } from "ts-morph"; // src/cli/config.ts import * as fs from "fs/promises"; import * as path from "path"; import * as url from "url"; import "ts-morph"; import { tsImport } from "tsx/esm/api"; var defaultConfigFilePath = "drizzle-zero.config.ts"; var getDefaultConfigFilePath = async () => { const fullConfigPath = path.resolve(process.cwd(), defaultConfigFilePath); try { await fs.access(fullConfigPath); } catch (error) { return null; } return "drizzle-zero.config.ts"; }; var getConfigFromFile = async ({ configFilePath, tsProject }) => { const fullConfigPath = path.resolve(process.cwd(), configFilePath); try { await fs.access(fullConfigPath); } catch (error) { throw new Error( `\u274C drizzle-zero: Failed to find config file at ${fullConfigPath}` ); } const zeroConfigFilePathUrl = url.pathToFileURL(fullConfigPath).href; const zeroConfigImport = await tsImport( zeroConfigFilePathUrl, import.meta.url ); const exportName = (zeroConfigImport == null ? void 0 : zeroConfigImport.default) ? "default" : "schema"; const zeroSchema = (zeroConfigImport == null ? void 0 : zeroConfigImport.default) ?? (zeroConfigImport == null ? void 0 : zeroConfigImport.schema); const typeDeclarations = await getZeroSchemaDefsFromConfig({ tsProject, configPath: fullConfigPath, exportName }); return { type: "config", zeroSchema, exportName, zeroSchemaTypeDeclarations: typeDeclarations }; }; async function getZeroSchemaDefsFromConfig({ tsProject, configPath, exportName }) { const fileName = configPath.slice(configPath.lastIndexOf("/") + 1); const sourceFile = tsProject.getSourceFile(fileName); if (!sourceFile) { throw new Error( `\u274C drizzle-zero: Failed to find type definitions for ${fileName}` ); } const exportDeclarations = sourceFile.getExportedDeclarations(); for (const [name, declarations] of exportDeclarations.entries()) { for (const declaration of declarations) { if (exportName === name) { return [name, declaration]; } } } throw new Error( `\u274C drizzle-zero: No config type found in the config file - did you export \`default\` or \`schema\`? Found: ${sourceFile.getVariableDeclarations().map((v) => v.getName()).join(", ")}` ); } // src/cli/drizzle-kit.ts import * as fs2 from "fs/promises"; import * as path2 from "path"; import * as url2 from "url"; import "ts-morph"; import { tsImport as tsImport2 } from "tsx/esm/api"; // src/relations.ts import { createSchema } from "@rocicorp/zero"; import { createTableRelationsHelpers, getTableName as getTableName3, is as is2, One, Relations, Table as Table2 } from "drizzle-orm"; // src/tables.ts import { boolean as zeroBoolean, enumeration as zeroEnumeration, json as zeroJson, number as zeroNumber, string as zeroString, table as zeroTable } from "@rocicorp/zero"; import { getTableColumns, getTableName as getTableName2 } from "drizzle-orm"; import { toCamelCase, toSnakeCase } from "drizzle-orm/casing"; // src/db.ts import { getTableName, is } from "drizzle-orm"; import { getTableConfig, PgTable } from "drizzle-orm/pg-core"; var getTableConfigForDatabase = (table) => { if (is(table, PgTable)) { return getTableConfig(table); } throw new Error( `drizzle-zero: Unsupported table type: ${getTableName(table)}. Only Postgres tables are supported.` ); }; // src/drizzle-to-zero.ts var drizzleDataTypeToZeroType = { number: "number", bigint: "number", boolean: "boolean", date: "number" }; var drizzleColumnTypeToZeroType = { PgText: "string", PgChar: "string", PgVarchar: "string", PgUUID: "string", PgEnumColumn: "string", PgJsonb: "json", PgJson: "json", PgNumeric: "number", PgDateString: "number", PgTimestampString: "number" }; // src/util.ts function typedEntries(obj) { return Object.entries(obj); } function debugLog(debug, message, ...args) { if (debug) { console.log(`\u2139\uFE0F drizzle-zero: ${message}`, ...args); } } // src/tables.ts var createZeroTableBuilder = (tableName, table, columns, debug, casing) => { const actualTableName = getTableName2(table); const tableColumns = getTableColumns(table); const tableConfig = getTableConfigForDatabase(table); const primaryKeysFromColumns = []; const columnsMapped = typedEntries(tableColumns).reduce( (acc, [key, column]) => { const columnConfig = columns == null ? void 0 : columns[key]; if (columnConfig === false) { debugLog( debug, `Skipping column ${String(key)} because columnConfig is false` ); return acc; } const resolvedColumnName = !column.keyAsName || casing === void 0 ? column.name : casing === "camelCase" ? toCamelCase(column.name) : toSnakeCase(column.name); if (typeof columnConfig !== "boolean" && typeof columnConfig !== "object" && typeof columnConfig !== "undefined") { throw new Error( `drizzle-zero: Invalid column config for column ${resolvedColumnName} - expected boolean or ColumnBuilder but was ${typeof columnConfig}` ); } const isColumnBuilder = (value) => typeof value === "object" && value !== null && "schema" in value; const isColumnConfigOverride = isColumnBuilder(columnConfig); const type = drizzleColumnTypeToZeroType[column.columnType] ?? drizzleDataTypeToZeroType[column.dataType] ?? null; if (type === null && !isColumnConfigOverride) { console.warn( `\u{1F6A8} drizzle-zero: Unsupported column type: ${resolvedColumnName} - ${column.columnType} (${column.dataType}). It will not be included in the output. Must be supported by Zero, e.g.: ${Object.keys({ ...drizzleDataTypeToZeroType, ...drizzleColumnTypeToZeroType }).join(" | ")}` ); return acc; } const isColumnOptional = typeof columnConfig === "boolean" || typeof columnConfig === "undefined" ? column.hasDefault && column.defaultFn === void 0 ? true : !column.notNull : isColumnConfigOverride ? columnConfig.schema.optional : false; if (column.primary) { primaryKeysFromColumns.push(String(key)); } if (columnConfig && typeof columnConfig !== "boolean") { return { ...acc, [key]: columnConfig }; } const schemaValue = column.enumValues ? zeroEnumeration() : type === "string" ? zeroString() : type === "number" ? zeroNumber() : type === "json" ? zeroJson() : zeroBoolean(); const schemaValueWithFrom = resolvedColumnName !== key ? schemaValue.from(resolvedColumnName) : schemaValue; return { ...acc, [key]: isColumnOptional ? schemaValueWithFrom.optional() : schemaValueWithFrom }; }, {} ); const primaryKeys = [ ...primaryKeysFromColumns, ...tableConfig.primaryKeys.flatMap( (k) => k.columns.map( (c) => getDrizzleColumnKeyFromColumnName({ columnName: c.name, table: c.table }) ) ) ]; if (!primaryKeys.length) { throw new Error( `drizzle-zero: No primary keys found in table - ${actualTableName}. Did you forget to define a primary key?` ); } const resolvedTableName = tableConfig.schema ? `${tableConfig.schema}.${actualTableName}` : actualTableName; const zeroBuilder = zeroTable(tableName); const zeroBuilderWithFrom = resolvedTableName !== tableName ? zeroBuilder.from(resolvedTableName) : zeroBuilder; return zeroBuilderWithFrom.columns(columnsMapped).primaryKey(...primaryKeys); }; var getDrizzleColumnKeyFromColumnName = ({ columnName, table }) => { var _a; const tableColumns = getTableColumns(table); return (_a = typedEntries(tableColumns).find( ([_name, column]) => column.name === columnName )) == null ? void 0 : _a[0]; }; // src/relations.ts var drizzleZeroConfig = (schema, config) => { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r; let tables = []; const tableColumnNamesForSourceTable = /* @__PURE__ */ new Map(); const assertRelationNameIsNotAColumnName = ({ sourceTableName, relationName }) => { const tableColumnNames = tableColumnNamesForSourceTable.get(sourceTableName); if (tableColumnNames == null ? void 0 : tableColumnNames.has(relationName)) { throw new Error( `drizzle-zero: Invalid relationship name for ${String(sourceTableName)}.${relationName}: there is already a table column with the name ${relationName} and this cannot be used as a relationship name` ); } }; for (const [tableName, tableOrRelations] of typedEntries(schema)) { if (!tableOrRelations) { throw new Error( `drizzle-zero: table or relation with key ${String(tableName)} is not defined` ); } if (is2(tableOrRelations, Table2)) { const table = tableOrRelations; const tableConfig = (_a = config == null ? void 0 : config.tables) == null ? void 0 : _a[tableName]; if (tableConfig === false) { debugLog( config == null ? void 0 : config.debug, `Skipping table ${String(tableName)} - no config provided` ); continue; } const tableSchema = createZeroTableBuilder( String(tableName), table, tableConfig, config == null ? void 0 : config.debug, config == null ? void 0 : config.casing ); tables.push(tableSchema); const tableColumnNames = /* @__PURE__ */ new Set(); for (const columnName of Object.keys(tableSchema.schema.columns)) { tableColumnNames.add(columnName); } tableColumnNamesForSourceTable.set(String(tableName), tableColumnNames); } } let relationships = {}; if (config == null ? void 0 : config.manyToMany) { for (const [sourceTableName, manyConfig] of Object.entries( config.manyToMany )) { if (!manyConfig) continue; for (const [ relationName, [junctionTableNameOrObject, destTableNameOrObject] ] of Object.entries(manyConfig)) { if (typeof junctionTableNameOrObject === "string" && typeof destTableNameOrObject === "string") { const junctionTableName = junctionTableNameOrObject; const destTableName = destTableNameOrObject; const sourceTable = (_b = typedEntries(schema).find( ([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === sourceTableName )) == null ? void 0 : _b[1]; const destTable = (_c = typedEntries(schema).find( ([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === destTableName )) == null ? void 0 : _c[1]; const junctionTable = (_d = typedEntries(schema).find( ([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === junctionTableName )) == null ? void 0 : _d[1]; if (!sourceTable || !destTable || !junctionTable || !is2(sourceTable, Table2) || !is2(destTable, Table2) || !is2(junctionTable, Table2)) { throw new Error( `drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Could not find ${!sourceTable ? "source" : !destTable ? "destination" : "junction"} table` ); } const sourceJunctionFields = findRelationSourceAndDestFields(schema, { sourceTable, referencedTableName: getTableName3(junctionTable) }); const junctionDestFields = findRelationSourceAndDestFields(schema, { sourceTable: destTable, referencedTableName: getTableName3(junctionTable) }); if (!sourceJunctionFields.sourceFieldNames.length || !junctionDestFields.sourceFieldNames.length || !junctionDestFields.destFieldNames.length || !sourceJunctionFields.destFieldNames.length) { throw new Error( `drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Could not find relationships in junction table ${junctionTableName}` ); } if (!((_e = config.tables) == null ? void 0 : _e[junctionTableName]) || !((_f = config.tables) == null ? void 0 : _f[sourceTableName]) || !((_g = config.tables) == null ? void 0 : _g[destTableName])) { debugLog( config.debug, `Skipping many-to-many relationship - tables not in schema config:`, { junctionTable, sourceTableName, destTableName } ); continue; } assertRelationNameIsNotAColumnName({ sourceTableName, relationName }); relationships[sourceTableName] = { ...(relationships == null ? void 0 : relationships[sourceTableName]) ?? {}, [relationName]: [ { sourceField: sourceJunctionFields.sourceFieldNames, destField: sourceJunctionFields.destFieldNames, destSchema: junctionTableName, cardinality: "many" }, { sourceField: junctionDestFields.destFieldNames, destField: junctionDestFields.sourceFieldNames, destSchema: destTableName, cardinality: "many" } ] }; debugLog(config.debug, `Added many-to-many relationship:`, { sourceTable: sourceTableName, relationName, relationship: (_h = relationships[sourceTableName]) == null ? void 0 : _h[relationName] }); } else { const junctionTableName = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.destTable) ?? null; const junctionSourceField = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.sourceField) ?? null; const junctionDestField = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.destField) ?? null; const destTableName = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.destTable) ?? null; const destSourceField = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.sourceField) ?? null; const destDestField = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.destField) ?? null; if (!junctionSourceField || !junctionDestField || !destSourceField || !destDestField || !junctionTableName || !destTableName) { throw new Error( `drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Not all required fields were provided.` ); } if (!((_i = config.tables) == null ? void 0 : _i[junctionTableName]) || !((_j = config.tables) == null ? void 0 : _j[sourceTableName]) || !((_k = config.tables) == null ? void 0 : _k[destTableName])) { continue; } assertRelationNameIsNotAColumnName({ sourceTableName, relationName }); relationships[sourceTableName] = { ...(relationships == null ? void 0 : relationships[sourceTableName]) ?? {}, [relationName]: [ { sourceField: junctionSourceField, destField: junctionDestField, destSchema: junctionTableName, cardinality: "many" }, { sourceField: destSourceField, destField: destDestField, destSchema: destTableName, cardinality: "many" } ] }; } } } } for (const [relationName, tableOrRelations] of typedEntries(schema)) { if (!tableOrRelations) { throw new Error( `drizzle-zero: table or relation with key ${String(relationName)} is not defined` ); } if (is2(tableOrRelations, Relations)) { const actualTableName = getTableName3(tableOrRelations.table); const tableName = getDrizzleKeyFromTableName({ schema, tableName: actualTableName }); const relationsConfig = getRelationsConfig(tableOrRelations); for (const relation of Object.values(relationsConfig)) { let sourceFieldNames = []; let destFieldNames = []; if (is2(relation, One)) { sourceFieldNames = ((_m = (_l = relation == null ? void 0 : relation.config) == null ? void 0 : _l.fields) == null ? void 0 : _m.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f == null ? void 0 : f.name, table: f.table }) )) ?? []; destFieldNames = ((_o = (_n = relation == null ? void 0 : relation.config) == null ? void 0 : _n.references) == null ? void 0 : _o.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f == null ? void 0 : f.name, table: f.table }) )) ?? []; } if (!sourceFieldNames.length || !destFieldNames.length) { if (relation.relationName) { const sourceAndDestFields = findNamedSourceAndDestFields( schema, relation ); sourceFieldNames = sourceAndDestFields.sourceFieldNames; destFieldNames = sourceAndDestFields.destFieldNames; } else { const sourceAndDestFields = findRelationSourceAndDestFields( schema, relation ); sourceFieldNames = sourceAndDestFields.sourceFieldNames; destFieldNames = sourceAndDestFields.destFieldNames; } } if (!sourceFieldNames.length || !destFieldNames.length) { throw new Error( `drizzle-zero: No relationship found for: ${relation.fieldName} (${is2(relation, One) ? "One" : "Many"} from ${String(tableName)} to ${relation.referencedTableName}). Did you forget to define ${relation.relationName ? `a named relation "${relation.relationName}"` : `an opposite ${is2(relation, One) ? "Many" : "One"} relation`}?` ); } const referencedTableKey = getDrizzleKeyFromTableName({ schema, tableName: relation.referencedTableName }); if (typeof (config == null ? void 0 : config.tables) !== "undefined" && (!((_p = config == null ? void 0 : config.tables) == null ? void 0 : _p[tableName]) || !((_q = config == null ? void 0 : config.tables) == null ? void 0 : _q[referencedTableKey]))) { debugLog( config == null ? void 0 : config.debug, `Skipping relation - tables not in schema config:`, { sourceTable: tableName, referencedTable: referencedTableKey } ); continue; } if ((_r = relationships[tableName]) == null ? void 0 : _r[relation.fieldName]) { throw new Error( `drizzle-zero: Duplicate relationship found for: ${relation.fieldName} (from ${String(tableName)} to ${relation.referencedTableName}).` ); } assertRelationNameIsNotAColumnName({ sourceTableName: tableName, relationName: relation.fieldName }); relationships[tableName] = { ...(relationships == null ? void 0 : relationships[tableName]) ?? {}, [relation.fieldName]: [ { sourceField: sourceFieldNames, destField: destFieldNames, destSchema: getDrizzleKeyFromTableName({ schema, tableName: relation.referencedTableName }), cardinality: is2(relation, One) ? "one" : "many" } ] }; } } } const finalSchema = createSchema({ tables, relationships: Object.entries(relationships).map(([key, value]) => ({ name: key, relationships: value })) }); debugLog( config == null ? void 0 : config.debug, "Output Zero schema", JSON.stringify(finalSchema, null, 2) ); return finalSchema; }; var getReferencedTableName = (rel) => { if ("referencedTableName" in rel && rel.referencedTableName) { return rel.referencedTableName; } if ("referencedTable" in rel && rel.referencedTable) { return getTableName3(rel.referencedTable); } return void 0; }; var findRelationSourceAndDestFields = (schema, relation) => { var _a, _b, _c, _d, _e, _f, _g, _h; const sourceTableName = getTableName3(relation.sourceTable); const referencedTableName = getReferencedTableName(relation); for (const tableOrRelations of Object.values(schema)) { if (is2(tableOrRelations, Relations)) { const relationsConfig = getRelationsConfig(tableOrRelations); for (const relationConfig of Object.values(relationsConfig)) { if (!is2(relationConfig, One)) continue; const foundSourceName = getTableName3(relationConfig.sourceTable); const foundReferencedName = getReferencedTableName(relationConfig); if (foundSourceName === referencedTableName && foundReferencedName === sourceTableName) { const sourceFieldNames = ((_b = (_a = relationConfig.config) == null ? void 0 : _a.references) == null ? void 0 : _b.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? []; const destFieldNames = ((_d = (_c = relationConfig.config) == null ? void 0 : _c.fields) == null ? void 0 : _d.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? []; if (sourceFieldNames.length && destFieldNames.length) { return { sourceFieldNames, destFieldNames }; } } if (foundSourceName === sourceTableName && foundReferencedName === referencedTableName) { const sourceFieldNames = ((_f = (_e = relationConfig.config) == null ? void 0 : _e.fields) == null ? void 0 : _f.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? []; const destFieldNames = ((_h = (_g = relationConfig.config) == null ? void 0 : _g.references) == null ? void 0 : _h.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? []; if (sourceFieldNames.length && destFieldNames.length) { return { sourceFieldNames, destFieldNames }; } } } } } return { sourceFieldNames: [], destFieldNames: [] }; }; var findNamedSourceAndDestFields = (schema, relation) => { var _a, _b, _c, _d; for (const tableOrRelations of Object.values(schema)) { if (is2(tableOrRelations, Relations)) { const relationsConfig = getRelationsConfig(tableOrRelations); for (const relationConfig of Object.values(relationsConfig)) { if (is2(relationConfig, One) && relationConfig.relationName === relation.relationName) { return { destFieldNames: ((_b = (_a = relationConfig.config) == null ? void 0 : _a.fields) == null ? void 0 : _b.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? [], sourceFieldNames: ((_d = (_c = relationConfig.config) == null ? void 0 : _c.references) == null ? void 0 : _d.map( (f) => getDrizzleColumnKeyFromColumnName({ columnName: f.name, table: f.table }) )) ?? [] }; } } } } return { sourceFieldNames: [], destFieldNames: [] }; }; var getRelationsConfig = (relations) => { return relations.config( createTableRelationsHelpers(relations.table) ); }; var getDrizzleKeyFromTableName = ({ schema, tableName }) => { var _a; return (_a = typedEntries(schema).find( ([_name, tableOrRelations]) => is2(tableOrRelations, Table2) && getTableName3(tableOrRelations) === tableName )) == null ? void 0 : _a[0]; }; // src/cli/drizzle-kit.ts var getDefaultConfig = async ({ drizzleSchemaPath, drizzleKitConfigPath, tsProject, debug }) => { const { drizzleSchemaPath: resolvedDrizzleSchemaPath, casing: drizzleCasing } = await getFullDrizzleSchemaFilePath({ drizzleSchemaPath, drizzleKitConfigPath }); const resolvedDrizzleSchemaPathUrl = url2.pathToFileURL( resolvedDrizzleSchemaPath ).href; const drizzleSchema = await tsImport2( resolvedDrizzleSchemaPathUrl, import.meta.url ); const zeroSchema = drizzleZeroConfig(drizzleSchema, { casing: drizzleCasing ?? void 0, debug: Boolean(debug) }); return { type: "drizzle-kit", zeroSchema, drizzleSchemaSourceFile: await getDrizzleSchemaSourceFile({ tsProject, drizzleSchemaPath: resolvedDrizzleSchemaPath }), drizzleCasing }; }; var getFullDrizzleSchemaFilePath = async ({ drizzleKitConfigPath, drizzleSchemaPath }) => { if (drizzleSchemaPath) { const fullPath = path2.resolve(process.cwd(), drizzleSchemaPath); try { await fs2.access(fullPath); return { drizzleSchemaPath: fullPath, casing: null }; } catch (error) { console.error( `\u274C drizzle-zero: could not find Drizzle schema file at ${fullPath}` ); process.exit(1); } } if (drizzleKitConfigPath) { const fullPath = path2.resolve(process.cwd(), drizzleKitConfigPath); try { await fs2.access(fullPath); const drizzleKitConfigFilePathUrl = url2.pathToFileURL(fullPath).href; const drizzleKitConfigImport = await tsImport2( drizzleKitConfigFilePathUrl, import.meta.url ); const drizzleKitConfig = drizzleKitConfigImport == null ? void 0 : drizzleKitConfigImport.default; try { if (Array.isArray(drizzleKitConfig.schema)) { throw new Error( "\u274C drizzle-zero: Drizzle Kit config schema is an array. Please specify a single schema file for imports to be able to work correctly." ); } if (drizzleKitConfig.schema) { const fullPath2 = path2.resolve(process.cwd(), drizzleKitConfig.schema); await fs2.access(fullPath2); return { drizzleSchemaPath: fullPath2, casing: drizzleKitConfig.casing ?? null }; } } catch (error) { console.error( `\u274C drizzle-zero: could not find Drizzle file pulled from Drizzle Kit config at ${JSON.stringify(drizzleKitConfig)}`, error ); process.exit(1); } return { drizzleSchemaPath: fullPath, casing: drizzleKitConfig.casing ?? null }; } catch (error) { console.error( `\u274C drizzle-zero: could not find Drizzle Kit config file at ${drizzleKitConfigPath}`, error ); process.exit(1); } } console.error(`\u274C drizzle-zero: could not find Drizzle Kit config file`); process.exit(1); }; async function getDrizzleSchemaSourceFile({ tsProject, drizzleSchemaPath }) { const sourceFile = tsProject.getSourceFile(drizzleSchemaPath); if (!sourceFile) { throw new Error( `\u274C drizzle-zero: Failed to find type definitions for ${drizzleSchemaPath}` ); } return sourceFile; } // src/cli/shared.ts import { VariableDeclarationKind } from "ts-morph"; async function getGeneratedSchema({ tsProject, result, outputFilePath, jsFileExtension = false }) { const schemaObjectName = "schema"; const typename = "Schema"; const zeroSchemaGenerated = tsProject.createSourceFile(outputFilePath, "", { overwrite: true }); zeroSchemaGenerated.addImportDeclaration({ moduleSpecifier: "drizzle-zero", namedImports: [{ name: "ZeroCustomType" }], isTypeOnly: true }); let zeroSchemaSpecifier; if (result.type === "config") { const moduleSpecifier = zeroSchemaGenerated.getRelativePathAsModuleSpecifierTo( result.zeroSchemaTypeDeclarations[1].getSourceFile() ); zeroSchemaGenerated.addImportDeclaration({ moduleSpecifier: jsFileExtension ? `${moduleSpecifier}.js` : moduleSpecifier, namedImports: [{ name: result.exportName, alias: "zeroSchema" }], isTypeOnly: true }); zeroSchemaSpecifier = "typeof zeroSchema"; } else { const moduleSpecifier = zeroSchemaGenerated.getRelativePathAsModuleSpecifierTo( result.drizzleSchemaSourceFile ); zeroSchemaGenerated.addImportDeclaration({ moduleSpecifier: jsFileExtension ? `${moduleSpecifier}.js` : moduleSpecifier, namespaceImport: "drizzleSchema", isTypeOnly: true }); zeroSchemaGenerated.addImportDeclaration({ moduleSpecifier: "drizzle-zero", namedImports: [{ name: "DrizzleToZeroSchema" }], isTypeOnly: true }); zeroSchemaGenerated.addTypeAlias({ name: "ZeroSchema", isExported: false, type: `DrizzleToZeroSchema<typeof drizzleSchema${result.drizzleCasing ? `, "${result.drizzleCasing}"` : ""}>` }); zeroSchemaSpecifier = "ZeroSchema"; } const schemaVariable = zeroSchemaGenerated.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, isExported: true, declarations: [ { name: schemaObjectName, initializer: (writer) => { const writeValue = (value, keys = [], indent = 0) => { const indentStr = " ".repeat(indent); if (!value || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value)) { writer.write(JSON.stringify(value)); } else if (typeof value === "object" && value !== null) { writer.write("{"); const entries = Object.entries(value); if (entries.length > 0) { writer.newLine(); for (let i = 0; i < entries.length; i++) { const [key, propValue] = entries[i] ?? []; if (!key) { continue; } writer.write(indentStr + " " + JSON.stringify(key) + ": "); if (key === "customType" && propValue === null) { const tableIndex = 1; const columnIndex = 3; writer.write( `null as unknown as ZeroCustomType<${zeroSchemaSpecifier}, "${keys[tableIndex]}", "${keys[columnIndex]}">` ); } else { writeValue(propValue, [...keys, key], indent + 2); } if (i < entries.length - 1) { writer.write(","); } writer.newLine(); } writer.write(indentStr); } writer.write("}"); } }; writeValue(result.zeroSchema); writer.write(` as const`); } } ] }); schemaVariable.addJsDoc({ description: "\nThe Zero schema object.\nThis type is auto-generated from your Drizzle schema definition." }); const schemaTypeAlias = zeroSchemaGenerated.addTypeAlias({ name: typename, isExported: true, type: `typeof ${schemaObjectName}` }); schemaTypeAlias.addJsDoc({ description: "\nRepresents the Zero schema type.\nThis type is auto-generated from your Drizzle schema definition." }); zeroSchemaGenerated.formatText(); const organizedFile = zeroSchemaGenerated.organizeImports(); const file = organizedFile.getText(); return `/* eslint-disable */ /* tslint:disable */ // noinspection JSUnusedGlobalSymbols // biome-ignore-all /* * ------------------------------------------------------------ * ## This file was automatically generated by drizzle-zero. ## * ## Any changes you make to this file will be overwritten. ## * ## ## * ## Additionally, you should also exclude this file from ## * ## your linter and/or formatter to prevent it from being ## * ## checked or modified. ## * ## ## * ## SOURCE: https://github.com/0xcadams/drizzle-zero ## * ------------------------------------------------------------ */ ${file}`; } // src/cli/index.ts var defaultConfigFile = "./drizzle-zero.config.ts"; var defaultOutputFile = "./zero-schema.gen.ts"; var defaultTsConfigFile = "./tsconfig.json"; var defaultDrizzleKitConfigPath = "./drizzle.config.ts"; async function loadPrettier() { try { return await import("prettier"); } catch (_) { } try { const path4 = __require.resolve("prettier", { paths: [process.cwd()] }); return await import(pathToFileURL3(path4).href); } catch { throw new Error( "\u26A0\uFE0F drizzle-zero: prettier could not be found. Install it locally with\n npm i -D prettier" ); } } async function formatSchema(schema) { try { const prettier = await loadPrettier(); return prettier.format(schema, { parser: "typescript" }); } catch (error) { console.warn("\u26A0\uFE0F drizzle-zero: prettier not found, skipping formatting"); return schema; } } async function main(opts = {}) { const { config, tsConfigPath, format, outputFilePath, drizzleSchemaPath, drizzleKitConfigPath, debug, jsFileExtension } = { ...opts }; const resolvedTsConfigPath = tsConfigPath ?? defaultTsConfigFile; const resolvedOutputFilePath = outputFilePath ?? defaultOutputFile; const defaultConfigFilePath2 = await getDefaultConfigFilePath(); const configFilePath = config ?? defaultConfigFilePath2; if (!configFilePath) { console.log( "\u{1F636}\u200D\u{1F32B}\uFE0F drizzle-zero: Using all tables/columns from Drizzle schema" ); } const tsProject = new Project4({ tsConfigFilePath: resolvedTsConfigPath }); const result = configFilePath ? await getConfigFromFile({ configFilePath, tsProject }) : await getDefaultConfig({ drizzleSchemaPath, drizzleKitConfigPath, tsProject, debug: Boolean(debug) }); if (!(result == null ? void 0 : result.zeroSchema)) { console.error( "\u274C drizzle-zero: No config found in the config file - did you export `default` or `schema`?" ); process.exit(1); } let zeroSchemaGenerated = await getGeneratedSchema({ tsProject, result, outputFilePath: resolvedOutputFilePath, jsFileExtension: Boolean(jsFileExtension) }); if (format) { zeroSchemaGenerated = await formatSchema(zeroSchemaGenerated); } return zeroSchemaGenerated; } async function cli() { const program = new Command(); program.name("drizzle-zero").description("The CLI for converting Drizzle ORM schemas to Zero schemas"); program.command("generate").option( "-c, --config <input-file>", `Path to the ${defaultConfigFile} configuration file` ).option("-s, --schema <input-file>", `Path to the Drizzle schema file`).option( "-k, --drizzle-kit-config <input-file>", `Path to the Drizzle Kit config file`, defaultDrizzleKitConfigPath ).option( "-o, --output <output-file>", `Path to the generated output file`, defaultOutputFile ).option( "-t, --tsconfig <tsconfig-file>", `Path to the custom tsconfig file`, defaultTsConfigFile ).option("-f, --format", `Format the generated schema`, false).option("-d, --debug", `Enable debug mode`).option( "-j, --js-file-extension", `Add a .js file extension to the output (for usage without "bundler" module resolution)`, false ).action(async (command) => { console.log(`\u2699\uFE0F drizzle-zero: Generating zero schema...`); const zeroSchema = await main({ config: command.config, tsConfigPath: command.tsconfig, format: command.format, outputFilePath: command.output, drizzleSchemaPath: command.schema, drizzleKitConfigPath: command.drizzleKitConfig, debug: command.debug, jsFileExtension: command.jsFileExtension }); if (command.output) { await fs3.writeFile( path3.resolve(process.cwd(), command.output), zeroSchema ); console.log( `\u2705 drizzle-zero: Zero schema written to ${command.output}` ); } else { console.log("drizzle-zero: ", { schema: zeroSchema }); } }); program.parse(); } cli().catch((error) => { console.error("\u274C drizzle-zero error:", error); process.exit(1); }); export { formatSchema, loadPrettier };