Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(esm): __dirname and pg.Client #1206

Closed
wants to merge 2 commits into from

Conversation

aprendendofelipe
Copy link
Contributor

@aprendendofelipe aprendendofelipe commented Jun 19, 2024

Resolve #1167 : __dirname is not defined
Resolve #1190 : pg.Client is not a constructor

It is possible to test with node-pg-migrate-exp@7.4.0-experimental-1189-13:

npm i node-pg-migrate-exp

Copy link

github-actions bot commented Jun 19, 2024

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 95.59% (🎯 90%)
⬆️ +0.15%
6751 / 7062
🟢 Statements 95.59% (🎯 90%)
⬆️ +0.15%
6751 / 7062
🟢 Functions 95.47% (🎯 90%)
🟰 ±0%
253 / 265
🟢 Branches 89.96% (🎯 85%)
🟰 ±0%
807 / 897
File Coverage
File Stmts % Branch % Funcs % Lines Uncovered Lines
Unchanged Files
src/db.ts 88.02% 88.46% 87.5% 88.02% 67-69, 99, 102-114, 154-156
src/index.ts 100% 100% 100% 100%
src/migration.ts 75.32% 78.57% 61.53% 75.32% 63, 66-68, 70-82, 111-117, 122-158, 215-216, 241-244, 252-253, 270-273, 302-303
src/migrationBuilder.ts 97.56% 92.85% 88.88% 97.56% 354-358, 497-502, 526-527
src/runner.ts 74.3% 58.92% 80% 74.3% 42, 70-71, 80-81, 84-92, 130-131, 173-176, 185-189, 202, 206-219, 238, 240-246, 249, 262, 274-287, 290-293, 303-304, 313-315, 324, 326-335, 347-354
src/sqlMigration.ts 90.19% 100% 80% 90.19% 45-49
src/types.ts 100% 100% 100% 100%
src/operations/sql.ts 100% 100% 100% 100%
src/operations/casts/createCast.ts 100% 100% 100% 100%
src/operations/casts/dropCast.ts 100% 100% 100% 100%
src/operations/casts/index.ts 100% 100% 100% 100%
src/operations/domains/alterDomain.ts 100% 100% 100% 100%
src/operations/domains/createDomain.ts 100% 100% 100% 100%
src/operations/domains/dropDomain.ts 100% 100% 100% 100%
src/operations/domains/index.ts 100% 100% 100% 100%
src/operations/domains/renameDomain.ts 100% 100% 100% 100%
src/operations/domains/shared.ts 100% 100% 100% 100%
src/operations/extensions/createExtension.ts 100% 100% 100% 100%
src/operations/extensions/dropExtension.ts 100% 100% 100% 100%
src/operations/extensions/index.ts 100% 100% 100% 100%
src/operations/extensions/shared.ts 100% 100% 100% 100%
src/operations/functions/createFunction.ts 100% 100% 100% 100%
src/operations/functions/dropFunction.ts 100% 100% 100% 100%
src/operations/functions/index.ts 100% 100% 100% 100%
src/operations/functions/renameFunction.ts 100% 100% 100% 100%
src/operations/functions/shared.ts 100% 100% 100% 100%
src/operations/grants/grantOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/grantOnTables.ts 100% 100% 100% 100%
src/operations/grants/grantRoles.ts 100% 100% 100% 100%
src/operations/grants/index.ts 100% 100% 100% 100%
src/operations/grants/revokeOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/revokeOnTables.ts 100% 100% 100% 100%
src/operations/grants/revokeRoles.ts 100% 100% 100% 100%
src/operations/grants/shared.ts 98.63% 85.71% 100% 98.63% 71
src/operations/indexes/createIndex.ts 98.03% 95.23% 100% 98.03% 74-75
src/operations/indexes/dropIndex.ts 100% 100% 100% 100%
src/operations/indexes/index.ts 100% 100% 100% 100%
src/operations/indexes/shared.ts 91.17% 86.95% 100% 91.17% 22, 32-35, 47
src/operations/materializedViews/alterMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/createMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/dropMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/index.ts 100% 100% 100% 100%
src/operations/materializedViews/refreshMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedViewColumn.ts 100% 100% 100% 100%
src/operations/materializedViews/shared.ts 100% 87.5% 100% 100%
src/operations/operators/addToOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/createOperator.ts 100% 100% 100% 100%
src/operations/operators/createOperatorClass.ts 100% 83.33% 100% 100%
src/operations/operators/createOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/dropOperator.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/index.ts 100% 100% 100% 100%
src/operations/operators/removeFromOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/shared.ts 90% 75% 100% 90% 24-25, 37-38
src/operations/policies/alterPolicy.ts 100% 100% 100% 100%
src/operations/policies/createPolicy.ts 100% 100% 100% 100%
src/operations/policies/dropPolicy.ts 100% 100% 100% 100%
src/operations/policies/index.ts 100% 100% 100% 100%
src/operations/policies/renamePolicy.ts 100% 100% 100% 100%
src/operations/policies/shared.ts 100% 100% 100% 100%
src/operations/roles/alterRole.ts 100% 100% 100% 100%
src/operations/roles/createRole.ts 100% 75% 100% 100%
src/operations/roles/dropRole.ts 100% 100% 100% 100%
src/operations/roles/index.ts 100% 100% 100% 100%
src/operations/roles/renameRole.ts 100% 100% 100% 100%
src/operations/roles/shared.ts 98.97% 76.92% 100% 98.97% 78
src/operations/schemas/createSchema.ts 100% 100% 100% 100%
src/operations/schemas/dropSchema.ts 100% 100% 100% 100%
src/operations/schemas/index.ts 100% 100% 100% 100%
src/operations/schemas/renameSchema.ts 100% 100% 100% 100%
src/operations/sequences/alterSequence.ts 96.87% 75% 100% 96.87% 23
src/operations/sequences/createSequence.ts 100% 100% 100% 100%
src/operations/sequences/dropSequence.ts 100% 100% 100% 100%
src/operations/sequences/index.ts 100% 100% 100% 100%
src/operations/sequences/renameSequence.ts 100% 100% 100% 100%
src/operations/sequences/shared.ts 87.67% 68.75% 100% 87.67% 41, 43-44, 49-50, 63-64, 69-70
src/operations/tables/addColumns.ts 100% 80% 100% 100%
src/operations/tables/addConstraint.ts 100% 100% 100% 100%
src/operations/tables/alterColumn.ts 92.5% 76.47% 100% 92.5% 75, 82-89
src/operations/tables/alterTable.ts 100% 100% 100% 100%
src/operations/tables/createTable.ts 90.47% 66.66% 100% 90.47% 44-48, 65, 75-76
src/operations/tables/dropColumns.ts 100% 100% 100% 100%
src/operations/tables/dropConstraint.ts 100% 100% 100% 100%
src/operations/tables/dropTable.ts 100% 100% 100% 100%
src/operations/tables/index.ts 100% 100% 100% 100%
src/operations/tables/renameColumn.ts 100% 100% 100% 100%
src/operations/tables/renameConstraint.ts 100% 100% 100% 100%
src/operations/tables/renameTable.ts 100% 100% 100% 100%
src/operations/tables/shared.ts 88.47% 78.46% 80% 88.47% 137-138, 141-142, 195-199, 224, 228-229, 248-249, 274-275, 278-285, 288-289, 426-451
src/operations/triggers/createTrigger.ts 90.83% 68.18% 100% 90.83% 53-54, 66-67, 70-71, 74-77, 113
src/operations/triggers/dropTrigger.ts 100% 100% 100% 100%
src/operations/triggers/index.ts 100% 100% 100% 100%
src/operations/triggers/renameTrigger.ts 100% 100% 100% 100%
src/operations/triggers/shared.ts 100% 100% 100% 100%
src/operations/types/addTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/addTypeValue.ts 100% 100% 100% 100%
src/operations/types/createType.ts 100% 100% 100% 100%
src/operations/types/dropType.ts 100% 100% 100% 100%
src/operations/types/dropTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/index.ts 100% 100% 100% 100%
src/operations/types/renameType.ts 100% 100% 100% 100%
src/operations/types/renameTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/renameTypeValue.ts 100% 100% 100% 100%
src/operations/types/setTypeAttribute.ts 100% 100% 100% 100%
src/operations/views/alterView.ts 100% 100% 100% 100%
src/operations/views/alterViewColumn.ts 100% 100% 100% 100%
src/operations/views/createView.ts 100% 100% 100% 100%
src/operations/views/dropView.ts 100% 100% 100% 100%
src/operations/views/index.ts 100% 100% 100% 100%
src/operations/views/renameView.ts 100% 100% 100% 100%
src/operations/views/shared.ts 100% 66.66% 100% 100%
src/utils/PgLiteral.ts 96.49% 100% 75% 96.49% 37-38
src/utils/StringIdGenerator.ts 100% 100% 100% 100%
src/utils/createSchemalize.ts 100% 100% 100% 100%
src/utils/createTransformer.ts 100% 100% 100% 100%
src/utils/decamelize.ts 100% 100% 100% 100%
src/utils/escapeValue.ts 100% 100% 100% 100%
src/utils/formatLines.ts 100% 100% 100% 100%
src/utils/formatParams.ts 100% 100% 100% 100%
src/utils/getMigrationTableSchema.ts 100% 100% 100% 100%
src/utils/getSchemas.ts 100% 100% 100% 100%
src/utils/identity.ts 100% 100% 100% 100%
src/utils/index.ts 100% 100% 100% 100%
src/utils/intersection.ts 100% 100% 100% 100%
src/utils/makeComment.ts 100% 100% 100% 100%
src/utils/quote.ts 100% 100% 100% 100%
src/utils/toArray.ts 100% 100% 100% 100%
src/utils/types.ts 100% 100% 100% 100%
Generated in workflow #887

