@atlaspack/core 2.16.2-dev.55 → 2.16.2-dev.72

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/_empty.js +0 -0
  3. package/lib/AssetGraph.js +17 -6
  4. package/lib/Atlaspack.js +2 -5
  5. package/lib/AtlaspackConfig.schema.js +1 -7
  6. package/lib/BundleGraph.js +10 -8
  7. package/lib/Dependency.js +6 -2
  8. package/lib/Environment.js +4 -3
  9. package/lib/EnvironmentManager.js +137 -0
  10. package/lib/InternalConfig.js +3 -2
  11. package/lib/PackagerRunner.js +15 -9
  12. package/lib/RequestTracker.js +56 -46
  13. package/lib/Transformation.js +2 -2
  14. package/lib/UncommittedAsset.js +3 -2
  15. package/lib/applyRuntimes.js +2 -1
  16. package/lib/assetUtils.js +2 -1
  17. package/lib/atlaspack-v3/AtlaspackV3.js +44 -1
  18. package/lib/atlaspack-v3/NapiWorkerPool.js +1 -1
  19. package/lib/atlaspack-v3/worker/compat/environment.js +2 -2
  20. package/lib/atlaspack-v3/worker/compat/mutable-asset.js +6 -6
  21. package/lib/atlaspack-v3/worker/compat/plugin-config.js +5 -5
  22. package/lib/atlaspack-v3/worker/napi-worker.js +3 -0
  23. package/lib/atlaspack-v3/worker/worker.js +11 -4
  24. package/lib/dumpGraphToGraphViz.js +1 -1
  25. package/lib/index.js +9 -1
  26. package/lib/internal-plugins.js +9 -0
  27. package/lib/isSuperPackage.js +23 -0
  28. package/lib/loadAtlaspackPlugin.js +15 -0
  29. package/lib/public/Asset.js +3 -2
  30. package/lib/public/Bundle.js +2 -1
  31. package/lib/public/Config.js +86 -19
  32. package/lib/public/Dependency.js +2 -1
  33. package/lib/public/MutableBundleGraph.js +2 -1
  34. package/lib/public/PluginOptions.js +3 -0
  35. package/lib/public/Target.js +2 -1
  36. package/lib/requests/AssetRequest.js +2 -1
  37. package/lib/requests/AtlaspackConfigRequest.js +44 -29
  38. package/lib/requests/ConfigRequest.js +27 -4
  39. package/lib/requests/TargetRequest.js +18 -16
  40. package/lib/requests/WriteBundleRequest.js +5 -2
  41. package/lib/requests/WriteBundlesRequest.js +1 -0
  42. package/lib/resolveOptions.js +5 -4
  43. package/lib/worker.js +9 -26
  44. package/package.json +21 -18
  45. package/src/AssetGraph.js +12 -6
  46. package/src/Atlaspack.js +8 -7
  47. package/src/BundleGraph.js +21 -9
  48. package/src/Dependency.js +13 -5
  49. package/src/Environment.js +8 -5
  50. package/src/EnvironmentManager.js +145 -0
  51. package/src/InternalConfig.js +6 -5
  52. package/src/PackagerRunner.js +17 -11
  53. package/src/RequestTracker.js +39 -13
  54. package/src/UncommittedAsset.js +7 -2
  55. package/src/applyRuntimes.js +6 -1
  56. package/src/assetUtils.js +4 -3
  57. package/src/atlaspack-v3/AtlaspackV3.js +53 -2
  58. package/src/atlaspack-v3/NapiWorkerPool.js +5 -1
  59. package/src/atlaspack-v3/worker/compat/plugin-config.js +1 -1
  60. package/src/atlaspack-v3/worker/worker.js +11 -4
  61. package/src/index.js +2 -0
  62. package/src/internal-plugins.js +4 -0
  63. package/src/isSuperPackage.js +28 -0
  64. package/src/loadAtlaspackPlugin.js +23 -1
  65. package/src/public/Asset.js +9 -2
  66. package/src/public/Bundle.js +2 -1
  67. package/src/public/Config.js +110 -29
  68. package/src/public/Dependency.js +2 -1
  69. package/src/public/MutableBundleGraph.js +2 -1
  70. package/src/public/PluginOptions.js +4 -0
  71. package/src/public/Target.js +2 -1
  72. package/src/requests/AssetRequest.js +2 -1
  73. package/src/requests/AtlaspackConfigRequest.js +77 -31
  74. package/src/requests/ConfigRequest.js +33 -9
  75. package/src/requests/TargetRequest.js +19 -25
  76. package/src/requests/WriteBundleRequest.js +6 -7
  77. package/src/requests/WriteBundlesRequest.js +1 -0
  78. package/src/resolveOptions.js +2 -0
  79. package/src/types.js +10 -7
  80. package/src/worker.js +8 -7
  81. package/test/Environment.test.js +43 -34
  82. package/test/EnvironmentManager.test.js +192 -0
  83. package/test/PublicEnvironment.test.js +10 -7
  84. package/test/RequestTracker.test.js +1 -4
  85. package/test/public/Config.test.js +108 -0
  86. package/test/requests/ConfigRequest.test.js +187 -3
  87. package/test/test-utils.js +3 -2
  88. package/lib/atlaspack-v3/worker/index.js +0 -6
  89. /package/src/atlaspack-v3/worker/{index.js → napi-worker.js} +0 -0
