@stackline/deepmerge 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes are documented here. This project follows Semantic
4
4
  Versioning.
5
5
 
6
+ ## [1.0.1] - 2026-08-21
7
+
8
+ ### Added
9
+
10
+ - Executable adoption examples for hostile configuration input, custom array
11
+ strategies, cycles, and shared references.
12
+ - Direct-install and npm-alias migration guide.
13
+ - Reproducible benchmark methodology and public Stackline catalog link.
14
+ - First-party documentation analytics that never records playground input.
15
+ - Trusted-publishing workflow for provenance-enabled future releases.
16
+
17
+ ### Changed
18
+
19
+ - Package tarballs now include the public guides and executable examples.
20
+ - CI resolves release tarballs dynamically instead of hardcoding a version.
21
+
22
+ No runtime API or declaration behavior changed in this release.
23
+
6
24
  ## [1.0.0] - 2026-08-16
7
25
 
8
26
  ### Added
@@ -21,3 +39,4 @@ Versioning.
21
39
  - Public documentation, security policy, CI matrix, and live playground.
22
40
 
23
41
  [1.0.0]: https://github.com/alexandroit/stackline-deepmerge/releases/tag/v1.0.0
42
+ [1.0.1]: https://github.com/alexandroit/stackline-deepmerge/compare/v1.0.0...v1.0.1
package/README.md CHANGED
@@ -246,7 +246,7 @@ The release matrix tests TypeScript `3.9`, `4.7`, `4.9`, `5.9`, `6.0`, and
246
246
  Use the ESM build with a bundler, or load the small browser global directly:
247
247
 
