Developing applications and libraries with pnpm

Classic project driven by package.json

The LifecyclePnpmNodeWithPackageJson is the standard development model where there is only a single JavaScript/TypeScript project and everything is configured in package.json.

Minimal configuration

build.gradle
import org.ysb33r.gradle.jse.pnpm.toolchains.JsePnpmToolchain
import org.ysb33r.gradle.jse.pnpm.dev.models.LifecyclePnpmNodeWithPackageJson

plugins {
 id 'org.ysb33r.jsecosystem.pnpm.dev' version '5.3.1'
}

jsEcosystem {
  toolchains {
    pnpm(JsePnpmToolchain) { (1)
    }
  }
}

jsProjects {
  lifecycleManagers {
    pnpm(LifecyclePnpmNodeWithPackageJson) { (2)
      toolchain = 'pnpm' (3)
    }
  }

  sourceSets {
    main { (4)
        lifecycle('pnpnm', LifecyclePnpmNodeWithPackageJson) (5)
    }
  }
}
1 A pnpm toolchain is required. See Toolchains for pnpm for more details on toolchains.
2 Create the lifecycle manager toolchain.
3 Create a source set. See Basics of configuring projects on how to configure source sets.
4 Tell Gradle that the specific source set will use pnpm and package.json.

Tasks

For the main source set, the following tasks will be added to Gradle lifecycles:

  • assemble: jseAssemble.

  • check: jseTest.

For a list of available tasks classes see Task classes

Audits

You can enable tasks for pnpm audit within the lifecycle itself.

build.gradle
import org.ysb33r.gradle.jse.pnpm.dev.models.LifecyclePnpmNodeWithPackageJson
import org.ysb33r.gradle.jse.pnpm.configuration.PnpmAuditLevel

jsProjects {
  lifecycleManagers {
    pnpm(LifecyclePnpmNodeWithPackageJson) {
      withPnpmAudit {
          auditLevel = PnpmAuditLevel.CRITICAL (1)
          auditLevel = 'critical' (2)
          prodOnly = true (3)
          includeOptional = true (4)
          ignoreRegistryErrors = true (5)
          ignoreUnfixable = true (6)
      }
    }
  }
}
1 Sets the audit level using an enumeration. See PnpmAuditLevel.
2 Sets the audit level using a string. The value must match that of a pnpm audit level, but the value is case-insensitive. If the audit level is not set via either method, the default is to read it from the audit.level value in pnpm-workspace.yaml. If that does not exist, it is level to pnpm to use its default.
3 Whether to only audit production dependencies. The default is to do both production and development.
4 Whether to include optional dependencies. The default is to not include optional dependencies.
5 Whether to ignore registry errors. The default is to fail on registry errors.
6 Whether to ignore unfixable vulnerabilities. The default is to not ignore unfixable vulnerabilities.

This adds a PnpmAuditTask and a PnpmAuditFixTask. THe former is also added to the securityCheck lifecycle task if the appropriate Lifecycle plugin is applied.

Publishing packages

There is currently not a registered task that will publish a package to a PNPM registry. This will probably be added in a future release. However, it is easy to implement your own by extending AbstractPnpmLifecycleTask

Running scripts in package.json

The lifecycle manager already implements tasks for handling certain scripts within the package.json file. However, for any other scripts that are not covered by a defined task, there is support for using a Gradle rule to run any script defined in the scripts block.

The format of the rule is jseRun<ScriptName><sourceSetName> or if the source set is called main it is simply jseRun<ScriptName>. If you have a script called lint you can use a task jseRunLint.

Workspace project driven by pnpm-workspace.yaml

The LifecyclePnpmNodeWithWorkspaces is the standard development model when working with a monorepo-style PNPM workspace. In this model Gradle wraps the root workspace and there are multiple JavaScript/TypeScript projects in the workspace. The project is configured with pnpm-workspace.yaml and subprojects are configured with package.json

In most cases Gradle will just delegate to pnpm which will then perform all of the building. Gradle reads th tasks field from pnpm-workspace.yaml to determine the toplevel Gradle tasks. As a configurable option, Gradle can also scan every package defined in the packages field and create tasks for run scripts in the package.json files.

Minimal configuration

build.gradle
import org.ysb33r.gradle.jse.pnpm.toolchains.JsePnpmToolchain
import org.ysb33r.gradle.jse.pnpm.dev.models.LifecyclePnpmNodeWithWorkspaces

