/npm-publish

Gradle plugin for NPM package publishing. Allows for arbitrary publishing as well as seamless integration with Kotlin JS/MPP plugins.

Primary LanguageKotlinApache License 2.0Apache-2.0

Gitpod ready-to-code Slack chat Dokka docs Version gradle-plugin-portal Version maven-central

NPM-PUBLISH GRADLE PLUGIN

Gradle plugin enabling NPM publishing (essentially maven-publish for NPM packages). Integrates seamlessly with Kotlin/JS/MPP plugin if applied, providing auto configurations.

The plugin was last tested with JDK 11, Kotlin 1.5.30 & Gradle 7.2.0

Setup

Here's a bare minimum setup when using together with one of the kotlin plugins. This setup would produce the following tasks:

  • assembleJsNpmPublication
  • packJsNpmPublication
  • publishJsNpmPublicationToNpmjs
  • assembleIrNpmPublication
  • packIrNpmPublication
  • publishIrNpmPublicationToNpmjs
plugins {
  id("dev.petuska.npm.publish") version "<VERSION>"
  kotlin("multiplatform") version "1.5.21" // Optional, also supports "js"
}

kotlin {
  // Legacy mode
  js(Legacy) {
    browser() // or nodejs()
  }
  // OR IR mode
  js(IR) {
    binaries.library()
    browser() // or nodejs()
  }
}

npmPublishing {
  repositories {
    repository("npmjs") {
      registry = uri("https://registry.npmjs.org")
      authToken = "asdhkjsdfjvhnsdrishdl"
    }
  }
}

Configuration

You can add publications and npm repositories or override existing configuration defaults via npmPublishing extension. When kotlin JS/MPP plugin is applied, this plugin will automatically create a publication for each JS target you JS/MPP project has.

DSL

npmPublishing {
  readme = file("README.MD") // (optional) Default readme file
  organization = "my.org" // (Optional) Used as default scope for all publications
  access = PUBLIC // or RESTRICTED. Specifies package visibility, defaults to PUBLIC
  /*
    Enables kotlin jar dependencies (including their transitive dependencies) to be resolved bundled automatically for autogenerated publications.
    Defaults to true and can be overridden for each publication.
    
    Is disabled for IR binaries as they already come with all kotlin dependencies bundled into js output file
   */
  bundleKotlinDependencies = true
  /*
    Adds all bundled dependencies to npm-shrinkwrap.json. Defaults to true and can be overridden for each publication.
    Does not generate a file, even if enabled if there are no bundledDependencies resolved
   */
  shrinkwrapBundledDependencies = true
  /*
    (Optional) Enables run npm publishing with `--dry-run` (does everything except uploading the files). Defaults to false.
   */
  dry = false
  /*
    Overriding default version. Defaults to project.version or rootProject.version, whichever found first
   */
  version = "1.0.0"

  repositories {
    repository("npmjs") {
      registry = uri("https://registry.npmjs.org") // Registry to publish to
      authToken = "asdhkjsdfjvhnsdrishdl" // NPM registry authentication token
      otp = "gfahsdjglknamsdkpjnmasdl" // NPM registry authentication OTP
    }
    repository("bintray") {
      access = RESTRICTED
      registry = ("https://dl.bintray.com/mpetuska/dev.petuska.npm") // Registry to publish to
      authToken = "sngamascdgb" // NPM registry authentication token
      otp = "miopuhimpdfsazxfb" // (Optional) NPM registry authentication OTP
      dry = true // (Optional) Overrides extension default
    }
  }
  publications {
    val jsOne by getting { // Publication build for target declared as `kotlin { js("jsOne") { nodejs() } }`
      scope = "not.my.org" // Overriding package scope that defaulted to organization property from before
      version = "1.0.0-custom" // Overriding version for this publication. Defaults to extension default version
    }
    publication("customPublication") { //Custom publication
      bundleKotlinDependencies = true // Overrides the global default for this publication
      shrinkwrapBundledDependencies = true // Overrides the global default for this publication
      nodeJsDir =
        file("~/nodejs") // NodeJs home directory. Defaults to $NODE_HOME if present or kotlinNodeJsSetup output for default publications
      moduleName = "my-module-name-override" // Defaults to project name
      scope = "other.comp" // Defaults to global organisation
      readme = file("docs/OTHER.MD") // Defaults to global readme
      destinationDir =
        file("$buildDir/vipPackage") // Package collection directory, defaults to File($buildDir/publications/npm/$name")
      main = "my-module-name-override-js.js" // Main output file name, set automatically for default publications
      types = "my-module-name-override-js.d.ts" // TS types output file name, set automatically for default publications

      // Entirely Optional

      dependencies {
        npm("snabbdom", "*")
        npmDev("typescript", "*")
        npmOptional("webpack", "*")
        npmPeer("react", "*")
      }
      files { assemblyDir -> // Specifies what files should be packaged. Preconfigured for default publications, yet can be extended if needed
        from("$assemblyDir/../dir")
        // Rest of your CopySpec     
      }
      pakageJsonFile =
        file("static/package.template.json") // If set will be used as-is ignoring further configurations while getting renamed to package.json regardless of the actual name.
      packageJsonTemplateFile =
        file("templates/package.template.json") // Will be used as a template for default settings, `packageJson` DSL can override its settings.
      packagejson = { // Full package.json override
        main = "./dist/yet-another-override.js"
        types = "./dist/yet-another-override.d.ts"
      }
      packageJson { // Will be patched on top of default generated package.json
        private = false
        bundledDependencies = jsonSet("kotlin") // Suppresses and replaces autogenerated bundled dependencies
        bundledDependencies("kotlin") { // Always includes "kotlin" dependency and filters out the rest by the spec
          -"kotlin-test" // Exclude "kotlin-test" dependency
          +"kotlin-test" // Include "kotlin-test" dependency
          -"kotlin.*".toRegex() // Exclude all dependencies starting with "kotlin"
          +"kotlin.*".toRegex() // Include all dependencies starting with "kotlin"
        }
        keywords = jsonArray(
          "kotlin"
        )
        publishConfig {
          tag = "latest"
        }
        "customField" to jsonObject {
          "customValues" to jsonArray(1, 2, 3)
        }
      }
    }
  }
}