248
248
  ```html
249
- <script src="https://unpkg.com/@stackline/deepmerge@1.0.0/dist/index.min.js"></script>
249
+ <script src="https://unpkg.com/@stackline/deepmerge@1/dist/index.min.js"></script>
250
250
  <script>
251
251
  const merged = StacklineDeepmerge(
252
252
  { theme: { contrast: 'normal' } },
@@ -289,6 +289,17 @@ npm run benchmark
289
289
  Use benchmark results as regression signals, not universal claims. Runtime,
290
290
  CPU, input shape, and custom callbacks materially affect throughput.
291
291
 
292
+ ## Adoption resources
293
+
294
+ - [Direct install and drop-in alias guide](docs/ADOPTION.md)
295
+ - [Reproducible benchmark methodology](docs/BENCHMARKS.md)
296
+ - [Executable examples](examples)
297
+ - [Stackline open-source catalog](https://alexandro.net/docs/open-source/)
298
+
299
+ The examples are included in the npm tarball and run against the package's
300
+ public exports. They cover hostile configuration input, custom array strategy,
301
+ cycles, and shared references.
302
+
292
303
  ## Trust and maintenance
293
304
 
294
305
  - No runtime dependencies.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/deepmerge v1.0.0 | MIT */
1
+ /*! @stackline/deepmerge v1.0.1 | MIT */
2
2
  var StacklineDeepmergeModule = (() => {
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/deepmerge v1.0.0 | MIT */
1
+ /*! @stackline/deepmerge v1.0.1 | MIT */
2
2
 
3
3
  // src/index.js
4
4
  var objectToString = Object.prototype.toString;
package/dist/index.min.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/deepmerge v1.0.0 | MIT */
1
+ /*! @stackline/deepmerge v1.0.1 | MIT */
2
2
  var StacklineDeepmergeModule=(()=>{var h=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var N=(e,n)=>{for(var r in n)h(e,r,{get:n[r],enumerable:!0})},V=(e,n,r,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of W(n))!L.call(e,t)&&t!==r&&h(e,t,{get:()=>n[t],enumerable:!(o=C(n,t))||o.enumerable});return e};var z=e=>V(h({},"__esModule",{value:!0}),e);var ne={};N(ne,{DeepMergeLimitError:()=>p,UnsafeKeyError:()=>a,all:()=>P,deepmerge:()=>y,default:()=>ee,isMergeableObject:()=>M});var G=Object.prototype.toString,F=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable,J=new Set(["__proto__","prototype","constructor"]),Y=typeof Symbol=="function"&&typeof Symbol.for=="function"?Symbol.for("react.element"):60103,a=class extends TypeError{constructor(n,r){let o=$(r.concat(n));super(`Refusing to merge unsafe key ${String(n)} at ${o}`),this.name="UnsafeKeyError",this.code="ERR_DEEPMERGE_UNSAFE_KEY",this.key=n,this.path=o}},p=class extends RangeError{constructor(n,r,o){let t=$(o);super(`Deep merge ${n} limit of ${r} exceeded at ${t}`),this.name="DeepMergeLimitError",this.code="ERR_DEEPMERGE_LIMIT",this.kind=n,this.limit=r,this.path=t}};function M(e){if(!e||typeof e!="object")return!1;let n=G.call(e);return n==="[object Date]"||n==="[object RegExp]"?!1:e.$$typeof!==Y}function $(e){if(e.length===0)return"<root>";let n="<root>";for(let r of e)typeof r=="number"?n+=`[${r}]`:typeof r=="symbol"?n+=`[${String(r)}]`:/^[A-Za-z_$][\w$]*$/.test(r)?n+=`.${r}`:n+=`[${JSON.stringify(r)}]`;return n}function A(e,n,r){return e.concat(n).map(o=>r.cloneUnlessOtherwiseSpecified(o,r))}function O(e,n,r){let o=e===void 0?n:e;if(o===1/0)return o;if(!Number.isSafeInteger(o)||o<0)throw new TypeError(`${r} must be a non-negative safe integer or Infinity`);return o}function I(e){if(e!==void 0&&(e===null||typeof e!="object"))throw new TypeError("options must be an object when provided");let n=e||{},r=n.onUnsafeKey===void 0?"skip":n.onUnsafeKey;if(r!=="skip"&&r!=="throw")throw new TypeError("onUnsafeKey must be either 'skip' or 'throw'");let o={cloneMemo:new WeakMap,pairMemo:new WeakMap,keyCount:0,callbackDepth:0,options:null},t={...n,arrayMerge:n.arrayMerge===void 0?A:n.arrayMerge,isMergeableObject:n.isMergeableObject===void 0?M:n.isMergeableObject,maxDepth:O(n.maxDepth,1e3,"maxDepth"),maxKeys:O(n.maxKeys,1e5,"maxKeys"),onUnsafeKey:r};if(typeof t.arrayMerge!="function")throw new TypeError("arrayMerge must be a function");if(typeof t.isMergeableObject!="function")throw new TypeError("isMergeableObject must be a function");if(t.customMerge!==void 0&&typeof t.customMerge!="function")throw new TypeError("customMerge must be a function");return t.cloneUnlessOtherwiseSpecified=(f,i)=>{if(i&&i!==t){let u=I(i);return l(f,u,0,[])}return l(f,o,o.callbackDepth+1,[])},o.options=t,o}function m(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function U(e,n,r){if(!m(n)||!m(r))return;let o=e.pairMemo.get(n);return o&&o.get(r)}function T(e,n,r,o){if(!m(n)||!m(r))return;let t=e.pairMemo.get(n);t||(t=new WeakMap,e.pairMemo.set(n,t)),t.set(r,o)}function g(e,n,r){m(n)&&e.cloneMemo.set(n,r)}function Z(e,n,r){if(n>e.options.maxDepth)throw new p("depth",e.options.maxDepth,r)}function q(e,n,r){if(e.keyCount+=n,e.keyCount>e.options.maxKeys)throw new p("key",e.options.maxKeys,r)}function x(e){let n=Object.keys(e);return typeof Object.getOwnPropertySymbols!="function"?n:n.concat(Object.getOwnPropertySymbols(Object(e)).filter(r=>k.call(e,r)))}function B(e){return typeof e=="string"&&J.has(e)}function D(e,n,r){if(!B(e))return!1;if(n.options.onUnsafeKey==="throw")throw new a(e,r);return!0}function _(e,n){try{return n in e}catch(r){return!1}}function H(e,n){return _(e,n)&&!(F.call(e,n)&&k.call(e,n))}function K(e,n,r){Object.defineProperty(e,n,{configurable:!0,enumerable:!0,value:r,writable:!0})}function l(e,n,r,o){if(n.options.clone===!1||!n.options.isMergeableObject(e))return e;let t=n.cloneMemo.get(e);return t!==void 0?t:w(Array.isArray(e)?[]:{},e,n,r,o)}function Q(e,n,r,o,t){if(r.options.arrayMerge!==A){let s=r.callbackDepth;r.callbackDepth=o;try{return r.options.arrayMerge(e,n,r.options)}finally{r.callbackDepth=s}}let f=U(r,e,n);if(f!==void 0)return f;let i=[];T(r,e,n,i),g(r,e,i),g(r,n,i);let u=e.concat(n);i.length=u.length;for(let s=0;s<u.length;s+=1)s in u&&(i[s]=l(u[s],r,o+1,t.concat(s)));return i}function X(e,n){if(!n.options.customMerge)return;let r=n.options.customMerge(e,n.options);return typeof r=="function"?r:void 0}function v(e,n,r,o,t){let f=U(r,e,n);if(f!==void 0)return f;let i={};T(r,e,n,i),g(r,e,i),g(r,n,i);let u=r.options.isMergeableObject(e)?x(e):[],s=x(n);q(r,u.length+s.length,t);let E=[],j=new Set;for(let c of s)D(c,r,t)||H(e,c)||(E.push(c),j.add(c));for(let c of u)D(c,r,t)||j.has(c)||K(i,c,l(e[c],r,o+1,t.concat(c)));for(let c of E){let b=n[c],d;if(_(e,c)&&r.options.isMergeableObject(b)){let S=X(c,r);if(S){let R=r.callbackDepth;r.callbackDepth=o;try{d=S(e[c],b,r.options)}finally{r.callbackDepth=R}}else d=w(e[c],b,r,o+1,t.concat(c))}else d=l(b,r,o+1,t.concat(c));K(i,c,d)}return i}function w(e,n,r,o,t){Z(r,o,t);let f=Array.isArray(n),i=Array.isArray(e);return f!==i?l(n,r,o+1,t):f?Q(e,n,r,o,t):v(e,n,r,o,t)}function y(e,n,r){return w(e,n,I(r),0,[])}function P(e,n){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((r,o)=>y(r,o,n),{})}y.all=P;y.isMergeableObject=M;y.UnsafeKeyError=a;y.DeepMergeLimitError=p;y.deepmerge=y;var ee=y;return z(ne);})();
3
3
  (typeof globalThis !== 'undefined' ? globalThis : window).StacklineDeepmerge = StacklineDeepmergeModule.default;
4
4
  //# sourceMappingURL=index.min.js.map
@@ -0,0 +1,48 @@
1
+ # Adoption Guide
2
+
3
+ ## Choose the direct install when
4
+
5
+ - the application is new;
6
+ - imports can use the Stackline package name;
7
+ - explicit ownership of the hardened behavior is desirable.
8
+
9
+ ```bash
10
+ npm install @stackline/deepmerge
11
+ ```
12
+
13
+ ```js
14
+ import merge from '@stackline/deepmerge';
15
+ ```
16
+
17
+ ## Choose the npm alias when
18
+
19
+ - an application already imports `deepmerge`;
20
+ - a framework or internal package expects the existing package name;
21
+ - the migration should avoid source changes.
22
+
23
+ ```bash
24
+ npm uninstall deepmerge
25
+ npm install deepmerge@npm:@stackline/deepmerge
26
+ ```
27
+
28
+ Existing imports remain unchanged:
29
+
30
+ ```js
31
+ import merge from 'deepmerge';
32
+ ```
33
+
34
+ Commit the changed lockfile and run the application's complete test suite. The
35
+ alias changes package resolution, so source imports do not need to change.
36
+
37
+ ## Security boundary
38
+
39
+ Use `onUnsafeKey: 'throw'` when an unsafe key should reject an entire request.
40
+ The default `skip` mode ignores `__proto__`, `prototype`, and `constructor` at
41
+ every traversed level. Depth and key budgets remain enabled in both modes.
42
+
43
+ ## Compatibility review
44
+
45
+ Before migration, review custom `arrayMerge`, `customMerge`, and
46
+ `isMergeableObject` callbacks. The package preserves the documented v4 call
47
+ shape, while cycles, unsafe keys, invalid options, and traversal limits are
48
+ intentionally hardened.
@@ -0,0 +1,60 @@
1
+ # Architecture
2
+
3
+ ## Scope
4
+
5
+ The runtime has one responsibility: merge two JavaScript values without
6
+ mutating either input while enforcing a small set of security invariants.
7
+
8
+ ## Merge state
9
+
10
+ Each top-level merge creates isolated state containing:
11
+
12
+ - normalized options;
13
+ - a target/source pair memo for recursive merges;
14
+ - a clone memo for repeated source references;
15
+ - the cumulative enumerable-key count;
16
+ - callback depth for compatibility hooks.
17
+
18
+ No state is shared between calls.
19
+
20
+ ## Traversal
21
+
22
+ `mergeInternal` dispatches by array shape:
23
+
24
+ - array plus array uses the configured array strategy;
25
+ - mismatched array/object shape clones the source;
26
+ - object plus object copies target-only keys and then merges source keys.
27
+
28
+ Accepted values are written with `Object.defineProperty` as own enumerable data
29
+ properties. This avoids invoking legacy object prototype setters during writes.
30
+
31
+ ## Security invariants
32
+
33
+ Before reading an enumerable property value, traversal checks:
34
+
35
+ 1. the key is not `__proto__`, `prototype`, or `constructor`;
36
+ 2. the target does not expose that property only through an inherited or
37
+ non-enumerable slot;
38
+ 3. configured depth and key budgets have not been exceeded.
39
+
40
+ Both source and target keys pass the dangerous-key filter. Rejected getters are
41
+ never invoked.
42
+
43
+ ## Cycle handling
44
+
45
+ Weak maps preserve cycles without retaining merged graphs after the operation.
46
+ The pair memo identifies a repeated target/source recursion. The clone memo
47
+ preserves repeated references when a source value is cloned into the result.
48
+
49
+ ## Distribution
50
+
51
+ One source module produces:
52
+
53
+ - `dist/index.js`: ESM;
54
+ - `dist/index.cjs`: callable CommonJS;
55
+ - `dist/index.min.js`: browser global;
56
+ - resolver-specific `.d.ts`, `.d.cts`, and `.d.mts` declarations;
57
+ - source maps for each JavaScript build.
58
+
59
+ The CommonJS footer assigns the function itself to `module.exports`, preserving
60
+ legacy `require('deepmerge')({...})` usage.
@@ -0,0 +1,25 @@
1
+ # Benchmark Methodology
2
+
3
+ The checked-in benchmark compares `@stackline/deepmerge` with
4
+ `deepmerge@4.3.1` in the same Node.js process and with the same nested
5
+ configuration objects.
6
+
7
+ ```bash
8
+ npm ci
9
+ npm run benchmark
10
+ ```
11
+
12
+ Set `BENCHMARK_ITERATIONS` to control the sample size:
13
+
14
+ ```bash
15
+ BENCHMARK_ITERATIONS=500000 npm run benchmark
16
+ ```
17
+
18
+ The benchmark warms both implementations before measurement and reports
19
+ operations per second. It is a regression tool, not a universal performance
20
+ claim. CPU, Node.js version, object shape, callbacks, cycle tracking, and
21
+ security limits materially affect results.
22
+
23
+ Compatibility and security take priority over winning a synthetic throughput
24
+ number. Report the complete command, Node.js version, CPU, and raw output when
25
+ sharing results.
@@ -0,0 +1,72 @@
1
+ # Compatibility
2
+
3
+ ## Behavioral target
4
+
5
+ The default contract follows documented `deepmerge@4.3.1` behavior:
6
+
7
+ - inputs are not mutated;
8
+ - plain and custom mergeable objects recurse;
9
+ - arrays concatenate and clone by default;
10
+ - source wins when array/object shapes differ;
11
+ - `clone: false` preserves nested references;
12
+ - custom array and property merge hooks receive compatible options;
13
+ - Date, RegExp, and React element values are atomic by default;
14
+ - own enumerable symbols are copied;
15
+ - `all` merges an array of objects.
16
+
17
+ The test suite includes documented scenarios and a deterministic corpus of
18
+ 5,000 JSON-compatible input pairs compared against `deepmerge@4.3.1`.
19
+
20
+ ## Intentional differences
21
+
22
+ | Area | `@stackline/deepmerge` behavior |
23
+ | :--- | :--- |
24
+ | Dangerous keys | Always skipped or rejected at every depth |
25
+ | Cycles | Preserved through weak-reference memoization |
26
+ | Resource limits | Depth and key limits enabled by default |
27
+ | Invalid options | Rejected early with controlled errors |
28
+ | Package format | Native ESM, callable CJS, browser build, conditional types |
29
+ | Runtime dependencies | None |
30
+
31
+ ## Module systems
32
+
33
+ | Consumer | Supported form |
34
+ | :--- | :--- |
35
+ | ESM | Default and named exports |
36
+ | CommonJS | Callable `module.exports` plus static helpers |
37
+ | Bundlers | ESM `module` field and `browser` export condition |
38
+ | Browser script | `globalThis.StacklineDeepmerge` |
39
+ | npm alias | `deepmerge@npm:@stackline/deepmerge` |
40
+
41
+ ## TypeScript
42
+
43
+ The release test matrix covers:
44
+
45
+ - 3.9.10;
46
+ - 4.7.4;
47
+ - 4.9.5;
48
+ - 5.9.3;
49
+ - 6.0.3;
50
+ - 7.0.2.
51
+
52
+ Older resolvers use `dist/index.d.ts`. Modern ESM and CommonJS resolvers use
53
+ `dist/index.d.mts` and `dist/index.d.cts` respectively.
54
+
55
+ ## Runtime
56
+
57
+ The published JavaScript targets ES2018 and declares Node.js 14.17 or newer.
58
+ The development toolchain requires a newer Node.js release; that does not alter
59
+ the package's runtime floor.
60
+
61
+ ## Migration test
62
+
63
+ Test the alias in the consuming project without source changes:
64
+
65
+ ```bash
66
+ npm install deepmerge@npm:@stackline/deepmerge
67
+ npm test
68
+ ```
69
+
70
+ If a project relies on undocumented prototype keys, accessor descriptors, or a
71
+ specific cycle failure, treat migration as a behavior change and report the use
72
+ case before production rollout.
@@ -0,0 +1,110 @@
1
+ # Market Research
2
+
3
+ Research date: 2026-08-15
4
+
5
+ ## Decision
6
+
7
+ Build `@stackline/deepmerge`: a secure, zero-dependency, modern-package
8
+ replacement for the established `deepmerge` API.
9
+
10
+ The goal is not to predict one million monthly downloads. No package can
11
+ guarantee adoption. The goal is to enter a proven, extremely high-volume
12
+ category with a low-friction migration and clear unmet maintenance needs.
13
+
14
+ ## Demand snapshot
15
+
16
+ The official npm downloads API reported the following for 2026-07-11 through
17
+ 2026-08-09:
18
+
19
+ | Package | Downloads |
20
+ | :--- | ---: |
21
+ | `lodash.merge` | 493,506,292 |
22
+ | `deepmerge` | 353,418,274 |
23
+ | `deep-extend` | 180,360,708 |
24
+ | `defu` | 143,244,968 |
25
+ | `deepmerge-ts` | 64,560,064 |
26
+ | `@fastify/deepmerge` | 13,948,869 |
27
+ | `ts-deepmerge` | 13,902,566 |
28
+ | `merge-deep` | 7,970,115 |
29
+
30
+ Source: `https://api.npmjs.org/downloads/point/last-month/<package>`.
31
+
32
+ These values are a dated market snapshot, not implementation constants or
33
+ future promises.
34
+
35
+ ## Maintenance gap
36
+
37
+ At the research snapshot:
38
+
39
+ - npm reported `deepmerge@4.3.1`, modified 2023-03-16;
40
+ - the upstream GitHub repository was last pushed 2023-04-21;
41
+ - the repository had 2,816 stars and 59 open issues;
42
+ - open requests covered ESM packaging, default-import interop, TypeScript
43
+ inference, and cyclic objects.
44
+
45
+ Sources:
46
+
47
+ - [deepmerge on npm](https://www.npmjs.com/package/deepmerge)
48
+ - [deepmerge repository](https://github.com/TehShrike/deepmerge)
49
+ - [ESM request 194](https://github.com/TehShrike/deepmerge/issues/194)
50
+ - [Cycle request 207](https://github.com/TehShrike/deepmerge/issues/207)
51
+ - [ESM request 250](https://github.com/TehShrike/deepmerge/issues/250)
52
+ - [Default import issue 255](https://github.com/TehShrike/deepmerge/issues/255)
53
+ - [Type inference issue 271](https://github.com/TehShrike/deepmerge/issues/271)
54
+
55
+ ## Security need
56
+
57
+ Merge utilities repeatedly appear in prototype-pollution advisories because
58
+ they recursively turn attacker-controlled property names into object writes.
59
+ Modern exploit research also shows that read-side gadgets can make inherited
60
+ pollution dangerous even when a specific merge implementation does not write
61
+ directly to `Object.prototype`.
62
+
63
+ References:
64
+
65
+ - [GitHub Security Lab merge-deep advisory](https://securitylab.github.com/advisories/GHSL-2020-160-merge-deep/)
66
+ - [GHSA-9wv6-86v2-598j](https://github.com/advisories/GHSA-9wv6-86v2-598j)
67
+ - [GHSA-wf5p-g6vw-rhxx](https://github.com/advisories/GHSA-wf5p-g6vw-rhxx)
68
+
69
+ ## Product thesis
70
+
71
+ Adoption depends on reducing migration cost:
72
+
73
+ 1. Preserve the callable default export and documented extension hooks.
74
+ 2. Support an npm alias so existing `import merge from 'deepmerge'` calls do
75
+ not change.
76
+ 3. Ship both modern and legacy module shapes with accurate types.
77
+ 4. Remove runtime supply-chain exposure by using zero dependencies.
78
+ 5. Make unsafe-key behavior and resource limits visible and testable.
79
+ 6. Provide a browser playground that demonstrates actual merge behavior.
80
+
81
+ ## Alternatives considered
82
+
83
+ ### General utility collection
84
+
85
+ Rejected because it increases API surface and supply-chain responsibility
86
+ without a sharper migration story.
87
+
88
+ ### Fork the existing implementation
89
+
90
+ Rejected for this package. An independent implementation can adopt modern
91
+ architecture and security invariants while behavioral differential tests
92
+ protect compatibility.
93
+
94
+ ### Type-only deep merge
95
+
96
+ Rejected because the largest need is runtime behavior at trust boundaries.
97
+
98
+ ### New incompatible API
99
+
100
+ Rejected for 1.0. A new API would discard the strongest distribution path:
101
+ drop-in migration from an established package.
102
+
103
+ ## Success measures
104
+
105
+ - clean direct and alias installs;
106
+ - no runtime dependencies;
107
+ - no known package export or type-resolution defects;
108
+ - compatibility reports from real consumers;
109
+ - security reports handled through private disclosure;
110
+ - sustained download growth without weakening the contract.
@@ -0,0 +1,53 @@
1
+ # Releasing
2
+
3
+ Only maintainers with npm scope access can publish a release.
4
+
5
+ ## Required checks
6
+
7
+ ```bash
8
+ npm ci
9
+ npm test
10
+ npm run test:attw
11
+ npm run benchmark
12
+ npm run audit:dependencies
13
+ npm pack --dry-run
14
+ ```
15
+
16
+ Run the full TypeScript matrix and clean-install smoke test. Verify GitHub CI is
17
+ green on `main` before public npm publication.
18
+
19
+ ## Artifact rule
20
+
21
+ Build one tarball and identify its SHA-512 digest. Publish that exact file to
22
+ Verdaccio. The trusted GitHub workflow rebuilds from the reviewed commit,
23
+ requires the expected digest as input, and refuses to publish to npm unless the
24
+ bytes are identical.
25
+
26
+ ```bash
27
+ version="$(node -p "require('./package.json').version")"
28
+ mkdir -p "release/$version"
29
+ npm pack --ignore-scripts --pack-destination "release/$version"
30
+ (cd "release/$version" && sha512sum *.tgz > SHA512SUMS)
31
+ ```
32
+
33
+ Do not rebuild between registries. After each publish, install from that
34
+ registry in an empty project and test direct ESM, direct CommonJS, npm alias,
35
+ and TypeScript resolution.
36
+
37
+ ## Release order
38
+
39
+ 1. Update version, changelog, docs, and project memory.
40
+ 2. Run all required checks.
41
+ 3. Commit and push `main`.
42
+ 4. Wait for GitHub checks.
43
+ 5. Create the final tarball and digest.
44
+ 6. Publish and smoke-test Verdaccio.
45
+ 7. Run `publish.yml` with the tarball's SHA-512 hex digest and watch it publish
46
+ the byte-identical artifact through npm trusted publishing.
47
+ 8. Download from public npm, compare SHA-512, and smoke-test.
48
+ 9. Push the signed or annotated version tag.
49
+ 10. Create the GitHub release.
50
+ 11. Deploy and verify public documentation.
51
+ 12. Record package metadata and validation evidence in project memory.
52
+
53
+ Published npm versions are immutable. Never reuse a version number.
@@ -0,0 +1,17 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import merge from '@stackline/deepmerge';
4
+
5
+ const overwriteArrays = (_target, source) => source;
6
+ const config = merge(
7
+ { plugins: ['core'], server: { port: 3000 } },
8
+ { plugins: ['metrics'], server: { secure: true } },
9
+ { arrayMerge: overwriteArrays }
10
+ );
11
+
12
+ assert.deepEqual(config, {
13
+ plugins: ['metrics'],
14
+ server: { port: 3000, secure: true }
15
+ });
16
+
17
+ console.log(config);
@@ -0,0 +1,14 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import merge from '@stackline/deepmerge';
4
+
5
+ const shared = { enabled: true };
6
+ const source = { first: shared, second: shared };
7
+ source.self = source;
8
+
9
+ const result = merge({}, source);
10
+
11
+ assert.equal(result.first, result.second);
12
+ assert.equal(result.self, result);
13
+
14
+ console.log({ cyclePreserved: true, sharedReferencePreserved: true });
@@ -0,0 +1,22 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import merge from '@stackline/deepmerge';
4
+
5
+ const defaults = {
6
+ cache: { enabled: true, ttl: 60 },
7
+ server: { host: '127.0.0.1', port: 3000 }
8
+ };
9
+ const environment = JSON.parse(`{
10
+ "server": { "port": 8080 },
11
+ "constructor": { "prototype": { "isAdmin": true } }
12
+ }`);
13
+
14
+ const config = merge(defaults, environment);
15
+
16
+ assert.deepEqual(config, {
17
+ cache: { enabled: true, ttl: 60 },
18
+ server: { host: '127.0.0.1', port: 8080 }
19
+ });
20
+ assert.equal(Object.prototype.isAdmin, undefined);
21
+
22
+ console.log(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackline/deepmerge",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Secure, immutable, zero-dependency deep merge with a deepmerge-compatible API for ESM, CommonJS, TypeScript, and browsers",
5
5
  "keywords": [
6
6
  "deepmerge",
@@ -59,6 +59,8 @@
59
59
  },
60
60
  "files": [
61
61
  "dist",
62
+ "docs",
63
+ "examples",
62
64
  "CHANGELOG.md",
63
65
  "CONTRIBUTING.md",
64
66
  "LICENSE",
@@ -73,10 +75,11 @@
73
75
  "clean": "node scripts/clean.mjs",
74
76
  "build": "node scripts/build.mjs",
75
77
  "lint": "eslint . && node scripts/check-markdown.mjs",
76
- "test": "npm run build && npm run lint && npm run test:coverage && npm run test:types && npm run test:package && npm run test:install && npm run test:docs",
78
+ "test": "npm run build && npm run lint && npm run test:coverage && npm run test:types && npm run test:examples && npm run test:package && npm run test:install && npm run test:docs",
77
79
  "test:unit": "node --test --test-reporter=spec test/*.test.mjs",
78
80
  "test:coverage": "c8 --all --src src --check-coverage --lines 100 --functions 100 --statements 100 --branches 95 node --test test/core.test.mjs test/cycles-limits.test.mjs test/security.test.mjs test/compatibility.test.mjs",
79
81
  "test:types": "node scripts/test-types.mjs",
82
+ "test:examples": "node examples/secure-config.mjs && node examples/array-strategy.mjs && node examples/cyclic-graph.mjs",
80
83
  "test:package": "node --test test/package.test.mjs && node scripts/check-dist.mjs && publint",
81
84
  "test:install": "node scripts/smoke-install.mjs",
82
85
  "test:docs": "npm run docs:build && node scripts/check-docs.mjs",