@@ -5,10 +5,11 @@ import assert from 'assert';
5
5
  import expect from 'expect';
6
6
  import {createEnvironment} from '../src/Environment';
7
7
  import {initializeMonitoring} from '../../rust';
8
+ import {fromEnvironmentId} from '../src/EnvironmentManager';
8
9
 
9
10
  describe('Environment', () => {
10
11
  it('assigns a default environment with nothing passed', () => {
11
- assert.deepEqual(createEnvironment(), {
12
+ assert.deepEqual(fromEnvironmentId(createEnvironment()), {
12
13
  id: 'd821e85f6b50315e',
13
14
  context: 'browser',
14
15
  engines: {
@@ -27,27 +28,32 @@ describe('Environment', () => {
27
28
  });
28
29
 
29
30
  it('assigns a node context if a node engine is given', () => {
30
- assert.deepEqual(createEnvironment({engines: {node: '>= 10.0.0'}}), {
31
- id: '2320af923a717577',
32
- context: 'node',
33
- engines: {
34
- node: '>= 10.0.0',
31
+ assert.deepEqual(
32
+ fromEnvironmentId(createEnvironment({engines: {node: '>= 10.0.0'}})),
33
+ {
34
+ id: '2320af923a717577',
35
+ context: 'node',
36
+ engines: {
37
+ node: '>= 10.0.0',
38
+ },
39
+ includeNodeModules: false,
40
+ outputFormat: 'commonjs',
41
+ isLibrary: false,
42
+ shouldOptimize: false,
43
+ shouldScopeHoist: false,
44
+ sourceMap: undefined,
45
+ loc: undefined,
46
+ sourceType: 'module',
47
+ unstableSingleFileOutput: false,
35
48
  },
36
- includeNodeModules: false,
37
- outputFormat: 'commonjs',
38
- isLibrary: false,
39
- shouldOptimize: false,
40
- shouldScopeHoist: false,
41
- sourceMap: undefined,
42
- loc: undefined,
43
- sourceType: 'module',
44
- unstableSingleFileOutput: false,
45
- });
49
+ );
46
50
  });
47
51
 
48
52
  it('assigns a browser context if browser engines are given', () => {
49
53
  assert.deepEqual(
50
- createEnvironment({engines: {browsers: ['last 1 version']}}),
54
+ fromEnvironmentId(
55
+ createEnvironment({engines: {browsers: ['last 1 version']}}),
56
+ ),
51
57
  {
52
58
  id: '75603271034eff15',
53
59
  context: 'browser',
@@ -68,7 +74,7 @@ describe('Environment', () => {
68
74
  });
69
75
 
70
76
  it('assigns default engines for node', () => {
71
- assert.deepEqual(createEnvironment({context: 'node'}), {
77
+ assert.deepEqual(fromEnvironmentId(createEnvironment({context: 'node'})), {
72
78
  id: 'e45cc12216f7857d',
73
79
  context: 'node',
74
80
  engines: {
@@ -87,22 +93,25 @@ describe('Environment', () => {
87
93
  });
88
94
 
89
95
  it('assigns default engines for browsers', () => {
90
- assert.deepEqual(createEnvironment({context: 'browser'}), {
91
- id: 'd821e85f6b50315e',
92
- context: 'browser',
93
- engines: {
94
- browsers: ['> 0.25%'],
96
+ assert.deepEqual(
97
+ fromEnvironmentId(createEnvironment({context: 'browser'})),
98
+ {
99
+ id: 'd821e85f6b50315e',
100
+ context: 'browser',
101
+ engines: {
102
+ browsers: ['> 0.25%'],
103
+ },
104
+ includeNodeModules: true,
105
+ outputFormat: 'global',
106
+ isLibrary: false,
107
+ shouldOptimize: false,
108
+ shouldScopeHoist: false,
109
+ sourceMap: undefined,
110
+ loc: undefined,
111
+ sourceType: 'module',
112
+ unstableSingleFileOutput: false,
95
113
  },
96
- includeNodeModules: true,
97
- outputFormat: 'global',
98
- isLibrary: false,
99
- shouldOptimize: false,
100
- shouldScopeHoist: false,
101
- sourceMap: undefined,
102
- loc: undefined,
103
- sourceType: 'module',
104
- unstableSingleFileOutput: false,
105
- });
114
+ );
106
115
  });
107
116
  });
108
117
 
@@ -114,6 +123,6 @@ describe('createEnvironment', function () {
114
123
  /* ignore */
115
124
  }
116
125
  const environment = createEnvironment({});
117
- expect(environment.id).toEqual('d821e85f6b50315e');
126
+ expect(fromEnvironmentId(environment).id).toEqual('d821e85f6b50315e');
118
127
  });
119
128
  });
@@ -0,0 +1,192 @@
1
+ // @flow strict-local
2
+
3
+ import assert from 'assert';
4
+ import nullthrows from 'nullthrows';
5
+ import sinon from 'sinon';
6
+ import {ATLASPACK_VERSION} from '../src/constants';
7
+ import {DEFAULT_FEATURE_FLAGS, setFeatureFlags} from '@atlaspack/feature-flags';
8
+ import {setAllEnvironments, getAllEnvironments} from '@atlaspack/rust';
9
+ import {
10
+ loadEnvironmentsFromCache,
11
+ writeEnvironmentsToCache,
12
+ } from '../src/EnvironmentManager';
13
+ import {DEFAULT_OPTIONS} from './test-utils';
14
+ import {LMDBLiteCache} from '@atlaspack/cache';
15
+
16
+ const options = {
17
+ ...DEFAULT_OPTIONS,
18
+ cache: new LMDBLiteCache(DEFAULT_OPTIONS.cacheDir),
19
+ };
20
+
21
+ describe('EnvironmentManager', () => {
22
+ const env1 = {
23
+ id: 'd821e85f6b50315e',
24
+ context: 'browser',
25
+ engines: {browsers: ['> 0.25%']},
26
+ includeNodeModules: true,
27
+ outputFormat: 'global',
28
+ isLibrary: false,
29
+ shouldOptimize: false,
30
+ shouldScopeHoist: false,
31
+ loc: undefined,
32
+ sourceMap: undefined,
33
+ sourceType: 'module',
34
+ unstableSingleFileOutput: false,
35
+ };
36
+ const env2 = {
37
+ id: 'de92f48baa8448d2',
38
+ context: 'node',
39
+ engines: {
40
+ browsers: [],
41
+ node: '>= 8',
42
+ },
43
+ includeNodeModules: false,
44
+ outputFormat: 'commonjs',
45
+ isLibrary: true,
46
+ shouldOptimize: true,
47
+ shouldScopeHoist: true,
48
+ loc: null,
49
+ sourceMap: null,
50
+ sourceType: 'module',
51
+ unstableSingleFileOutput: false,
52
+ };
53
+
54
+ beforeEach(async () => {
55
+ await options.cache.ensure();
56
+
57
+ for (const key of options.cache.keys()) {
58
+ await options.cache.getNativeRef().delete(key);
59
+ }
60
+ setAllEnvironments([]);
61
+
62
+ setFeatureFlags({
63
+ ...DEFAULT_FEATURE_FLAGS,
64
+ environmentDeduplication: true,
65
+ });
66
+ });
67
+
68
+ it('should store environments by ID in the cache', async () => {
69
+ setAllEnvironments([env1]);
70
+ await writeEnvironmentsToCache(options.cache);
71
+
72
+ const cachedEnv1 = await options.cache.get(
73
+ `Environment/${ATLASPACK_VERSION}/${env1.id}`,
74
+ );
75
+ assert.deepEqual(cachedEnv1, env1, 'Environment 1 should be cached');
76
+ });
77
+
78
+ it('should list all environment IDs in the environment manager', async () => {
79
+ const environmentIds = [env1.id, env2.id];
80
+ setAllEnvironments([env1, env2]);
81
+ await writeEnvironmentsToCache(options.cache);
82
+
83
+ const cachedEnvIds = await options.cache.get(
84
+ `EnvironmentManager/${ATLASPACK_VERSION}`,
85
+ );
86
+ const cachedIdsArray = nullthrows(cachedEnvIds);
87
+ assert.equal(
88
+ cachedIdsArray.length,
89
+ environmentIds.length,
90
+ 'Should have same number of IDs',
91
+ );
92
+ assert(
93
+ environmentIds.every((id) => cachedIdsArray.includes(id)),
94
+ 'All environment IDs should be present in cache',
95
+ );
96
+ });
97
+
98
+ it('should write all environments to cache using writeEnvironmentsToCache', async () => {
99
+ setAllEnvironments([env1, env2]);
100
+ await writeEnvironmentsToCache(options.cache);
101
+
102
+ // Verify each environment was stored individually
103
+ const cachedEnv1 = await options.cache.get(
104
+ `Environment/${ATLASPACK_VERSION}/${env1.id}`,
105
+ );
106
+ const cachedEnv2 = await options.cache.get(
107
+ `Environment/${ATLASPACK_VERSION}/${env2.id}`,
108
+ );
109
+ assert.deepEqual(cachedEnv1, env1, 'Environment 1 should be cached');
110
+ assert.deepEqual(cachedEnv2, env2, 'Environment 2 should be cached');
111
+
112
+ // Verify environment IDs were stored in manager
113
+ const cachedEnvIds = await options.cache.get(
114
+ `EnvironmentManager/${ATLASPACK_VERSION}`,
115
+ );
116
+ const cachedIdsArray = nullthrows(cachedEnvIds);
117
+ assert(
118
+ cachedIdsArray.length === 2 &&
119
+ [env1.id, env2.id].every((id) => cachedIdsArray.includes(id)),
120
+ 'Environment IDs should be stored in manager',
121
+ );
122
+ });
123
+
124
+ it('should load environments from cache on loadRequestGraph on a subsequent build', async () => {
125
+ // Simulate cache written on a first build
126
+ setAllEnvironments([env1, env2]);
127
+ await writeEnvironmentsToCache(options.cache);
128
+
129
+ await loadEnvironmentsFromCache(options.cache);
130
+
131
+ const loadedEnvironments = getAllEnvironments();
132
+ assert.equal(
133
+ loadedEnvironments.length,
134
+ 2,
135
+ 'Should load 2 environments from cache',
136
+ );
137
+
138
+ const env1Loaded = loadedEnvironments.find((e) => e.id === env1.id);
139
+ const env2Loaded = loadedEnvironments.find((e) => e.id === env2.id);
140
+
141
+ assert.deepEqual(
142
+ env1Loaded,
143
+ env1,
144
+ 'First environment should match cached environment',
145
+ );
146
+ assert.deepEqual(
147
+ env2Loaded,
148
+ env2,
149
+ 'Second environment should match cached environment',
150
+ );
151
+ });
152
+
153
+ it('should handle empty cache gracefully without calling setAllEnvironments', async () => {
154
+ const setAllEnvironmentsSpy = sinon.spy(setAllEnvironments);
155
+
156
+ await assert.doesNotReject(
157
+ loadEnvironmentsFromCache(options.cache),
158
+ 'loadEnvironmentsFromCache should not throw when cache is empty',
159
+ );
160
+
161
+ assert.equal(
162
+ setAllEnvironmentsSpy.callCount,
163
+ 0,
164
+ 'setAllEnvironments should not be called when loading from empty cache',
165
+ );
166
+ });
167
+
168
+ it('should not load environments from a different version', async () => {
169
+ const setAllEnvironmentsSpy = sinon.spy(setAllEnvironments);
170
+ const differentVersion = '2.17.2'; // A different version than ATLASPACK_VERSION
171
+
172
+ // Store an environment with a different version
173
+ await options.cache.set(`Environment/${differentVersion}/${env1.id}`, env1);
174
+ await options.cache.set(`EnvironmentManager/${differentVersion}`, [
175
+ env1.id,
176
+ ]);
177
+
178
+ await loadEnvironmentsFromCache(options.cache);
179
+
180
+ assert.equal(
181
+ setAllEnvironmentsSpy.callCount,
182
+ 0,
183
+ 'setAllEnvironments should not be called when loading from different version',
184
+ );
185
+ const loadedEnvironments = getAllEnvironments();
186
+ assert.equal(
187
+ loadedEnvironments.length,
188
+ 0,
189
+ 'Should not load any environments from different version',
190
+ );
191
+ });
192
+ });
@@ -2,19 +2,22 @@
2
2
 
3
3
  import assert from 'assert';
4
4
  import {createEnvironment} from '../src/Environment';
5
+ import {fromEnvironmentId} from '../src/EnvironmentManager';
5
6
  import PublicEnvironment from '../src/public/Environment';
6
7
  import {DEFAULT_OPTIONS} from './test-utils';
7
8
 
8
9
  describe('Public Environment', () => {
9
10
  it('has correct support data for ChromeAndroid', () => {
10
11
  let env = new PublicEnvironment(
11
- createEnvironment({
12
- context: 'browser',
13
- engines: {
14
- browsers: ['last 1 Chrome version', 'last 1 ChromeAndroid version'],
15
- },
16
- outputFormat: 'esmodule',
17
- }),
12
+ fromEnvironmentId(
13
+ createEnvironment({
14
+ context: 'browser',
15
+ engines: {
16
+ browsers: ['last 1 Chrome version', 'last 1 ChromeAndroid version'],
17
+ },
18
+ outputFormat: 'esmodule',
19
+ }),
20
+ ),
18
21
  DEFAULT_OPTIONS,
19
22
  );
20
23
 
@@ -16,10 +16,7 @@ import {DEFAULT_OPTIONS} from './test-utils';
16
16
  import {FILE_CREATE, FILE_UPDATE, INITIAL_BUILD} from '../src/constants';
17
17
  import {makeDeferredWithPromise} from '@atlaspack/utils';
18
18
  import {toProjectPath} from '../src/projectPath';
19
- import {
20
- DEFAULT_FEATURE_FLAGS,
21
- setFeatureFlags,
22
- } from '../../../unified/src/feature-flags/index.js';
19
+ import {DEFAULT_FEATURE_FLAGS, setFeatureFlags} from '../../feature-flags/src';
23
20
  import sinon from 'sinon';
24
21
  import type {AtlaspackOptions} from '../src/types';
25
22
 
@@ -0,0 +1,108 @@
1
+ // @flow strict-local
2
+
3
+ import sinon from 'sinon';
4
+ import {makeConfigProxy} from '../../src/public/Config';
5
+ import assert from 'assert';
6
+
7
+ describe('makeConfigProxy', () => {
8
+ it('tracks reads to nested fields', () => {
9
+ const onRead = sinon.spy();
10
+ const target = {a: {b: {c: 'd'}}};
11
+ const config = makeConfigProxy(onRead, target);
12
+ config.a.b.c;
13
+ assert.ok(onRead.calledWith(['a', 'b', 'c']));
14
+ assert.ok(onRead.calledOnce);
15
+ });
16
+
17
+ it('works for reading package.json dependencies', () => {
18
+ const packageJson = {
19
+ dependencies: {
20
+ react: '18.2.0',
21
+ },
22
+ };
23
+
24
+ const onRead = sinon.spy();
25
+ const config = makeConfigProxy(onRead, packageJson);
26
+ assert.equal(config.dependencies.react, '18.2.0');
27
+ // $FlowFixMe
28
+ assert.equal(config.dependencies.preact, undefined);
29
+ assert.ok(onRead.calledWith(['dependencies', 'react']));
30
+ assert.ok(onRead.calledWith(['dependencies', 'preact']));
31
+ assert.equal(onRead.callCount, 2);
32
+ });
33
+
34
+ it('will track reads for any missing or null keys', () => {
35
+ const packageJson = {
36
+ dependencies: {
37
+ react: '18.2.0',
38
+ },
39
+ };
40
+
41
+ const onRead = sinon.spy();
42
+ const config = makeConfigProxy(onRead, packageJson);
43
+
44
+ // $FlowFixMe
45
+ assert.equal(config.alias?.react, undefined);
46
+ assert.ok(onRead.calledWith(['alias']));
47
+ assert.equal(onRead.callCount, 1);
48
+ });
49
+
50
+ it('iterating over keys works normally and will register a read for the key being enumerated', () => {
51
+ const packageJson = {
52
+ nested: {
53
+ dependencies: {
54
+ react: '18.2.0',
55
+ 'react-dom': '18.2.0',
56
+ 'react-router': '6.14.2',
57
+ },
58
+ },
59
+ };
60
+
61
+ const onRead = sinon.spy();
62
+ const config = makeConfigProxy(onRead, packageJson);
63
+ assert.equal(Object.keys(config.nested.dependencies).length, 3);
64
+
65
+ assert.ok(onRead.calledWith(['nested', 'dependencies']));
66
+ });
67
+
68
+ it('if a key has an array value we will track a read for that key', () => {
69
+ const packageJson = {
70
+ scripts: ['build', 'test'],
71
+ };
72
+
73
+ const onRead = sinon.spy();
74
+ const config = makeConfigProxy(onRead, packageJson);
75
+ assert.equal(config.scripts[0], 'build');
76
+ assert.equal(onRead.callCount, 1);
77
+ assert.ok(onRead.calledWith(['scripts']));
78
+ });
79
+
80
+ it('if a key array value is iterated over we will track a read for that key', () => {
81
+ const packageJson = {
82
+ scripts: ['build', 'test'],
83
+ };
84
+
85
+ const onRead = sinon.spy();
86
+ const config = makeConfigProxy(onRead, packageJson);
87
+ let scriptCount = 0;
88
+ // eslint-disable-next-line no-unused-vars
89
+ for (const _script of config.scripts) {
90
+ scriptCount += 1;
91
+ }
92
+ assert.equal(scriptCount, 2);
93
+ assert.ok(onRead.calledWith(['scripts']));
94
+ assert.equal(onRead.callCount, 1);
95
+ });
96
+
97
+ it('if a key array value length is verified we will track a read for that key', () => {
98
+ const packageJson = {
99
+ scripts: ['build', 'test'],
100
+ };
101
+
102
+ const onRead = sinon.spy();
103
+ const config = makeConfigProxy(onRead, packageJson);
104
+ assert.equal(config.scripts.length, 2);
105
+ assert.ok(onRead.calledWith(['scripts']));
106
+ assert.equal(onRead.callCount, 1);
107
+ });
108
+ });
@@ -12,7 +12,10 @@ import type {
12
12
  ConfigRequestResult,
13
13
  } from '../../src/requests/ConfigRequest';
14
14
  import type {RunAPI} from '../../src/RequestTracker';
15
- import {runConfigRequest} from '../../src/requests/ConfigRequest';
15
+ import {
16
+ getValueAtPath,
17
+ runConfigRequest,
18
+ } from '../../src/requests/ConfigRequest';
16
19
  import {toProjectPath} from '../../src/projectPath';
17
20
 
18
21
  // $FlowFixMe unclear-type forgive me
@@ -228,7 +231,7 @@ describe('ConfigRequest tests', () => {
228
231
  ...baseRequest,
229
232
  invalidateOnConfigKeyChange: [
230
233
  {
231
- configKey: 'key1',
234
+ configKey: ['key1'],
232
235
  filePath: toProjectPath(projectRoot, 'config.json'),
233
236
  },
234
237
  ],
@@ -244,8 +247,189 @@ describe('ConfigRequest tests', () => {
244
247
  const call = mockCast(mockRunApi.invalidateOnConfigKeyChange).getCall(0);
245
248
  assert.deepEqual(
246
249
  call.args,
247
- ['config.json', 'key1', hashString('"value1"')],
250
+ ['config.json', ['key1'], hashString('"value1"')],
248
251
  'Invalidate was called for key1',
249
252
  );
250
253
  });
251
254
  });
255
+
256
+ describe('getValueAtPath', () => {
257
+ it('can get a key from an object', () => {
258
+ const obj = {a: {b: {c: 'd'}}};
259
+ assert.equal(getValueAtPath(obj, ['a', 'b', 'c']), 'd');
260
+ });
261
+
262
+ it('returns the original object when key array is empty', () => {
263
+ const obj = {a: 1, b: 2};
264
+ assert.deepEqual(getValueAtPath(obj, []), obj);
265
+ });
266
+
267
+ it('can access single-level properties', () => {
268
+ const obj = {name: 'test', age: 25};
269
+ assert.equal(getValueAtPath(obj, ['name']), 'test');
270
+ assert.equal(getValueAtPath(obj, ['age']), 25);
271
+ });
272
+
273
+ it('returns undefined for non-existent keys', () => {
274
+ const obj = {a: {b: 'value'}};
275
+ assert.equal(getValueAtPath(obj, ['nonexistent']), undefined);
276
+ assert.equal(getValueAtPath(obj, ['a', 'nonexistent']), undefined);
277
+ assert.equal(getValueAtPath(obj, ['a', 'b', 'nonexistent']), undefined);
278
+ });
279
+
280
+ it('handles null and undefined values in the path', () => {
281
+ const obj = {a: null, b: {c: undefined}};
282
+ assert.equal(getValueAtPath(obj, ['a']), null);
283
+ assert.equal(getValueAtPath(obj, ['b', 'c']), undefined);
284
+ });
285
+
286
+ it('does not throw when trying to access property of null', () => {
287
+ const obj = {a: null};
288
+ assert.equal(getValueAtPath(obj, ['a', 'b']), undefined);
289
+ });
290
+
291
+ it('does not throw when trying to access property of undefined', () => {
292
+ const obj = {a: undefined};
293
+ assert.equal(getValueAtPath(obj, ['a', 'b']), undefined);
294
+ });
295
+
296
+ it('can access nested arrays and objects', () => {
297
+ const obj = {
298
+ data: [
299
+ {name: 'item1', props: {color: 'red'}},
300
+ {name: 'item2', props: {color: 'blue'}},
301
+ ],
302
+ };
303
+ assert.equal(getValueAtPath(obj, ['data', '0', 'name']), 'item1');
304
+ assert.equal(getValueAtPath(obj, ['data', '1', 'props', 'color']), 'blue');
305
+ });
306
+
307
+ it('handles numeric keys as strings', () => {
308
+ const obj = {'0': 'first', '1': {nested: 'value'}};
309
+ assert.equal(getValueAtPath(obj, ['0']), 'first');
310
+ assert.equal(getValueAtPath(obj, ['1', 'nested']), 'value');
311
+ });
312
+
313
+ it('handles keys with special characters', () => {
314
+ const obj = {
315
+ 'key-with-dashes': 'value1',
316
+ 'key.with.dots': {
317
+ 'nested-key': 'value2',
318
+ },
319
+ 'key with spaces': 'value3',
320
+ '@special$chars#': 'value4',
321
+ };
322
+ assert.equal(getValueAtPath(obj, ['key-with-dashes']), 'value1');
323
+ assert.equal(
324
+ getValueAtPath(obj, ['key.with.dots', 'nested-key']),
325
+ 'value2',
326
+ );
327
+ assert.equal(getValueAtPath(obj, ['key with spaces']), 'value3');
328
+ assert.equal(getValueAtPath(obj, ['@special$chars#']), 'value4');
329
+ });
330
+
331
+ it('handles falsy values correctly', () => {
332
+ const obj = {
333
+ zero: 0,
334
+ false: false,
335
+ emptyString: '',
336
+ nullValue: null,
337
+ undefinedValue: undefined,
338
+ nested: {
339
+ zero: 0,
340
+ false: false,
341
+ },
342
+ };
343
+ assert.equal(getValueAtPath(obj, ['zero']), 0);
344
+ assert.equal(getValueAtPath(obj, ['false']), false);
345
+ assert.equal(getValueAtPath(obj, ['emptyString']), '');
346
+ assert.equal(getValueAtPath(obj, ['nullValue']), null);
347
+ assert.equal(getValueAtPath(obj, ['undefinedValue']), undefined);
348
+ assert.equal(getValueAtPath(obj, ['nested', 'zero']), 0);
349
+ assert.equal(getValueAtPath(obj, ['nested', 'false']), false);
350
+ });
351
+
352
+ it('handles deep nesting', () => {
353
+ const obj = {
354
+ level1: {
355
+ level2: {
356
+ level3: {
357
+ level4: {
358
+ level5: {
359
+ deepValue: 'found',
360
+ },
361
+ },
362
+ },
363
+ },
364
+ },
365
+ };
366
+ assert.equal(
367
+ getValueAtPath(obj, [
368
+ 'level1',
369
+ 'level2',
370
+ 'level3',
371
+ 'level4',
372
+ 'level5',
373
+ 'deepValue',
374
+ ]),
375
+ 'found',
376
+ );
377
+ });
378
+
379
+ it('handles Date objects', () => {
380
+ const date = new Date('2023-01-01');
381
+ const obj = {
382
+ timestamp: date,
383
+ nested: {
384
+ date: date,
385
+ },
386
+ };
387
+ assert.equal(getValueAtPath(obj, ['timestamp']), date);
388
+ assert.equal(getValueAtPath(obj, ['nested', 'date']), date);
389
+ });
390
+
391
+ it('handles complex nested structures with mixed types', () => {
392
+ const obj = {
393
+ users: [
394
+ {
395
+ id: 1,
396
+ profile: {
397
+ settings: {
398
+ theme: 'dark',
399
+ notifications: true,
400
+ },
401
+ },
402
+ },
403
+ {
404
+ id: 2,
405
+ profile: {
406
+ settings: {
407
+ theme: 'light',
408
+ notifications: false,
409
+ },
410
+ },
411
+ },
412
+ ],
413
+ config: {
414
+ version: '1.0.0',
415
+ features: ['feature1', 'feature2'],
416
+ },
417
+ };
418
+
419
+ assert.equal(
420
+ getValueAtPath(obj, ['users', '0', 'profile', 'settings', 'theme']),
421
+ 'dark',
422
+ );
423
+ assert.equal(
424
+ getValueAtPath(obj, [
425
+ 'users',
426
+ '1',
427
+ 'profile',
428
+ 'settings',
429
+ 'notifications',
430
+ ]),
431
+ false,
432
+ );
433
+ assert.equal(getValueAtPath(obj, ['config', 'features', '0']), 'feature1');
434
+ });
435
+ });
@@ -1,6 +1,6 @@
1
1
  // @flow strict-local
2
2
 
3
- import type {Environment, AtlaspackOptions, Target} from '../src/types';
3
+ import type {AtlaspackOptions, Target} from '../src/types';
4
4
 
5
5
  import {DEFAULT_FEATURE_FLAGS} from '@atlaspack/feature-flags';
6
6
  import {inputFS, outputFS, cache, cacheDir} from '@atlaspack/test-utils';
@@ -8,6 +8,7 @@ import {relativePath} from '@atlaspack/utils';
8
8
  import {NodePackageManager} from '@atlaspack/package-manager';
9
9
  import {createEnvironment} from '../src/Environment';
10
10
  import {toProjectPath} from '../src/projectPath';
11
+ import type {EnvironmentRef} from '../src/EnvironmentManager';
11
12
 
12
13
  export const DEFAULT_OPTIONS: AtlaspackOptions = {
13
14
  cacheDir,
@@ -51,7 +52,7 @@ export const DEFAULT_OPTIONS: AtlaspackOptions = {
51
52
  },
52
53
  };
53
54
 
54
- export const DEFAULT_ENV: Environment = createEnvironment({
55
+ export const DEFAULT_ENV: EnvironmentRef = createEnvironment({
55
56
  context: 'browser',
56
57
  engines: {
57
58
  browsers: ['> 1%'],