@prairielearn/postgres-tools 3.0.0 → 3.0.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @prairielearn/postgres-tools
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f929a68: Replace lodash with es-toolkit
8
+ - Updated dependencies [f929a68]
9
+ - @prairielearn/postgres@5.0.1
10
+
3
11
  ## 3.0.0
4
12
 
5
13
  ### Major Changes
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAqBA,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAmKD,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,mBAY7F;AAED,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,mBAarB;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,mBAarB;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,mBAarB","sourcesContent":["import path from 'node:path';\n\nimport chalk from 'chalk';\nimport { structuredPatch } from 'diff';\nimport fs from 'fs-extra';\nimport _ from 'lodash';\n\nimport { describeDatabase, formatDatabaseDescription } from './describe.js';\n\ninterface DatabaseInfo {\n type: 'database';\n name: string;\n}\n\ninterface DirectoryInfo {\n type: 'directory';\n path: string;\n}\n\ntype DiffTarget = DatabaseInfo | DirectoryInfo;\n\ninterface DiffOptions {\n coloredOutput?: boolean;\n}\n\ninterface Description {\n tables: Record<string, string>;\n enums: Record<string, string>;\n}\n\nasync function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Promise<string> {\n function formatText(text: string, formatter?: ((s: string) => string) | null): string {\n if (options.coloredOutput && formatter) {\n return formatter(text);\n }\n return text;\n }\n\n const db2Name = db2.type === 'database' ? db2.name : db2.path;\n const db2NameBold = formatText(db2Name, chalk.bold);\n\n let result = '';\n\n const description1 = await loadDescription(db1);\n const description2 = await loadDescription(db2);\n\n // Determine if both databases have the same tables\n const tablesMissingFrom1 = _.difference(\n Object.keys(description2.tables),\n Object.keys(description1.tables),\n );\n const tablesMissingFrom2 = _.difference(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n\n if (tablesMissingFrom1.length > 0) {\n result += formatText(`Tables added to ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom1.map((table) => `+ ${table}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (tablesMissingFrom2.length > 0) {\n result += formatText(`Tables missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom2.map((table) => `- ${table}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if both databases have the same enums\n const enumsMissingFrom1 = _.difference(\n Object.keys(description2.enums),\n Object.keys(description1.enums),\n );\n const enumsMissingFrom2 = _.difference(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n\n if (enumsMissingFrom1.length > 0) {\n result += formatText(`Enums added to ${db2NameBold} (${db1.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (enumsMissingFrom2.length > 0) {\n result += formatText(`Enums missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if the columns of any table differ\n const intersection = _.intersection(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n for (const table of intersection) {\n const patch = structuredPatch(\n `tables/${table}`,\n `tables/${table}`,\n description1.tables[table],\n description2.tables[table],\n );\n\n if (patch.hunks.length === 0) continue;\n\n const boldTable = formatText(table, chalk.bold);\n result += formatText(`Differences in ${boldTable} table\\n`, chalk.underline);\n\n patch.hunks.forEach((hunk, index) => {\n if (index !== 0) {\n result += formatText('...\\n', chalk.gray);\n }\n hunk.lines.forEach((line) => {\n const color = line[0] === '+' ? chalk.green : line[0] === '-' ? chalk.red : null;\n result += formatText(line, color);\n result += '\\n';\n });\n });\n\n result += '\\n\\n';\n }\n\n // Determine if the values of any enums differ\n const enumsIntersection = _.intersection(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n for (const enumName of enumsIntersection) {\n // We don't need to do a particularly fancy diff here, since\n // enums are just represented here as strings\n if (description1.enums[enumName].trim() !== description2.enums[enumName].trim()) {\n const boldEnum = formatText(enumName, chalk.bold);\n result += formatText(`Differences in ${boldEnum} enum\\n`);\n result += formatText(`- ${description1.enums[enumName].trim()}\\n`, chalk.red);\n result += formatText(`+ ${description2.enums[enumName].trim()}\\n`, chalk.green);\n result += '\\n\\n';\n }\n }\n\n return result;\n}\n\nasync function loadDescriptionFromDisk(dirPath: string): Promise<Description> {\n const description: Description = {\n tables: {},\n enums: {},\n };\n\n const tables = await fs.readdir(path.join(dirPath, 'tables'));\n for (const table of tables) {\n const data = await fs.readFile(path.join(dirPath, 'tables', table), 'utf8');\n description.tables[table.replace('.pg', '')] = data;\n }\n\n const enums = await fs.readdir(path.join(dirPath, 'enums'));\n for (const enumName of enums) {\n const data = await fs.readFile(path.join(dirPath, 'enums', enumName), 'utf8');\n description.enums[enumName.replace('.pg', '')] = data;\n }\n\n return description;\n}\n\nasync function loadDescriptionFromDatabase(name: string) {\n const description = await describeDatabase(name);\n return formatDatabaseDescription(description, { coloredOutput: false });\n}\n\nasync function loadDescription(db: DiffTarget): Promise<Description> {\n if (db.type === 'database') {\n return loadDescriptionFromDatabase(db.name);\n } else if (db.type === 'directory') {\n return loadDescriptionFromDisk(db.path);\n } else {\n throw new Error('Invalid database type');\n }\n}\n\nexport async function diffDatabases(database1: string, database2: string, options: DiffOptions) {\n return diff(\n {\n type: 'database',\n name: database1,\n },\n {\n type: 'database',\n name: database2,\n },\n options,\n );\n}\n\nexport async function diffDatabaseAndDirectory(\n database: string,\n directory: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'database',\n name: database,\n },\n {\n type: 'directory',\n path: directory,\n },\n options,\n );\n}\n\nexport async function diffDirectoryAndDatabase(\n directory: string,\n database: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory,\n },\n {\n type: 'database',\n name: database,\n },\n options,\n );\n}\n\nexport async function diffDirectories(\n directory1: string,\n directory2: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory1,\n },\n {\n type: 'directory',\n path: directory2,\n },\n options,\n );\n}\n"]}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAqBA,UAAU,WAAW;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAmKD,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,mBAY7F;AAED,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,WAAW,mBAarB;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,mBAarB;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,mBAarB","sourcesContent":["import path from 'node:path';\n\nimport chalk from 'chalk';\nimport { structuredPatch } from 'diff';\nimport { difference, intersection } from 'es-toolkit';\nimport fs from 'fs-extra';\n\nimport { describeDatabase, formatDatabaseDescription } from './describe.js';\n\ninterface DatabaseInfo {\n type: 'database';\n name: string;\n}\n\ninterface DirectoryInfo {\n type: 'directory';\n path: string;\n}\n\ntype DiffTarget = DatabaseInfo | DirectoryInfo;\n\ninterface DiffOptions {\n coloredOutput?: boolean;\n}\n\ninterface Description {\n tables: Record<string, string>;\n enums: Record<string, string>;\n}\n\nasync function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Promise<string> {\n function formatText(text: string, formatter?: ((s: string) => string) | null): string {\n if (options.coloredOutput && formatter) {\n return formatter(text);\n }\n return text;\n }\n\n const db2Name = db2.type === 'database' ? db2.name : db2.path;\n const db2NameBold = formatText(db2Name, chalk.bold);\n\n let result = '';\n\n const description1 = await loadDescription(db1);\n const description2 = await loadDescription(db2);\n\n // Determine if both databases have the same tables\n const tablesMissingFrom1 = difference(\n Object.keys(description2.tables),\n Object.keys(description1.tables),\n );\n const tablesMissingFrom2 = difference(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n\n if (tablesMissingFrom1.length > 0) {\n result += formatText(`Tables added to ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom1.map((table) => `+ ${table}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (tablesMissingFrom2.length > 0) {\n result += formatText(`Tables missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom2.map((table) => `- ${table}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if both databases have the same enums\n const enumsMissingFrom1 = difference(\n Object.keys(description2.enums),\n Object.keys(description1.enums),\n );\n const enumsMissingFrom2 = difference(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n\n if (enumsMissingFrom1.length > 0) {\n result += formatText(`Enums added to ${db2NameBold} (${db1.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (enumsMissingFrom2.length > 0) {\n result += formatText(`Enums missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if the columns of any table differ\n const tableIntersection = intersection(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n for (const table of tableIntersection) {\n const patch = structuredPatch(\n `tables/${table}`,\n `tables/${table}`,\n description1.tables[table],\n description2.tables[table],\n );\n\n if (patch.hunks.length === 0) continue;\n\n const boldTable = formatText(table, chalk.bold);\n result += formatText(`Differences in ${boldTable} table\\n`, chalk.underline);\n\n patch.hunks.forEach((hunk, index) => {\n if (index !== 0) {\n result += formatText('...\\n', chalk.gray);\n }\n hunk.lines.forEach((line) => {\n const color = line[0] === '+' ? chalk.green : line[0] === '-' ? chalk.red : null;\n result += formatText(line, color);\n result += '\\n';\n });\n });\n\n result += '\\n\\n';\n }\n\n // Determine if the values of any enums differ\n const enumsIntersection = intersection(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n for (const enumName of enumsIntersection) {\n // We don't need to do a particularly fancy diff here, since\n // enums are just represented here as strings\n if (description1.enums[enumName].trim() !== description2.enums[enumName].trim()) {\n const boldEnum = formatText(enumName, chalk.bold);\n result += formatText(`Differences in ${boldEnum} enum\\n`);\n result += formatText(`- ${description1.enums[enumName].trim()}\\n`, chalk.red);\n result += formatText(`+ ${description2.enums[enumName].trim()}\\n`, chalk.green);\n result += '\\n\\n';\n }\n }\n\n return result;\n}\n\nasync function loadDescriptionFromDisk(dirPath: string): Promise<Description> {\n const description: Description = {\n tables: {},\n enums: {},\n };\n\n const tables = await fs.readdir(path.join(dirPath, 'tables'));\n for (const table of tables) {\n const data = await fs.readFile(path.join(dirPath, 'tables', table), 'utf8');\n description.tables[table.replace('.pg', '')] = data;\n }\n\n const enums = await fs.readdir(path.join(dirPath, 'enums'));\n for (const enumName of enums) {\n const data = await fs.readFile(path.join(dirPath, 'enums', enumName), 'utf8');\n description.enums[enumName.replace('.pg', '')] = data;\n }\n\n return description;\n}\n\nasync function loadDescriptionFromDatabase(name: string) {\n const description = await describeDatabase(name);\n return formatDatabaseDescription(description, { coloredOutput: false });\n}\n\nasync function loadDescription(db: DiffTarget): Promise<Description> {\n if (db.type === 'database') {\n return loadDescriptionFromDatabase(db.name);\n } else if (db.type === 'directory') {\n return loadDescriptionFromDisk(db.path);\n } else {\n throw new Error('Invalid database type');\n }\n}\n\nexport async function diffDatabases(database1: string, database2: string, options: DiffOptions) {\n return diff(\n {\n type: 'database',\n name: database1,\n },\n {\n type: 'database',\n name: database2,\n },\n options,\n );\n}\n\nexport async function diffDatabaseAndDirectory(\n database: string,\n directory: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'database',\n name: database,\n },\n {\n type: 'directory',\n path: directory,\n },\n options,\n );\n}\n\nexport async function diffDirectoryAndDatabase(\n directory: string,\n database: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory,\n },\n {\n type: 'database',\n name: database,\n },\n options,\n );\n}\n\nexport async function diffDirectories(\n directory1: string,\n directory2: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory1,\n },\n {\n type: 'directory',\n path: directory2,\n },\n options,\n );\n}\n"]}
package/dist/diff.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import chalk from 'chalk';
3
3
  import { structuredPatch } from 'diff';
4
+ import { difference, intersection } from 'es-toolkit';
4
5
  import fs from 'fs-extra';
5
- import _ from 'lodash';
6
6
  import { describeDatabase, formatDatabaseDescription } from './describe.js';
7
7
  async function diff(db1, db2, options) {
8
8
  function formatText(text, formatter) {
@@ -17,8 +17,8 @@ async function diff(db1, db2, options) {
17
17
  const description1 = await loadDescription(db1);
18
18
  const description2 = await loadDescription(db2);
19
19
  // Determine if both databases have the same tables
20
- const tablesMissingFrom1 = _.difference(Object.keys(description2.tables), Object.keys(description1.tables));
21
- const tablesMissingFrom2 = _.difference(Object.keys(description1.tables), Object.keys(description2.tables));
20
+ const tablesMissingFrom1 = difference(Object.keys(description2.tables), Object.keys(description1.tables));
21
+ const tablesMissingFrom2 = difference(Object.keys(description1.tables), Object.keys(description2.tables));
22
22
  if (tablesMissingFrom1.length > 0) {
23
23
  result += formatText(`Tables added to ${db2NameBold} (${db2.type})\n`, chalk.underline);
24
24
  result += formatText(tablesMissingFrom1.map((table) => `+ ${table}`).join('\n') + '\n\n', chalk.green);
@@ -28,8 +28,8 @@ async function diff(db1, db2, options) {
28
28
  result += formatText(tablesMissingFrom2.map((table) => `- ${table}`).join('\n') + '\n\n', chalk.red);
29
29
  }
30
30
  // Determine if both databases have the same enums
31
- const enumsMissingFrom1 = _.difference(Object.keys(description2.enums), Object.keys(description1.enums));
32
- const enumsMissingFrom2 = _.difference(Object.keys(description1.enums), Object.keys(description2.enums));
31
+ const enumsMissingFrom1 = difference(Object.keys(description2.enums), Object.keys(description1.enums));
32
+ const enumsMissingFrom2 = difference(Object.keys(description1.enums), Object.keys(description2.enums));
33
33
  if (enumsMissingFrom1.length > 0) {
34
34
  result += formatText(`Enums added to ${db2NameBold} (${db1.type})\n`, chalk.underline);
35
35
  result += formatText(enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\n') + '\n\n', chalk.green);
@@ -39,8 +39,8 @@ async function diff(db1, db2, options) {
39
39
  result += formatText(enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\n') + '\n\n', chalk.red);
40
40
  }
41
41
  // Determine if the columns of any table differ
42
- const intersection = _.intersection(Object.keys(description1.tables), Object.keys(description2.tables));
43
- for (const table of intersection) {
42
+ const tableIntersection = intersection(Object.keys(description1.tables), Object.keys(description2.tables));
43
+ for (const table of tableIntersection) {
44
44
  const patch = structuredPatch(`tables/${table}`, `tables/${table}`, description1.tables[table], description2.tables[table]);
45
45
  if (patch.hunks.length === 0)
46
46
  continue;
@@ -59,7 +59,7 @@ async function diff(db1, db2, options) {
59
59
  result += '\n\n';
60
60
  }
61
61
  // Determine if the values of any enums differ
62
- const enumsIntersection = _.intersection(Object.keys(description1.enums), Object.keys(description2.enums));
62
+ const enumsIntersection = intersection(Object.keys(description1.enums), Object.keys(description2.enums));
63
63
  for (const enumName of enumsIntersection) {
64
64
  // We don't need to do a particularly fancy diff here, since
65
65
  // enums are just represented here as strings
package/dist/diff.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\n\nimport chalk from 'chalk';\nimport { structuredPatch } from 'diff';\nimport fs from 'fs-extra';\nimport _ from 'lodash';\n\nimport { describeDatabase, formatDatabaseDescription } from './describe.js';\n\ninterface DatabaseInfo {\n type: 'database';\n name: string;\n}\n\ninterface DirectoryInfo {\n type: 'directory';\n path: string;\n}\n\ntype DiffTarget = DatabaseInfo | DirectoryInfo;\n\ninterface DiffOptions {\n coloredOutput?: boolean;\n}\n\ninterface Description {\n tables: Record<string, string>;\n enums: Record<string, string>;\n}\n\nasync function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Promise<string> {\n function formatText(text: string, formatter?: ((s: string) => string) | null): string {\n if (options.coloredOutput && formatter) {\n return formatter(text);\n }\n return text;\n }\n\n const db2Name = db2.type === 'database' ? db2.name : db2.path;\n const db2NameBold = formatText(db2Name, chalk.bold);\n\n let result = '';\n\n const description1 = await loadDescription(db1);\n const description2 = await loadDescription(db2);\n\n // Determine if both databases have the same tables\n const tablesMissingFrom1 = _.difference(\n Object.keys(description2.tables),\n Object.keys(description1.tables),\n );\n const tablesMissingFrom2 = _.difference(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n\n if (tablesMissingFrom1.length > 0) {\n result += formatText(`Tables added to ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom1.map((table) => `+ ${table}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (tablesMissingFrom2.length > 0) {\n result += formatText(`Tables missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom2.map((table) => `- ${table}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if both databases have the same enums\n const enumsMissingFrom1 = _.difference(\n Object.keys(description2.enums),\n Object.keys(description1.enums),\n );\n const enumsMissingFrom2 = _.difference(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n\n if (enumsMissingFrom1.length > 0) {\n result += formatText(`Enums added to ${db2NameBold} (${db1.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (enumsMissingFrom2.length > 0) {\n result += formatText(`Enums missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if the columns of any table differ\n const intersection = _.intersection(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n for (const table of intersection) {\n const patch = structuredPatch(\n `tables/${table}`,\n `tables/${table}`,\n description1.tables[table],\n description2.tables[table],\n );\n\n if (patch.hunks.length === 0) continue;\n\n const boldTable = formatText(table, chalk.bold);\n result += formatText(`Differences in ${boldTable} table\\n`, chalk.underline);\n\n patch.hunks.forEach((hunk, index) => {\n if (index !== 0) {\n result += formatText('...\\n', chalk.gray);\n }\n hunk.lines.forEach((line) => {\n const color = line[0] === '+' ? chalk.green : line[0] === '-' ? chalk.red : null;\n result += formatText(line, color);\n result += '\\n';\n });\n });\n\n result += '\\n\\n';\n }\n\n // Determine if the values of any enums differ\n const enumsIntersection = _.intersection(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n for (const enumName of enumsIntersection) {\n // We don't need to do a particularly fancy diff here, since\n // enums are just represented here as strings\n if (description1.enums[enumName].trim() !== description2.enums[enumName].trim()) {\n const boldEnum = formatText(enumName, chalk.bold);\n result += formatText(`Differences in ${boldEnum} enum\\n`);\n result += formatText(`- ${description1.enums[enumName].trim()}\\n`, chalk.red);\n result += formatText(`+ ${description2.enums[enumName].trim()}\\n`, chalk.green);\n result += '\\n\\n';\n }\n }\n\n return result;\n}\n\nasync function loadDescriptionFromDisk(dirPath: string): Promise<Description> {\n const description: Description = {\n tables: {},\n enums: {},\n };\n\n const tables = await fs.readdir(path.join(dirPath, 'tables'));\n for (const table of tables) {\n const data = await fs.readFile(path.join(dirPath, 'tables', table), 'utf8');\n description.tables[table.replace('.pg', '')] = data;\n }\n\n const enums = await fs.readdir(path.join(dirPath, 'enums'));\n for (const enumName of enums) {\n const data = await fs.readFile(path.join(dirPath, 'enums', enumName), 'utf8');\n description.enums[enumName.replace('.pg', '')] = data;\n }\n\n return description;\n}\n\nasync function loadDescriptionFromDatabase(name: string) {\n const description = await describeDatabase(name);\n return formatDatabaseDescription(description, { coloredOutput: false });\n}\n\nasync function loadDescription(db: DiffTarget): Promise<Description> {\n if (db.type === 'database') {\n return loadDescriptionFromDatabase(db.name);\n } else if (db.type === 'directory') {\n return loadDescriptionFromDisk(db.path);\n } else {\n throw new Error('Invalid database type');\n }\n}\n\nexport async function diffDatabases(database1: string, database2: string, options: DiffOptions) {\n return diff(\n {\n type: 'database',\n name: database1,\n },\n {\n type: 'database',\n name: database2,\n },\n options,\n );\n}\n\nexport async function diffDatabaseAndDirectory(\n database: string,\n directory: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'database',\n name: database,\n },\n {\n type: 'directory',\n path: directory,\n },\n options,\n );\n}\n\nexport async function diffDirectoryAndDatabase(\n directory: string,\n database: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory,\n },\n {\n type: 'database',\n name: database,\n },\n options,\n );\n}\n\nexport async function diffDirectories(\n directory1: string,\n directory2: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory1,\n },\n {\n type: 'directory',\n path: directory2,\n },\n options,\n );\n}\n"]}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\n\nimport chalk from 'chalk';\nimport { structuredPatch } from 'diff';\nimport { difference, intersection } from 'es-toolkit';\nimport fs from 'fs-extra';\n\nimport { describeDatabase, formatDatabaseDescription } from './describe.js';\n\ninterface DatabaseInfo {\n type: 'database';\n name: string;\n}\n\ninterface DirectoryInfo {\n type: 'directory';\n path: string;\n}\n\ntype DiffTarget = DatabaseInfo | DirectoryInfo;\n\ninterface DiffOptions {\n coloredOutput?: boolean;\n}\n\ninterface Description {\n tables: Record<string, string>;\n enums: Record<string, string>;\n}\n\nasync function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Promise<string> {\n function formatText(text: string, formatter?: ((s: string) => string) | null): string {\n if (options.coloredOutput && formatter) {\n return formatter(text);\n }\n return text;\n }\n\n const db2Name = db2.type === 'database' ? db2.name : db2.path;\n const db2NameBold = formatText(db2Name, chalk.bold);\n\n let result = '';\n\n const description1 = await loadDescription(db1);\n const description2 = await loadDescription(db2);\n\n // Determine if both databases have the same tables\n const tablesMissingFrom1 = difference(\n Object.keys(description2.tables),\n Object.keys(description1.tables),\n );\n const tablesMissingFrom2 = difference(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n\n if (tablesMissingFrom1.length > 0) {\n result += formatText(`Tables added to ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom1.map((table) => `+ ${table}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (tablesMissingFrom2.length > 0) {\n result += formatText(`Tables missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n tablesMissingFrom2.map((table) => `- ${table}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if both databases have the same enums\n const enumsMissingFrom1 = difference(\n Object.keys(description2.enums),\n Object.keys(description1.enums),\n );\n const enumsMissingFrom2 = difference(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n\n if (enumsMissingFrom1.length > 0) {\n result += formatText(`Enums added to ${db2NameBold} (${db1.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom1.map((enumName) => `+ ${enumName}`).join('\\n') + '\\n\\n',\n chalk.green,\n );\n }\n\n if (enumsMissingFrom2.length > 0) {\n result += formatText(`Enums missing from ${db2NameBold} (${db2.type})\\n`, chalk.underline);\n result += formatText(\n enumsMissingFrom2.map((enumName) => `- ${enumName}`).join('\\n') + '\\n\\n',\n chalk.red,\n );\n }\n\n // Determine if the columns of any table differ\n const tableIntersection = intersection(\n Object.keys(description1.tables),\n Object.keys(description2.tables),\n );\n for (const table of tableIntersection) {\n const patch = structuredPatch(\n `tables/${table}`,\n `tables/${table}`,\n description1.tables[table],\n description2.tables[table],\n );\n\n if (patch.hunks.length === 0) continue;\n\n const boldTable = formatText(table, chalk.bold);\n result += formatText(`Differences in ${boldTable} table\\n`, chalk.underline);\n\n patch.hunks.forEach((hunk, index) => {\n if (index !== 0) {\n result += formatText('...\\n', chalk.gray);\n }\n hunk.lines.forEach((line) => {\n const color = line[0] === '+' ? chalk.green : line[0] === '-' ? chalk.red : null;\n result += formatText(line, color);\n result += '\\n';\n });\n });\n\n result += '\\n\\n';\n }\n\n // Determine if the values of any enums differ\n const enumsIntersection = intersection(\n Object.keys(description1.enums),\n Object.keys(description2.enums),\n );\n for (const enumName of enumsIntersection) {\n // We don't need to do a particularly fancy diff here, since\n // enums are just represented here as strings\n if (description1.enums[enumName].trim() !== description2.enums[enumName].trim()) {\n const boldEnum = formatText(enumName, chalk.bold);\n result += formatText(`Differences in ${boldEnum} enum\\n`);\n result += formatText(`- ${description1.enums[enumName].trim()}\\n`, chalk.red);\n result += formatText(`+ ${description2.enums[enumName].trim()}\\n`, chalk.green);\n result += '\\n\\n';\n }\n }\n\n return result;\n}\n\nasync function loadDescriptionFromDisk(dirPath: string): Promise<Description> {\n const description: Description = {\n tables: {},\n enums: {},\n };\n\n const tables = await fs.readdir(path.join(dirPath, 'tables'));\n for (const table of tables) {\n const data = await fs.readFile(path.join(dirPath, 'tables', table), 'utf8');\n description.tables[table.replace('.pg', '')] = data;\n }\n\n const enums = await fs.readdir(path.join(dirPath, 'enums'));\n for (const enumName of enums) {\n const data = await fs.readFile(path.join(dirPath, 'enums', enumName), 'utf8');\n description.enums[enumName.replace('.pg', '')] = data;\n }\n\n return description;\n}\n\nasync function loadDescriptionFromDatabase(name: string) {\n const description = await describeDatabase(name);\n return formatDatabaseDescription(description, { coloredOutput: false });\n}\n\nasync function loadDescription(db: DiffTarget): Promise<Description> {\n if (db.type === 'database') {\n return loadDescriptionFromDatabase(db.name);\n } else if (db.type === 'directory') {\n return loadDescriptionFromDisk(db.path);\n } else {\n throw new Error('Invalid database type');\n }\n}\n\nexport async function diffDatabases(database1: string, database2: string, options: DiffOptions) {\n return diff(\n {\n type: 'database',\n name: database1,\n },\n {\n type: 'database',\n name: database2,\n },\n options,\n );\n}\n\nexport async function diffDatabaseAndDirectory(\n database: string,\n directory: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'database',\n name: database,\n },\n {\n type: 'directory',\n path: directory,\n },\n options,\n );\n}\n\nexport async function diffDirectoryAndDatabase(\n directory: string,\n database: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory,\n },\n {\n type: 'database',\n name: database,\n },\n options,\n );\n}\n\nexport async function diffDirectories(\n directory1: string,\n directory2: string,\n options: DiffOptions,\n) {\n return diff(\n {\n type: 'directory',\n path: directory1,\n },\n {\n type: 'directory',\n path: directory2,\n },\n options,\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/postgres-tools",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,19 +20,18 @@
20
20
  "dev": "tsgo --watch --preserveWatchOutput & tscp --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@prairielearn/postgres": "^5.0.0",
23
+ "@prairielearn/postgres": "^5.0.1",
24
24
  "async": "^3.2.6",
25
25
  "chalk": "^5.6.2",
26
26
  "commander": "^14.0.2",
27
27
  "diff": "^8.0.3",
28
+ "es-toolkit": "^1.43.0",
28
29
  "fs-extra": "^11.3.3",
29
- "lodash": "^4.17.21",
30
30
  "postgres-array": "^3.0.4"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@prairielearn/tsconfig": "^0.0.0",
34
34
  "@types/fs-extra": "^11.0.4",
35
- "@types/lodash": "^4.17.23",
36
35
  "@types/node": "^24.10.9",
37
36
  "@typescript/native-preview": "^7.0.0-dev.20260106.1",
38
37
  "typescript": "^5.9.3",
package/src/diff.ts CHANGED
@@ -2,8 +2,8 @@ import path from 'node:path';
2
2
 
3
3
  import chalk from 'chalk';
4
4
  import { structuredPatch } from 'diff';
5
+ import { difference, intersection } from 'es-toolkit';
5
6
  import fs from 'fs-extra';
6
- import _ from 'lodash';
7
7
 
8
8
  import { describeDatabase, formatDatabaseDescription } from './describe.js';
9
9
 
@@ -45,11 +45,11 @@ async function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Pro
45
45
  const description2 = await loadDescription(db2);
46
46
 
47
47
  // Determine if both databases have the same tables
48
- const tablesMissingFrom1 = _.difference(
48
+ const tablesMissingFrom1 = difference(
49
49
  Object.keys(description2.tables),
50
50
  Object.keys(description1.tables),
51
51
  );
52
- const tablesMissingFrom2 = _.difference(
52
+ const tablesMissingFrom2 = difference(
53
53
  Object.keys(description1.tables),
54
54
  Object.keys(description2.tables),
55
55
  );
@@ -71,11 +71,11 @@ async function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Pro
71
71
  }
72
72
 
73
73
  // Determine if both databases have the same enums
74
- const enumsMissingFrom1 = _.difference(
74
+ const enumsMissingFrom1 = difference(
75
75
  Object.keys(description2.enums),
76
76
  Object.keys(description1.enums),
77
77
  );
78
- const enumsMissingFrom2 = _.difference(
78
+ const enumsMissingFrom2 = difference(
79
79
  Object.keys(description1.enums),
80
80
  Object.keys(description2.enums),
81
81
  );
@@ -97,11 +97,11 @@ async function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Pro
97
97
  }
98
98
 
99
99
  // Determine if the columns of any table differ
100
- const intersection = _.intersection(
100
+ const tableIntersection = intersection(
101
101
  Object.keys(description1.tables),
102
102
  Object.keys(description2.tables),
103
103
  );
104
- for (const table of intersection) {
104
+ for (const table of tableIntersection) {
105
105
  const patch = structuredPatch(
106
106
  `tables/${table}`,
107
107
  `tables/${table}`,
@@ -129,7 +129,7 @@ async function diff(db1: DiffTarget, db2: DiffTarget, options: DiffOptions): Pro
129
129
  }
130
130
 
131
131
  // Determine if the values of any enums differ
132
- const enumsIntersection = _.intersection(
132
+ const enumsIntersection = intersection(
133
133
  Object.keys(description1.enums),
134
134
  Object.keys(description2.enums),
135
135
  );