@aprendendofelipe aprendendofelipe changed the title Fix ESM: __dirname, pg.Client and types fix(esm): __dirname, pg.Client and types Jun 19, 2024
package.json Outdated Show resolved Hide resolved
// @ts-expect-error: ignore until esm only
import.meta.url || __dirname
);
const crossRequire = createRequire(resolve('noop.js'));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): what is this noop.js? I don't know this file 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no op = no operation

It is a file that does not exist and will be ignored. Only the path matters.

The same hack is used internally in module.createRequire:

https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/modules/cjs/loader.js#L1583-L1585

 const proxyPath = trailingSlash ? 
   path.join(filename, 'noop.js') : 
   filename; 

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting 🤔
I first thought you wanted to reference somehow https://github.com/salsita/node-pg-migrate/blob/main/test/migrations/001_noop.js, but it did not make any sense to me.

  1. I need to check how this behaves. Why does it not error when a file is not found/invalid. And what is the fallback behavior instead.
  2. If we are really using this, we should extract it into a const NON_EXISTING_FILE = 'noop.js' or similar, and potentially even add JSDoc to that constant.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a small test to see how noop.js behaves 👀

image
 node index.js
metaUrl: file:///Users/cquadflieg/WPSM/wui/test-project/index.js
noopUrl: /Users/cquadflieg/WPSM/wui/test-project/noop.js
 
 node src/index.js
