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:
- 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.
- 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.
Summary
The Dataverse build targets (
Pcf,ScriptLibrary,CodeApp) invokenpm installwith a hardcoded command string.npm installmay rewritepackage-lock.jsonduring the build (it reconciles the lock file topackage.jsonand 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: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.npm installresolves 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
src/Dataverse/Pcf/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.Pcf.targets<Exec Command="npm install" ... />src/Dataverse/ScriptLibrary/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.ScriptLibrary.targets<Exec ... Command="npm install" />src/Dataverse/CodeApp/msbuild/tasks/TALXIS.DevKit.Build.Dataverse.CodeApp.targets<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 ciwithout patching the SDK.Proposed fix
Prefer
npm ciin CI when a lock file is present.npm ci:package-lock.jsonand never writes it → stable cache key,Do not default to
npm cion the mere existence of a lock file — that would degrade local development, becausenpm ci:node_modulesand reinstalls from scratch on every build (these targets run on every MSBuild, so local incremental builds would pay a full reinstall each time), andpackage.jsonandpackage-lock.jsonare out of sync — a normal, transient state during local dev (editpackage.json, haven't reinstalled yet).npm installheals that;npm ciaborts.So gate on a CI signal as well. Make the command overridable and default to
npm cionly in CI with a lock file present.If defaulting to
npm ciis considered too breaking, at minimum expose$(NpmInstallCommand)as an overridable property (defaulting to the currentnpm install) so consumers can opt in. That alone unblocks lock-file-keyed caching. It would also allow to use other alternatives such aspnpm.