plugins {
 id 'org.ysb33r.jsecosystem.pnpm.dev' version '5.3.1'
}

jsEcosystem {
  toolchains {
    pnpm(JsePnpmToolchain) { (1)
    }
  }
}

jsProjects {
  lifecycleManagers {
    pnpm(LifecyclePnpmNodeWithWorkspaces) { (2)
      toolchain = 'pnpm' (3)

      noWorkspaceTreeTaskConversion() (4)
      convertWorkspaceTreeIntoTasks()  (5)
      convertMatchingPackagesIntoTasks 'mypackage', '@myscope/mypackage'  (6)
      convertMatchingPackagesIntoTasks ~/.*mypackage.*/  (7)

      environment( foo: 'bar') (8)
  }

  sourceSets {
    main { (9)
        lifecycle('pnpnm', LifecyclePnpmNodeWithWorkspaces) (10)
    }
  }
}
1 A pnpm toolchain is required. See Toolchains for pnpm for more details on toolchains.
2 Create the lifecycle manager toolchain.
3 Link the lifecycle to a toolchain
4 Do not scan the workspace’s packages for scripts. This is the default behaviour.
5 Scan all packages in the workspace for scripts and convert each package-script combination into a task.
6 Scan only the packages in the workspace that matche the given names and convert those package-script combinations into tasks.
7 Scan only the packages in the workspace that matches the given patterns and convert those package-script combinations into tasks.
8 Add environment variables to tasks (except wrapper tasks) that are created by this lifecycle. Can either be a map or lazy-evaluated values, or a provider to a map.
9 Create a source set. See Basics of configuring projects on how to configure source sets.
10 Tell Gradle that the specific source set will use pnpm and pnpm-workspace.yaml.

Tasks

For the main source set, the following tasks will be added to Gradle lifecycles:

  • assemble: jseAssemble.

  • check: jseTest.

For a list of available tasks classes see Task classes

If convertWorkspaceTreeIntoTasks or convertMatchingPackagesIntoTask was configured a range of tasks starting with jseWsRun will be added. For the latter packages that have scopes will have it added into the task name. For example, running the foo script in a package @myscope/mypackage, the task name will be jseWsRun@myscopeMypackageFoo. Any /, :, or - will be removed and the following character will be capitalised. Because these tasks are created late, you cannot use tasks.named to configure them. You should Grolifant’s taskTools().whenNamed instead.

build.gradle
import org.ysb33r.gradle.jse.pnpm.tasks.PnpmWorkspacePackageRunTask

grolifantOps.taskTools.whenNamed("jseWsRun@myscopeMypackageFoo", PnpmWorkspacePackageRunTask) {
  // configure the task here in the same way you would do with `tasks.named`.
}

Audits

You can enable tasks for pnpm audit within the lifecycle itself.

build.gradle
import org.ysb33r.gradle.jse.pnpm.dev.models.LifecyclePnpmNodeWithWorkspaces
import org.ysb33r.gradle.jse.pnpm.configuration.PnpmAuditLevel

jsProjects {
  lifecycleManagers {
    pnpm(LifecyclePnpmNodeWithWorkspaces) {
      withPnpmAudit {
          auditLevel = PnpmAuditLevel.CRITICAL (1)
          auditLevel = 'critical' (2)
          prodOnly = true (3)
          includeOptional = true (4)
          ignoreRegistryErrors = true (5)
          ignoreUnfixable = true (6)
      }
    }
  }
}
1 Sets the audit level using an enumeration. See PnpmAuditLevel.
2 Sets the audit level using a string. The value must match that of a pnpm audit level, but the value is case-insensitive. If the audit level is not set via either method, the default is to read it from the audit.level value in pnpm-workspace.yaml. If that does not exist, it is level to pnpm to use its default.
3 Whether to only audit production dependencies. The default is to do both production and development.
4 Whether to include optional dependencies. The default is to not include optional dependencies.
5 Whether to ignore registry errors. The default is to fail on registry errors.
6 Whether to ignore unfixable vulnerabilities. The default is to not ignore unfixable vulnerabilities.

This adds a PnpmWorkspaceAuditTask and a PnpmWorkspaceAuditFixTask. THe former is also added to the securityCheck lifecycle task if the appropriate Lifecycle plugin is applied.