Skip to content

Build targets run npm install (not npm ci), mutating package-lock.json mid-build #88

Description

@tompivny

Summary

The Dataverse build targets (Pcf, ScriptLibrary, CodeApp) invoke npm install with a hardcoded command string. npm install may rewrite package-lock.json during the build (it reconciles the lock file to package.json and can migrate lock-file format or resolve ranges to newer versions). In our build it rewrote 8 of 11 lock files. This has two downstream effects:

  1. Breaks pipeline npm caching. Any cache whose key is derived from package-lock.json (for example caching in Azure Pipelines) can never hit, because the lock file is modified between the cache restore and cache save steps — the key hashed at save time no longer matches the key hashed at the next run's restore time.
  2. Non-reproducible installs. npm install resolves semver ranges and may pull newer transitive versions than the committed lock file pins, so CI can build against a different dependency tree than was reviewed/committed.

Affected files

File Line Command
src/Dataverse/Pcf/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Pcf.targets 23 <Exec Command="npm install" ... />
src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets 16 <Exec ... Command="npm install" />
src/Dataverse/CodeApp/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CodeApp.targets 23 <Exec ... Command="npm install" />

The command is a literal string in all three targets — there is no MSBuild property to
override it, so consumers cannot opt into npm ci without patching the SDK.

Proposed fix

Prefer npm ci in CI when a lock file is present. npm ci:

  • installs strictly from package-lock.json and never writes it → stable cache key,
  • is faster (skips range resolution),
  • guarantees the committed tree is what gets built.

Do not default to npm ci on the mere existence of a lock file — that would degrade local development, because npm ci:

  • deletes node_modules and reinstalls from scratch on every build (these targets run on every MSBuild, so local incremental builds would pay a full reinstall each time), and
  • fails the build when package.json and package-lock.json are out of sync — a normal, transient state during local dev (edit package.json, haven't reinstalled yet). npm install heals that; npm ci aborts.

So gate on a CI signal as well. Make the command overridable and default to npm ci only in CI with a lock file present.

If defaulting to npm ci is considered too breaking, at minimum expose $(NpmInstallCommand) as an overridable property (defaulting to the current npm install) so consumers can opt in. That alone unblocks lock-file-keyed caching. It would also allow to use other alternatives such as pnpm.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions