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

Modifying pushArgs via plugin #988

Open
limenet opened this issue Mar 13, 2023 · 13 comments
Open

Modifying pushArgs via plugin #988

limenet opened this issue Mar 13, 2023 · 13 comments

Comments

@limenet
Copy link

limenet commented Mar 13, 2023

Thanks for this fantastic tool!

Depending on the release, we'd like to set push arguments to set CI/CD variables to toggle certain deployment steps. Whether a certain deployment step is active, is up to whoever creates that release.

To achieve this, I wrote a simple plugin which looks as follows:

import { Plugin } from 'release-it';
import inquirer from 'inquirer';

class ReleaseOptions extends Plugin {
  async init() {
    const { variables } = this.options;
    const { booleans } = variables;

    const prompts = Object.entries(booleans).map(([name, defaultValue]) => ({
      type: 'confirm',
      name: `CI_CUSTOM_${name}`,
      default: defaultValue,
    }));

    await inquirer
      .prompt(prompts)
      .then((answers) => {
        const pushArgs = [
          ...this.config.getContext('git.pushArgs'),
          ...Object.entries(answers).map(
            ([name, value]) => `-o ci.variable="${name}=${value}"`
          ),
        ];

        this.debug(pushArgs);
        this.config.setContext({ git: { pushArgs } });
      })
      .catch((error) => {
        this.log.error(error);
      });

    return true;
  }
}

export default ReleaseOptions;

Configuration in package.json is:

{
  "release-it": {
    "plugins": {
      "./release-options.js": {
        "variables": {
          "booleans": {
            "FOO": true,
            "BAR": false
          }
        }
      }
    }
  }
}

git.pushArgs is modified using this.config.setContext which works. However, when the actual git push is executed in lib/plugin/git/Git.js in push(), the options are passed as a default argument using args = this.options.pushArgs. args contains the push arguments as defined when initializing the plugin and defaults to [ '--follow-tags' ] and does not contain the changes made using config.setContext() which would look like this:

[
  '--follow-tags',
  '-o ci.variable="CI_CUSTOM_FOO=true"',
  '-o ci.variable="CI_CUSTOM_BAR=false"'
]

To fix this, I see two options:

  1. I made a stupid mistake and could easily fix this by e.g. using a different plugin hook / setting the arguments in a different way
  2. push() resolves args using config.getContext

Can you please help me resolve this?

@webpro
Copy link
Collaborator

webpro commented Mar 15, 2023

There's two options I can think of:

  1. Use a dynamic release-it.js to prepare the configuration and export the values so they enter release-it as desired.

  2. Perhaps another proper approach could be to extend the actual Git plugin class (you can replace the original plugin from your own). However, I'm afraid that's currently not possible due to the migration to ESM + the exports field in package.json. Can you confirm it's not possible to import that class from lib/plugin/git/Git.js? I'm happy to add the plugins to that exports field for custom stuff like this.

@limenet
Copy link
Author

limenet commented Mar 20, 2023

Thanks for the help, @webpro!

Unfortunately I can't get option 2 to work (after adding the export to package.json) as this seems to e.g. break the call to Git.stage() (despite calling await super.init() in my init()).

Option 1, however, solved the problem nicely! Unfortunately there's a GitLab bug now that prevents that solution from working, but that's another story... :)

One thing I did encounter, however, is .release-it.js can't be a module, i.e. use import and have "type": "module" in package.json. Is that something you're aware of, would you like me to open a new issue for that or did I miss something?

@webpro
Copy link
Collaborator

webpro commented Mar 22, 2023

Thanks for the help, @webpro!

You're most welcome, I like to see how people are using release-it in unexpected ways :)

Unfortunately I can't get option 2 to work (after adding the export to package.json) as this seems to e.g. break the call to Git.stage() (despite calling await super.init() in my init()).

That's unfortunate and weird, the internal classes do the some thing afaic.

Option 1, however, solved the problem nicely! Unfortunately there's a GitLab bug now that prevents that solution from working, but that's another story... :)

Cool, happy that part of the issue is sorted then.

One thing I did encounter, however, is .release-it.js can't be a module, i.e. use import and have "type": "module" in package.json. Is that something you're aware of, would you like me to open a new issue for that or did I miss something?

No I was not really aware of this. Are you saying that there is no way to use export default or module.exports or the .mjs extension and have a dynamic config working properly?

@limenet
Copy link
Author

limenet commented Apr 3, 2023

Sorry for the late reply.

No I was not really aware of this. Are you saying that there is no way to use export default or module.exports or the .mjs extension and have a dynamic config working properly?

I tried this with the following environment:

$ node --version
v18.15.0
$ npm --version
9.5.0
$ npm list
...
[email protected]
$ jq .type package.json
"module"

And the following contents:

module.exports = {
  npm: {
    publish: false,
  },
  git: {
    requireCleanWorkingDir: false,
  },
};

or also

export default {
  npm: {
    publish: false,
  },
  git: {
    requireCleanWorkingDir: false,
  },
};
  1. Using .release-it.mjs, the file isn't read at all, no matter what type is in package.json
  2. Using .release-it.js (and "type": "module"), I get the following message