Properties

Most of the DSL configuration options can also be set/overridden via gradle properties (./gradlew task -Pprop.name=propValue), gradle.properties or local.properties. These properties can also be set via environment variables by replacing ., & - with _ and capitalising all names. Properties are resolved in the following priority:

  1. Gradle Properties
  2. Environment Variables
  3. DSL
Extension
  • npm.publish.readme (NPM_PUBLISH_README)
  • npm.publish.organization (NPM_PUBLISH_ORGANIZATION)
  • npm.publish.access (NPM_PUBLISH_ACCESS)
  • npm.publish.bundleKotlinDependencies (NPM_PUBLISH_BUNDLEKOTLINDEPENDENCIES)
  • npm.publish.shrinkwrapBundledDependencies (NPM_PUBLISH_SHRINKWRAPBUNDLEDDEPENDENCIES)
  • npm.publish.dry (NPM_PUBLISH_DRY)
  • npm.publish.version (NPM_PUBLISH_VERSION)
Publication
  • npm.publish.publication.<name>.bundleKotlinDependencies (NPM_PUBLISH_PUBLICATION_<NAME>_BUNDLEKOTLINDEPENDENCIES)
  • npm.publish.publication.<name>.shrinkwrapBundledDependencies (NPM_PUBLISH_PUBLICATION_<NAME>_SHRINKWRAPBUNDLEDDEPENDENCIES)
  • npm.publish.publication.<name>.scope (NPM_PUBLISH_PUBLICATION_<NAME>_SCOPE)
  • npm.publish.publication.<name>.moduleName (NPM_PUBLISH_PUBLICATION_<NAME>_MODULENAME)
  • npm.publish.publication.<name>.version (NPM_PUBLISH_PUBLICATION_<NAME>_VERSION)
  • npm.publish.publication.<name>.main (NPM_PUBLISH_PUBLICATION_<NAME>_MAIN)
  • npm.publish.publication.<name>.types (NPM_PUBLISH_PUBLICATION_<NAME>_TYPES)
  • npm.publish.publication.<name>.readme (NPM_PUBLISH_PUBLICATION_<NAME>_README)
  • npm.publish.publication.<name>.nodeJsDir (NPM_PUBLISH_PUBLICATION_<NAME>_NODEJSDIR)
  • npm.publish.publication.<name>.destinationDir (NPM_PUBLISH_PUBLICATION_<NAME>_DESTINATIONDIR)
Repository
  • npm.publish.repository.<name>.dry (NPM_PUBLISH_REPOSITORY_<NAME>_DRY)
  • npm.publish.repository.<name>.access (NPM_PUBLISH_REPOSITORY_<NAME>_ACCESS)
  • npm.publish.repository.<name>.registry (NPM_PUBLISH_REPOSITORY_<NAME>_REGISTRY)
  • npm.publish.repository.<name>.otp (NPM_PUBLISH_REPOSITORY_<NAME>_OTP)
  • npm.publish.repository.<name>.authToken (NPM_PUBLISH_REPOSITORY_<NAME>_AUTHTOKEN)

Tasks

The plugin generates the following gradle tasks for various configuration elements:

  • NpmPackageAssembleTask: generated for each publication and named as assemble<UpperCammelCasePublicationName>NpmPublication
  • NpmPackTask: generated for each publication and named as pack<UpperCammelCasePublicationName>NpmPublication
  • NpmPublishTask: generated for each publication + repository combination and named as publish<UpperCammelCasePublicationName>NpmPublicationTo<UpperCammelCaseRepositoryName> All created tasks are added as dependencies to grouping tasks to allow for group-invocation:
  • NpmPackageAssembleTask: assemble task in build group
  • NpmPackTask: pack task in build group
  • NpmPublishTask: publish task in publishing group

Dependency Resolution

Npm dependencies detected/declared for each publication are resolved into relevant package.json dependency block by this priority order by their name (descending priority):

  1. Optional
  2. Peer
  3. Dev
  4. Normal This ensures that any given dependency does not appear in multiple dependency scopes.

Known Issues

  • #6435: (Only applies to legacy backend) npm and yarn tries to download bundled dependencies. Can be overcome for npm (sadly not yarn) with shrinkwrapBundledDependencies option. Note that it works fine for both package managers when installing from a tarball. Bug #2143 on the new npm repo, so please vote for that to get it fixed. Bug #8436 on the yarn repo, so please vote for that to get it fixed.