metaUrl: file:///Users/cquadflieg/WPSM/wui/test-project/src/index.js
noopUrl: /Users/cquadflieg/WPSM/wui/test-project/noop.js
 
 cd src
 node index.js
metaUrl: file:///Users/cquadflieg/WPSM/wui/test-project/src/index.js
noopUrl: /Users/cquadflieg/WPSM/wui/test-project/src/noop.js
 
 node ../index.js
metaUrl: file:///Users/cquadflieg/WPSM/wui/test-project/index.js
noopUrl: /Users/cquadflieg/WPSM/wui/test-project/src/noop.js

so..., import.meta.url is always pointing to the file where the statement is written in.
But noop is always the path where the execution was made from. (is it similar to process.cwd()?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name does not need to be noop.js. We can use any name as it will be ignored by Module.createRequire. In the end, only the directory matters.

process.cwd() will not work, as dirname(process.cwd()) will resolve to a level higher than desired.

It could be `${process.cwd()}/` because internally Module.createRequire will join with noop.js:

 const trailingSlash =
 StringPrototypeEndsWith(filename, '/') ||
 (isWindows && StringPrototypeEndsWith(filename, '\\'));

 const proxyPath = trailingSlash ?
 path.join(filename, 'noop.js') :
 filename;

And it should work fine on Windows too.

But I prefer something more flexible, like resolve(process.cwd(), 'anything.js'), in which case, due to the internal workings of resolve, we can omit process.cwd(). That's why I'm proposing something like resolve('NON_EXISTING_FILE_OR_FOLDER') or resolve('_'), but it can be whatever you prefer, as the result will be the same:

  • createRequire(resolve('NON_EXISTING_FILE_OR_FOLDER'))
  • createRequire(`${process.cwd()}/`)
  • createRequire(resolve('_'))
  • ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like even that does not work

image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried several other things locally

Could you try again on your own to bring the test\runner.spec.ts green, without touching it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #1208

.github/workflows/ci.yml Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
tsup.config.ts Outdated Show resolved Hide resolved
@@ -16,7 +16,7 @@ type Options =
export const run = async (options: Options): Promise<boolean> => {
const opts: Omit<RunnerOption, 'direction'> & Options = {
migrationsTable: 'migrations',
dir: resolve(__dirname, 'migrations'),
dir: resolve('test', 'ts', 'migrations'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (if-minor): this is just a test-file and does not need to be changes until package.json is set to "type": "module"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but we are already trying to remove everything that depends on __dirname. I don't see why leaving it for later, but I can remove the change if you think it's better. 🤝

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more like putting focus on one thing (fixing the source code for ESM).
But I might see now what you did here.

So... I follow the strategy to squash-merge every PR and using its title as commit message, so maybe extract this change into its own PR with title test(runner): <message>

const actions: MigrationBuilderActions =
extname(filePath) === '.sql'
? await migrateSqlFile(filePath)
: require(relative(__dirname, filePath));
: createRequire(resolve('noop.js'))(filePath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): is this a leftover? or did you not rebase on the latest main-branch? 👀
If this is still in the main-branch this indeed needs to be fixed.
Could you extract this out into its own PR please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is up to date.

I can extract this in a separate PR, but removing the last commit should be enough. Or not?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we now use this (or #1207) as the PR that will fix the esm source code issue

I really appreciate your help and I'm learning new things, but as I wrote in my other comment regarding the squash-merge PRs, try to make dedicated PRs

'templates',
`migration-template.${await resolveSuffix(directory, options)}`
)
'node_modules/node-pg-migrate/templates',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (if-minor): does this work in pnpm as well?
why is this change better than the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not depend on tsup handling import.meta.url and __dirname

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I will look into this later. I'm just only afraid of this code-piece in relation to several package-managers (yarn, pnpm, bun, and so on). e.g. yarn could have something so that there isn't even a node_modules folder, is it? (I'm not working personally with yarn anymore)

Comment on lines -14 to -15
// This needs to be imported as `*`, otherwise it will fail in esm
import * as pg from 'pg';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (if-minor): there is an explicit comment that esm would fail 🤔
you sure that this is correct? why would it suddenly work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run node bin/node-pg-migrate.mjs up (mjs) and you will get TypeError: pg.Client is not a constructor instead of the connection error you receive with node bin/node-pg-migrate.js up (js).

With import pg from 'pg' you will only see the connection error in both cases, which is as expected

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into this again later.
Maybe this should be extracted into its own PR.

@aprendendofelipe aprendendofelipe changed the title fix(esm): __dirname, pg.Client and types fix(esm): __dirname and pg.Client Jun 20, 2024
@Shinigami92
Copy link
Collaborator

Two more things:

  1. sorry that I currently have only limited time, but e.g. this evening I'm outside and so I cant quickly respond, so maybe I find some time tomorrow or at upcoming sunday 🤔, but no promises
  2. each PR with its title will also get its own line in Release Notes, so this is another reason why it's important to make smaller dedicated PRs

@aprendendofelipe
Copy link
Contributor Author

each PR with its title will also get its own line in Release Notes, so this is another reason why it's important to make smaller dedicated PRs

I separated it into #1208 and #1209 🤝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TypeError: pg.Client is not a constructor Incorrect reference to __dirname when compiling to esmodules
2 participants