Error [ERR_REQUIRE_ESM]: require() of ES Module /some/redacted/folder/.release-it.js from /some/redacted/folder/node_modules/cosmiconfig/dist/loaders.js not supported.
.release-it.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename .release-it.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /some/redacted/folder/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).

  at module.exports (/some/redacted/folder/node_modules/import-fresh/index.js:32:59)
  at loadJs (/some/redacted/folder/node_modules/cosmiconfig/dist/loaders.js:16:18)
  at ExplorerSync.loadFileContentSync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:90:14)
  at ExplorerSync.createCosmiconfigResultSync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:98:30)
  at ExplorerSync.loadSearchPlaceSync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:75:17)
  at ExplorerSync.searchDirectorySync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:60:32)
  at run (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:41:27)
  at cacheWrapperSync (/some/redacted/folder/node_modules/cosmiconfig/dist/cacheWrapper.js:28:18)
  at ExplorerSync.searchFromDirectorySync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:52:49)
  at ExplorerSync.searchSync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:34:17)
  at getLocalConfig (file:///some/redacted/folder/node_modules/release-it/lib/config.js:32:56)
  at new Config (file:///some/redacted/folder/node_modules/release-it/lib/config.js:43:24)
  at runTasks (file:///some/redacted/folder/node_modules/release-it/lib/index.js:16:44)
  at default (file:///some/redacted/folder/node_modules/release-it/lib/cli.js:39:12)
  at file:///some/redacted/folder/node_modules/release-it/bin/release-it.js:39:1 {
code: 'ERR_REQUIRE_ESM',
filepath: '/some/redacted/folder/.release-it.js'
}
  1. If type is missing, the error is (as expected) about the export token
/some/redacted/folder/.release-it.js:1
export default {
^^^^^^

SyntaxError: Unexpected token 'export'
  at internalCompileFunction (node:internal/vm:73:18)
  at wrapSafe (node:internal/modules/cjs/loader:1176:20)
  at Module._compile (node:internal/modules/cjs/loader:1218:27)
  at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
  at Module.load (node:internal/modules/cjs/loader:1117:32)
  at Module._load (node:internal/modules/cjs/loader:958:12)
  at Module.require (node:internal/modules/cjs/loader:1141:19)
  at module.exports (/some/redacted/folder/node_modules/import-fresh/index.js:32:59)
  at loadJs (/some/redacted/folder/node_modules/cosmiconfig/dist/loaders.js:16:18)
  at ExplorerSync.loadFileContentSync (/some/redacted/folder/node_modules/cosmiconfig/dist/ExplorerSync.js:90:14) {
filepath: '/some/redacted/folder/.release-it.js'
}

Not using "type": "module", calling the file .release-it.js, and not using import (but rather require) was the only way I could get the dynamic config to work.

Does this help?

@webpro
Copy link
Collaborator

webpro commented Apr 3, 2023

Thanks for all the details. Just a quick idea for now, did you try this already? Using "type": "module" with -c or --config like so:

release-it --config .release-it.mjs

Edit: afraid this probably does nothing else than using .js with "type": "module"

@JohnCampionJr
Copy link
Contributor

JohnCampionJr commented Oct 30, 2023

I ran into this same situation; it would be nice if plugins could set options on the main options object

For my use case, I want to override the git plugin options from my plugin.
so set:

{
  git: {
    commitMessage: 'my updated message'
  }
}

@webpro
Copy link
Collaborator

webpro commented Oct 31, 2023

Inside a plugin you have access to this.config, so I think you should be able to do this:

this.config.setContext({ git:  {commitMessage: 'my updated message' } })

This could only work in the constructor. The external plugins are instantiated before the internal ones.

@JohnCampionJr
Copy link
Contributor

I tried that, but the git plugin pulls it from the options.

@webpro
Copy link
Collaborator

webpro commented Oct 31, 2023

Oops, I didn't actually try it. Also tbh it didn't really sit right with me in the first place (change config of one plugin from another plugin).

@JohnCampionJr
Copy link
Contributor

Yeah, my plan was to only change the git plugin's defaults if there was no change at all from the default in the config. This would ensure no breaking.

What do you think of changing the commitMessage and tagAnnotation to retrieve from the config, like tagName?

tagName:

tag({ name, annotation = this.options.tagAnnotation, args = this.options.tagArgs } = {}) {
    const message = format(annotation, this.config.getContext());
    const tagName = name || this.config.getContext('tagName');

commitMessage (currently)

commit({ message = this.options.commitMessage, args = this.options.commitArgs } = {}) {
    const msg = format(message, this.config.getContext());
    const commitMessageArgs = msg ? ['--message', msg] : [];

@JohnCampionJr
Copy link
Contributor

Any thoughts?

@FRSgit
Copy link

FRSgit commented Apr 19, 2024

@webpro I've stumbled into this issue as well when building a bit more eloquent monorepo configuration. And I'd say exporting "native" release-it plugins (like Git, npm, etc) would be already a huge life-saver.

I saw that you've already proposed this here and I've confirmed - import { Git } from "release-it/lib/plugin/git/Git.js"; raises ERR_PACKAGE_PATH_NOT_EXPORTED error.

To overcome this issue in most of my libraries I'm adding this line - it's far from perfect, but allows accessing any js file in the repository 😄 Ofc, just writing explicit exports would work too

@webpro
Copy link
Collaborator

webpro commented May 8, 2024

Feel free to open pull request(s), I'm happy to look into them.

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

No branches or pull requests

4 participants