Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use combineOptions in chipper #1523

Open
samreid opened this issue Nov 9, 2024 · 23 comments
Open

Use combineOptions in chipper #1523

samreid opened this issue Nov 9, 2024 · 23 comments
Assignees

Comments

@samreid
Copy link
Member

samreid commented Nov 9, 2024

From #1520. When I tried using combineOptions, a lot of trouble came up. It was triggering usage in perennial. I need to take a closer look.

@samreid samreid self-assigned this Nov 9, 2024
samreid added a commit that referenced this issue Nov 9, 2024
@samreid
Copy link
Member Author

samreid commented Nov 10, 2024

Here's a patch with the trouble I ran into:

Subject: [PATCH] Improve types, see https://github.com/phetsims/chipper/issues/1482
---
Index: js/common/transpile.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/transpile.ts b/js/common/transpile.ts
--- a/js/common/transpile.ts	(revision 9c26f15299a93e2bdcd04d49ff8594ed682c2a5f)
+++ b/js/common/transpile.ts	(date 1731204916100)
@@ -9,6 +9,7 @@
 import { Repo } from '../../../perennial-alias/js/common/PerennialTypes.js';
 import getOption, { isOptionKeyProvided } from '../../../perennial-alias/js/grunt/tasks/util/getOption.js';
 import getRepo from '../../../perennial-alias/js/grunt/tasks/util/getRepo.js';
+import { combineOptions } from '../../../phet-core/js/optionize.js';
 
 export type TranspileOptions = {
 
@@ -39,7 +40,7 @@
   const start = Date.now();
 
   // TODO: use combineOptions, see https://github.com/phetsims/chipper/issues/1523
-  const options = _.assignIn( {
+  const options = combineOptions<TranspileOptions>( {
     all: false,
     silent: false,
     clean: false,
Index: js/common/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/tsconfig.json b/js/common/tsconfig.json
--- a/js/common/tsconfig.json	(revision 9c26f15299a93e2bdcd04d49ff8594ed682c2a5f)
+++ b/js/common/tsconfig.json	(date 1731205044730)
@@ -1,5 +1,8 @@
 {
   "extends": "../../tsconfig-chipper-node.json",
+  "include": [
+    "../../../phet-core/js/optionize.ts"
+  ],
   // this reference needs to be duplicated with the super config, because references to not extend
   "references": [
     {

I've tried a few things and am feeling stuck, would be good to consult with @zepumph

@samreid samreid assigned zepumph and unassigned samreid Nov 10, 2024
@zepumph
Copy link
Member

zepumph commented Nov 13, 2024

Nice work! This reminds me that we both decided once that perennial/tsconfig-chipper.json needs to go into chipper (obviously). Let's do that and this issue will get quite a bit easier I think.

@zepumph zepumph assigned samreid and unassigned zepumph Nov 13, 2024
@zepumph
Copy link
Member

zepumph commented Nov 19, 2024

After these tsconfigs are in chipper, this will be much easier.

@samreid
Copy link
Member Author

samreid commented Nov 22, 2024

tsconfig moved with git history. I could use @zepumph help sorting out the different chipper tsconfigs.

@samreid
Copy link
Member Author

samreid commented Nov 22, 2024

This patch gets further:

Subject: [PATCH] Improve types, see https://github.com/phetsims/chipper/issues/1482
---
Index: tsconfig-chipper.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tsconfig-chipper.json b/tsconfig-chipper.json
--- a/tsconfig-chipper.json	(revision eda6586cd649bab20b2853a66d59b282fa315b94)
+++ b/tsconfig-chipper.json	(date 1732299333031)
@@ -15,6 +15,9 @@
     "../perennial-alias/js/phet-types-module.d.ts",
     // TODO: should we split buildjson into a subsection that can be used by this one too (just phet-core?)   // TODO: split buildjson into a subsection that can be used by this one too (just phet-core?)
     "../phet-core/js/types/**/*",
+    "../phet-core/js/optionize.ts",
+    "../phet-core/js/merge.ts",
+    "../phet-core/js/phetCore.ts",
     "../tandem/js/phet-io-types.ts"
   ]
 }
\ No newline at end of file
Index: js/common/transpile.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/transpile.ts b/js/common/transpile.ts
--- a/js/common/transpile.ts	(revision eda6586cd649bab20b2853a66d59b282fa315b94)
+++ b/js/common/transpile.ts	(date 1732299086690)
@@ -10,6 +10,7 @@
 import { Repo } from '../../../perennial-alias/js/common/PerennialTypes.js';
 import getOption, { isOptionKeyProvided } from '../../../perennial-alias/js/grunt/tasks/util/getOption.js';
 import getRepo from '../../../perennial-alias/js/grunt/tasks/util/getRepo.js';
+import { combineOptions } from '../../../phet-core/js/optionize.js';
 
 // @ts-expect-error - until we have "type": "module" in our package.json
 const __dirname = dirname( import.meta.url );
@@ -41,8 +42,7 @@
 export default async function transpile( providedOptions: Partial<TranspileOptions> ): Promise<void> {
   const start = Date.now();
 
-  // TODO: use combineOptions, see https://github.com/phetsims/chipper/issues/1523
-  const options = _.assignIn( {
+  const options = combineOptions<TranspileOptions>( {
     all: false,
     silent: false,
     clean: false,

grunt check in chipper yields:

Running "check" task
(Forwarding task to perennial-alias)
Running "check" task
../phet-core/js/merge.ts:31:3 - error TS2304: Cannot find name 'assert'.

31   assert && assertIsMergeable( target );
     ~~~~~~

../phet-core/js/merge.ts:32:3 - error TS2304: Cannot find name 'assert'.

32   assert && assert( target !== null, 'target should not be null' ); // assertIsMergeable supports null
     ~~~~~~

../phet-core/js/merge.ts:32:13 - error TS2304: Cannot find name 'assert'.

32   assert && assert( target !== null, 'target should not be null' ); // assertIsMergeable supports null
               ~~~~~~

../phet-core/js/merge.ts:33:3 - error TS2304: Cannot find name 'assert'.

33   assert && assert( sources.length > 0, 'at least one source expected' );
     ~~~~~~

../phet-core/js/merge.ts:33:13 - error TS2304: Cannot find name 'assert'.

We had a similar problem in SimVersion. I feel the solution is to convert assert into a module instead of a preload. We could also add a workaround like in SimVersion (has its own assert). @zepumph what do you advise?

UPDATE: It's safe to import phet-core/types because they don't use assertions.

@zepumph
Copy link
Member

zepumph commented Nov 23, 2024

We were working on phetsims/assert#5 before getting too far on this one.

@zepumph
Copy link
Member

zepumph commented Dec 2, 2024

Also On hold until phetsims/perennial#418

@zepumph
Copy link
Member

zepumph commented Dec 2, 2024

  • Once we finish up the pattern in Create browser-and-node config perennial#418, we should most likely apply it to all of phet-core. If phet-core is linted like browser-and-node, then we don't need to worry about future proofing things. This will likely mean only using affirm instead of assert.

@samreid samreid assigned zepumph and unassigned samreid Dec 3, 2024
@samreid
Copy link
Member Author

samreid commented Dec 3, 2024

@zepumph is working on the browser-and-node config in phetsims/perennial#418 and will consider how to proceed with phet-core. Thanks!

samreid added a commit to phetsims/gravity-and-orbits that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/phet-core that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/density that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/pendulum-lab that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/scenery that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/expression-exchange that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/fractions-common that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/function-builder that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/counting-common that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/states-of-matter that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/isotopes-and-atomic-mass that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/circuit-construction-kit-common that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/balancing-chemical-equations that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/gene-expression-essentials that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/phet-lib that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/scenery-phet that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/proportion-playground that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/area-model-common that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/trig-tour that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/bending-light that referenced this issue Dec 17, 2024
samreid added a commit to phetsims/faradays-law that referenced this issue Dec 17, 2024
@samreid
Copy link
Member Author

samreid commented Dec 17, 2024

Current progress:

Subject: [PATCH] Move MipmapElement to chipper, see https://github.com/phetsims/chipper/issues/1523
---
Index: phet-core/js/tsconfig.json
===================================================================
diff --git a/phet-core/js/tsconfig.json b/phet-core/js/tsconfig.json
deleted file mode 100644
--- a/phet-core/js/tsconfig.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ /dev/null	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
@@ -1,8 +0,0 @@
-{
-  "extends": "../tsconfig-browser-and-node.json",
-  "references": [
-    {
-      "path": "../../perennial-alias/js/browser-and-node/tsconfig.json"
-    }
-  ]
-}
Index: phet-core/js/assertHasPropertiesTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasPropertiesTests.ts b/phet-core/js/assertHasPropertiesTests.ts
--- a/phet-core/js/assertHasPropertiesTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertHasPropertiesTests.ts	(date 1734453804021)
@@ -7,13 +7,14 @@
  */
 
 import assertHasProperties from './assertHasProperties.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'assertHasProperties' );
 
 QUnit.test( 'assertHasProperties', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     class MyObject {
 
Index: perennial-alias/js/browser-and-node/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/browser-and-node/tsconfig.json b/perennial-alias/js/browser-and-node/tsconfig.json
--- a/perennial-alias/js/browser-and-node/tsconfig.json	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/js/browser-and-node/tsconfig.json	(date 1734457179712)
@@ -1,3 +1,9 @@
 {
+  "compilerOptions": {
+    "allowSyntheticDefaultImports": true
+  },
+  "include": [
+    "**/*"
+  ],
   "extends": "../../tsconfig/tsconfig-browser-and-node.json"
 }
\ No newline at end of file
Index: phet-core/js/Poolable.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Poolable.ts b/phet-core/js/Poolable.ts
--- a/phet-core/js/Poolable.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Poolable.ts	(date 1734452746562)
@@ -10,6 +10,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import extend from './extend.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
@@ -72,8 +73,8 @@
       useDefaultConstruction: false
     }, providedOptions ) as Required<PoolableOptions<Type>>;
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     // The actual array we store things in. Always push/pop.
     const pool: InstanceType<Type>[] = [];
@@ -142,7 +143,7 @@
        * Sets the maximum pool size.
        */
       set maxPoolSize( value: number ) {
-        assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+        affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
         maxPoolSize = value;
       },
Index: phet-core/js/EnumerationValue.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationValue.ts b/phet-core/js/EnumerationValue.ts
--- a/phet-core/js/EnumerationValue.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationValue.ts	(date 1734452746581)
@@ -17,7 +17,7 @@
  * // Usage
  * console.log( MyEnumeration.VALUE_1 );
  * const printValue = enumValue => {
- *   assert && assert( enumValue.enumeration.values.includes(enumValue));
+ *   affirm( enumValue.enumeration.values.includes(enumValue));
  *   console.log( enumValue );
  * };
  * printValue( MyEnumeration.VALUE_2 );
@@ -26,6 +26,7 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import Enumeration from './Enumeration.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -50,29 +51,29 @@
 
   public constructor() {
     const c = this.constructor as Constructor<EnumerationValue>;
-    assert && assert( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
+    affirm( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
 
     this._name = null;
     this._enumeration = null;
   }
 
   public set name( name: string ) {
-    assert && assert( !this._name, 'name cannot be changed once defined.' );
+    affirm( !this._name, 'name cannot be changed once defined.' );
     this._name = name;
   }
 
   public get name(): string {
-    assert && assert( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
     return this._name!;
   }
 
   public set enumeration( enumeration: Enumeration<this> ) {
-    assert && assert( !this._enumeration, 'enumeration cannot be changed once defined.' );
+    affirm( !this._enumeration, 'enumeration cannot be changed once defined.' );
     this._enumeration = enumeration;
   }
 
   public get enumeration(): Enumeration<this> {
-    assert && assert( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
     return this._enumeration!;
   }
 }
Index: phet-core/js/partition.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/partition.ts b/phet-core/js/partition.ts
--- a/phet-core/js/partition.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/partition.ts	(date 1734452746589)
@@ -9,10 +9,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function partition<T>( array: T[], predicate: ( element: T ) => boolean ): readonly[ T[], T[] ] {
-  assert && assert( Array.isArray( array ) );
+  affirm( Array.isArray( array ) );
 
   const satisfied = [];
   const unsatisfied = [];
Index: phet-core/js/cleanArray.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/cleanArray.ts b/phet-core/js/cleanArray.ts
--- a/phet-core/js/cleanArray.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/cleanArray.ts	(date 1734452746559)
@@ -7,10 +7,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function cleanArray<T>( arr?: T[] | null | undefined ): T[] {
-  assert && assert( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
+  affirm( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
 
   if ( arr ) {
     // fastest way to clear an array (http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript, http://jsperf.com/array-destroy/32)
Index: perennial-alias/js/npm-dependencies/lodash.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/npm-dependencies/lodash.js b/perennial-alias/js/npm-dependencies/lodashModule.ts
rename from perennial-alias/js/npm-dependencies/lodash.js
rename to perennial-alias/js/npm-dependencies/lodashModule.ts
--- a/perennial-alias/js/npm-dependencies/lodash.js	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/js/npm-dependencies/lodashModule.ts	(date 1734457778169)
@@ -5,6 +5,9 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
-import lodash from 'lodash';
+import test from 'lodash';
 
+type t = typeof test;
+
+const lodash = test as t;
 export default lodash;
\ No newline at end of file
Index: phet-core/js/mutate.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mutate.ts b/phet-core/js/mutate.ts
--- a/phet-core/js/mutate.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/mutate.ts	(date 1734457041937)
@@ -6,7 +6,9 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 /**
  * For example:
@@ -21,21 +23,21 @@
  * First param will be mutated
  */
 function mutate( target: object, orderedKeys: string[], options?: object ): void {
-  assert && assert( target );
-  assert && assert( Array.isArray( orderedKeys ) );
+  affirm( target );
+  affirm( Array.isArray( orderedKeys ) );
 
   if ( !options ) {
     return;
   }
 
-  assert && assert( Object.getPrototypeOf( options ) === Object.prototype,
+  affirm( Object.getPrototypeOf( options ) === Object.prototype,
     'Extra prototype on options object is a code smell' );
 
   _.each( orderedKeys, key => {
 
     // See https://github.com/phetsims/scenery/issues/580 for more about passing undefined.
     // @ts-expect-error
-    assert && assert( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
+    affirm( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
       `Undefined not allowed for key: ${key}` );
 
     // @ts-expect-error
Index: aqua/js/server/QuickServer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/js/server/QuickServer.ts b/aqua/js/server/QuickServer.ts
--- a/aqua/js/server/QuickServer.ts	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/js/server/QuickServer.ts	(date 1734457778184)
@@ -24,11 +24,12 @@
 import npmUpdate from '../../../perennial/js/common/npmUpdate.js';
 import puppeteerLoad from '../../../perennial/js/common/puppeteerLoad.js';
 import withServer from '../../../perennial/js/common/withServer.js';
-import _ from '../../../perennial/js/npm-dependencies/lodash.js';
+import _ from '../../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import puppeteer from '../../../perennial/js/npm-dependencies/puppeteer.js';
 import winston from '../../../perennial/js/npm-dependencies/winston.js';
 import sendSlackMessage from './sendSlackMessage.js';
 
+_.assignIn()
 
 type TestData = {
   passed: boolean;
Index: aqua/js/server/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/js/server/tsconfig.json b/aqua/js/server/tsconfig.json
--- a/aqua/js/server/tsconfig.json	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/js/server/tsconfig.json	(date 1734457675736)
@@ -2,6 +2,9 @@
   "extends": "../../tsconfig-aqua-node.json",
   "references": [
     {
+      "path": "../../../perennial-alias/tsconfig.json"
+    },
+    {
       "path": "../../../perennial/tsconfig.json"
     }
   ]
Index: phet-core/js/extendDefined.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extendDefined.ts b/phet-core/js/extendDefined.ts
--- a/phet-core/js/extendDefined.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/extendDefined.ts	(date 1734457041942)
@@ -11,6 +11,7 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 function extendDefined<T>( obj: T, ...sources: Array<T | undefined> ): T {
   _.each( sources, source => {
Index: phet-core/js/Pool.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Pool.ts b/phet-core/js/Pool.ts
--- a/phet-core/js/Pool.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Pool.ts	(date 1734452746520)
@@ -22,6 +22,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -87,8 +88,8 @@
       useDefaultConstruction: false
     }, providedOptionsSpread[ 0 ] );
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     this._maxPoolSize = options.maxSize;
 
@@ -103,7 +104,7 @@
     this.DefaultConstructor = this.partialConstructor( ...options.defaultArguments! ); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
 
     this.initialize = options.initialize;
-    assert && assert( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
+    affirm( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
 
     this.useDefaultConstruction = options.useDefaultConstruction;
 
@@ -157,7 +158,7 @@
    * Sets the maximum pool size.
    */
   public set maxPoolSize( value: number ) {
-    assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+    affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
     this._maxPoolSize = value;
   }
Index: phet-core/js/interleave.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/interleave.ts b/phet-core/js/interleave.ts
--- a/phet-core/js/interleave.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/interleave.ts	(date 1734452746585)
@@ -8,6 +8,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -17,7 +18,7 @@
  * @returns - The interleaved array
  */
 function interleave<T>( arr: readonly T[], generator: ( element: number ) => T ): T[] {
-  assert && assert( Array.isArray( arr ) );
+  affirm( Array.isArray( arr ) );
 
   const result = [];
   const finalLength = arr.length * 2 - 1;
Index: phet-core/js/EventTimer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EventTimer.ts b/phet-core/js/EventTimer.ts
--- a/phet-core/js/EventTimer.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EventTimer.ts	(date 1734452746551)
@@ -75,6 +75,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 export default class EventTimer {
@@ -132,7 +133,7 @@
    * Event model that will fire events at a constant rate. An event will occur every 1/rate time units.
    */
   public constructor( private readonly rate: number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
@@ -149,12 +150,12 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
@@ -169,7 +170,7 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0,
+    affirm( rate > 0,
       'We need to have a strictly positive poisson rate in order to prevent infinite loops.' );
   }
 
@@ -181,7 +182,7 @@
     // http://en.wikipedia.org/wiki/Poisson_process
 
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
Index: phet-core/tsconfig-browser-and-node.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-browser-and-node.json b/phet-core/tsconfig-tests.json
rename from phet-core/tsconfig-browser-and-node.json
rename to phet-core/tsconfig-tests.json
--- a/phet-core/tsconfig-browser-and-node.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/tsconfig-tests.json	(date 1734456831474)
@@ -1,15 +1,13 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
   "include": [
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts"
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
+    "js/phet-core-tests.ts"
   ],
   "references": [
     {
-      "path": "../perennial-alias/js/browser-and-node/tsconfig.json"
+      "path": "../perennial-alias/tsconfig/buildjson/tsconfig.json"
     }
   ]
 }
Index: phet-core/js/dimensionForEachTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionForEachTests.ts b/phet-core/js/dimensionForEachTests.ts
--- a/phet-core/js/dimensionForEachTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/dimensionForEachTests.ts	(date 1734457041920)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import dimensionForEach from './dimensionForEach.js';
 
 QUnit.module( 'dimensionForEach' );
Index: perennial-alias/package.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/package.json b/perennial-alias/package.json
--- a/perennial-alias/package.json	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/package.json	(date 1734453026755)
@@ -12,6 +12,9 @@
     "type-check-all": "grunt type-check --all --clean",
     "lint-all": "grunt lint --all --clean"
   },
+  "dependencies": {
+    "lodash": "~4.17.10"
+  },
   "devDependencies": {
     "@octokit/rest": "~16.33.1",
     "@stylistic/eslint-plugin": "~2.11.0",
@@ -44,7 +47,6 @@
     "graceful-fs": "~4.1.11",
     "grunt": "~1.5.3",
     "html-differ": "~1.4.0",
-    "lodash": "~4.17.10",
     "mimelib": "~0.2.19",
     "minimist": "~1.2.8",
     "ncp": "~2.0.0",
Index: phet-core/js/isPhetioEnabled.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/isPhetioEnabled.ts b/phet-core/js/isPhetioEnabled.ts
--- a/phet-core/js/isPhetioEnabled.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/isPhetioEnabled.ts	(date 1734457041951)
@@ -1,5 +1,7 @@
 // Copyright 2024, University of Colorado Boulder
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
+
 /**
  * @author Sam Reid (PhET Interactive Simulations)
  */
Index: projectile-data-lab/js/common/view/PDLScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/projectile-data-lab/js/common/view/PDLScreenView.ts b/projectile-data-lab/js/common/view/PDLScreenView.ts
--- a/projectile-data-lab/js/common/view/PDLScreenView.ts	(revision 45b5f5de0c43fd3d51e15c25a583e459b57fae2a)
+++ b/projectile-data-lab/js/common/view/PDLScreenView.ts	(date 1734456502376)
@@ -201,6 +201,8 @@
       tandem: singleOrContinuousRadioButtonGroupTandem,
       phetioFeatured: true,
       radioButtonOptions: {
+
+        // TODO: https://github.com/phetsims/sun/issues/920 Remove these options. Customization by removing individual radio buttons is outside the scope of providing value-added instrumentation.
         phetioVisiblePropertyInstrumented: false,
         phetioEnabledPropertyInstrumented: false,
         radius: 8
Index: phet-core/js/qunitStartWithoutPhetio.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/qunitStartWithoutPhetio.ts b/phet-core/js/qunitStartWithoutPhetio.ts
--- a/phet-core/js/qunitStartWithoutPhetio.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/qunitStartWithoutPhetio.ts	(date 1734457041957)
@@ -7,6 +7,7 @@
  */
 
 import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 affirm( QUnit, 'QUnit global needed to start QUnit' );
 
Index: chipper/js/browser-and-node/tsconfig-dependencies.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/chipper/js/browser-and-node/tsconfig-dependencies.json b/chipper/js/browser-and-node/tsconfig-dependencies.json
--- a/chipper/js/browser-and-node/tsconfig-dependencies.json	(revision dd302e2dfbe14734bf900750868793eab4cb025d)
+++ b/chipper/js/browser-and-node/tsconfig-dependencies.json	(date 1734452539779)
@@ -8,7 +8,7 @@
       "path": "../../../perennial-alias/js/browser-and-node/tsconfig.json"
     },
     {
-      "path": "../../../phet-core/tsconfig-browser-and-node.json"
+      "path": "../../../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: phet-core/js/arrayRemove.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayRemove.ts b/phet-core/js/arrayRemove.ts
--- a/phet-core/js/arrayRemove.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayRemove.ts	(date 1734457041959)
@@ -6,13 +6,15 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import phetCore from './phetCore.js';
 
 function arrayRemove<T>( array: T[], toRemove: T ): void {
-  assert && assert( Array.isArray( array ), 'arrayRemove takes an Array' );
+  affirm( Array.isArray( array ), 'arrayRemove takes an Array' );
 
   const index = _.indexOf( array, toRemove );
-  assert && assert( index >= 0, 'item not found in Array' );
+  affirm( index >= 0, 'item not found in Array' );
 
   array.splice( index, 1 );
 }
Index: phet-core/tsconfig-module.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-module.json b/phet-core/tsconfig-module.json
--- a/phet-core/tsconfig-module.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/tsconfig-module.json	(date 1734457778196)
@@ -1,27 +1,18 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
+  "compilerOptions": {
+  },
   "include": [
-    "js/**/*",
-    "images/**/*",
-    "mipmaps/**/*",
-    "sounds/**/*"
+    "js/**/*"
   ],
   "exclude": [
-    // Browser-and-node
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts",
-    // Tests
-    "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
+    "js/phet-core-tests.ts"
   ],
   "references": [
     {
-      "path": "./tsconfig-browser-and-node.json"
+      "path": "../perennial-alias/tsconfig.json"
     }
   ]
 }
Index: phet-core/js/assertHasProperties.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasProperties.ts b/phet-core/js/assertHasProperties.ts
--- a/phet-core/js/assertHasProperties.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertHasProperties.ts	(date 1734457041932)
@@ -14,17 +14,19 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import inheritance from './inheritance.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
 
 const assertHasProperties = ( object: IntentionalAny, properties: string[] ): void => {
-  if ( assert && object ) {
+  if ( isAffirmEnabled() && object ) {
 
 
     properties.forEach( property => {
 
-      assert && assert( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
+      affirm( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
 
                         // test up the class hierarchy for if the property is defined on a prototype.
                         _.some( inheritance( object.constructor ).map( type => Object.getOwnPropertyDescriptor( type.prototype, property ) ) ),
Index: phet-core/js/arrayDifferenceTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifferenceTests.ts b/phet-core/js/arrayDifferenceTests.ts
--- a/phet-core/js/arrayDifferenceTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayDifferenceTests.ts	(date 1734457041939)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import arrayDifference from './arrayDifference.js';
 import arrayRemove from './arrayRemove.js';
 
Index: aqua/js/grunt/tasks/continuous-server.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/js/grunt/tasks/continuous-server.ts b/aqua/js/grunt/tasks/continuous-server.ts
--- a/aqua/js/grunt/tasks/continuous-server.ts	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/js/grunt/tasks/continuous-server.ts	(date 1734457041962)
@@ -12,7 +12,7 @@
 
 import assert from 'assert';
 import getOption from '../../../../perennial/js/grunt/tasks/util/getOption.js';
-import _ from '../../../../perennial/js/npm-dependencies/lodash.js';
+import _ from '../../../../perennial/js/npm-dependencies/lodashModule.js';
 import winston from '../../../../perennial/js/npm-dependencies/winston.js';
 import ContinuousServer from '../../server/ContinuousServer.js';
 
Index: phet-core/js/assertMutuallyExclusiveOptions.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptions.ts b/phet-core/js/assertMutuallyExclusiveOptions.ts
--- a/phet-core/js/assertMutuallyExclusiveOptions.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertMutuallyExclusiveOptions.ts	(date 1734457041929)
@@ -12,6 +12,8 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -20,7 +22,7 @@
  * @param sets - families of mutually exclusive option keys, see examples above.
  */
 const assertMutuallyExclusiveOptions = function( options: object | null | undefined, ...sets: string[][] ): void {
-  if ( assert && options ) {
+  if ( isAffirmEnabled() && options ) {
 
     // Determine which options are used from each set
     const usedElementsFromEachSet = sets.map( set => Object.keys( _.pick( options, ...set ) ) );
@@ -29,7 +31,7 @@
     if ( usedElementsFromEachSet.filter( usedElements => usedElements.length > 0 ).length > 1 ) {
 
       // Output the errant options.
-      assert && assert( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
+      affirm( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
     }
   }
 };
Index: phet-core/js/getGlobal.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/getGlobal.ts b/phet-core/js/getGlobal.ts
--- a/phet-core/js/getGlobal.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/getGlobal.ts	(date 1734457041948)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 /**
  * If the path exists on the window global, return it, otherwise returns null
  * @param path a path to global, such as 'phet.joist.sim'
  */
 const getGlobal = ( path: string ): IntentionalAny | null => {
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const global = _.get( window, path );
   return global !== undefined ? global : null;
 };
Index: phet-core/js/assertMutuallyExclusiveOptionsTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts
--- a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(date 1734453804002)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 import assertMutuallyExclusiveOptions from './assertMutuallyExclusiveOptions.js';
 
 QUnit.module( 'assertMutuallyExclusiveOptions' );
@@ -13,7 +14,7 @@
 QUnit.test( 'assertMutuallyExclusiveOptions', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // Should not throw error because options are all from one set.
     assertMutuallyExclusiveOptions( { a: true, b: false }, [ 'a', 'b' ], [ 'c' ] );
Index: chipper/js/phet-io/generatePhetioMacroAPI.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/chipper/js/phet-io/generatePhetioMacroAPI.ts b/chipper/js/phet-io/generatePhetioMacroAPI.ts
--- a/chipper/js/phet-io/generatePhetioMacroAPI.ts	(revision dd302e2dfbe14734bf900750868793eab4cb025d)
+++ b/chipper/js/phet-io/generatePhetioMacroAPI.ts	(date 1734457041968)
@@ -11,7 +11,7 @@
 import assert from 'assert';
 import callbackOnWorkers from '../../../perennial-alias/js/common/callbackOnWorkers.js';
 import withServer from '../../../perennial-alias/js/common/withServer.js';
-import _ from '../../../perennial-alias/js/npm-dependencies/lodash.js';
+import _ from '../../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import puppeteer from '../../../perennial-alias/js/npm-dependencies/puppeteer.js';
 import { PhetioAPI } from '../../../tandem/js/phet-io-types.js';
 import showCommandLineProgress from '../common/showCommandLineProgress.js';
Index: phet-core/js/dimensionMapTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionMapTests.ts b/phet-core/js/dimensionMapTests.ts
--- a/phet-core/js/dimensionMapTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/dimensionMapTests.ts	(date 1734457041974)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 import dimensionMap from './dimensionMap.js';
 
 QUnit.module( 'dimensionMap' );
Index: phet-core/js/mergeTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mergeTests.ts b/phet-core/js/mergeTests.ts
--- a/phet-core/js/mergeTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/mergeTests.ts	(date 1734457041955)
@@ -4,6 +4,8 @@
 
 import merge from './merge.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'merge' );
 
@@ -215,7 +217,7 @@
     }
   };
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
     assert.throws( () => merge( original, merges.a ), 'merge should not allow arrays to be merged' );
     assert.throws( () => merge( original, merges.b ), 'merge should not allow inherited objects to be merged' );
     assert.throws( () => merge( original, merges.f ), 'merge should not allow instances to be merged' );
@@ -372,7 +374,7 @@
 } );
 
 QUnit.test( 'test wrong args', assert => {
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // in first arg
     assert.throws( () => merge( undefined, {} ), 'unsupported first arg "undefined"' );
Index: phet-core/js/required.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/required.ts b/phet-core/js/required.ts
--- a/phet-core/js/required.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/required.ts	(date 1734452746570)
@@ -6,13 +6,14 @@
  * @author Denzell Barnett (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
  * Checks if the value passed is defined
  */
 function required<T>( entry: T ): T {
-  assert && assert( entry !== undefined, 'Required field is undefined.' );
+  affirm( entry !== undefined, 'Required field is undefined.' );
   return entry;
 }
 
Index: phet-core/js/EnumerationMap.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationMap.ts b/phet-core/js/EnumerationMap.ts
--- a/phet-core/js/EnumerationMap.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationMap.ts	(date 1734452746567)
@@ -7,6 +7,7 @@
  */
 
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 type TEnumeration<T> = {
   enumeration: {
@@ -31,7 +32,7 @@
 
     this._values = enumeration.enumeration.values;
     this._values.forEach( entry => {
-      assert && assert( !this._map.has( entry ), 'Enumeration key override problem' );
+      affirm( !this._map.has( entry ), 'Enumeration key override problem' );
       this._map.set( entry, factory( entry ) );
     } );
   }
@@ -40,8 +41,8 @@
    * Returns the value associated with the given enumeration entry.
    */
   public get( entry: T ): U {
-    assert && assert( this._values.includes( entry ) );
-    assert && assert( this._map.has( entry ) );
+    affirm( this._values.includes( entry ) );
+    affirm( this._map.has( entry ) );
     return this._map.get( entry )!;
   }
 
@@ -49,7 +50,7 @@
    * Sets the value associated with the given enumeration entry.
    */
   public set( entry: T, value: U ): void {
-    assert && assert( this._values.includes( entry ) );
+    affirm( this._values.includes( entry ) );
     this._map.set( entry, value );
   }
 
Index: phet-core/js/gracefulBind.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/gracefulBind.ts b/phet-core/js/gracefulBind.ts
--- a/phet-core/js/gracefulBind.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/gracefulBind.ts	(date 1734457041926)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 /**
  * If the path exists on the window global, return it as a bound function, otherwise returns null
  * @param path a path to a method, dot-separated, including the method, such as 'phet.joist.sim.showPopup'
  */
 const gracefulBind = ( path: string ): null | VoidFunction => {
-  assert && assert( path.split( '.' ).length > 1, 'path must have multiple parts' );
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.split( '.' ).length > 1, 'path must have multiple parts' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const terms = path.split( '.' );
   const method = terms.pop()!; // mutates terms to become the method container
   const object = _.get( window, terms );
Index: phet-core/js/arrayDifference.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifference.ts b/phet-core/js/arrayDifference.ts
--- a/phet-core/js/arrayDifference.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayDifference.ts	(date 1734457041931)
@@ -6,7 +6,14 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
+
+_.test;
+_.uniq;
+_.assignIn;
+
 
 /**
  * Given two arrays, find the items that are only in one of them (mutates the aOnly/bOnly/inBoth parameters)
@@ -35,16 +42,16 @@
  * @returns - Returns the value of aOnly (the classic definition of difference)
  */
 function arrayDifference<T>( a: T[], b: T[], aOnly?: T[], bOnly?: T[], inBoth?: T[] ): T[] {
-  assert && assert( Array.isArray( a ) && _.uniq( a ).length === a.length, 'a is not an array of unique items' );
-  assert && assert( Array.isArray( b ) && _.uniq( b ).length === b.length, 'b is not an array of unique items' );
+  affirm( Array.isArray( a ) && _.uniq( a ).length === a.length, 'a is not an array of unique items' );
+  affirm( Array.isArray( b ) && _.uniq( b ).length === b.length, 'b is not an array of unique items' );
 
   aOnly = aOnly || [];
   bOnly = bOnly || [];
   inBoth = inBoth || [];
 
-  assert && assert( Array.isArray( aOnly ) && aOnly.length === 0 );
-  assert && assert( Array.isArray( bOnly ) && bOnly.length === 0 );
-  assert && assert( Array.isArray( inBoth ) && inBoth.length === 0 );
+  affirm( Array.isArray( aOnly ) && aOnly.length === 0 );
+  affirm( Array.isArray( bOnly ) && bOnly.length === 0 );
+  affirm( Array.isArray( inBoth ) && inBoth.length === 0 );
 
   Array.prototype.push.apply( aOnly, a );
   Array.prototype.push.apply( bOnly, b );
Index: perennial-alias/tsconfig/tsconfig-browser-and-node.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/tsconfig/tsconfig-browser-and-node.json b/perennial-alias/tsconfig/tsconfig-browser-and-node.json
--- a/perennial-alias/tsconfig/tsconfig-browser-and-node.json	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/tsconfig/tsconfig-browser-and-node.json	(date 1734457101358)
@@ -4,5 +4,13 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 {
+  "compilerOptions": {
+//    "types": [
+//      "lodash"
+//    ]
+  },
+  "files": [
+    "../js/npm-dependencies/lodashModule.ts"
+  ],
   "extends": "./tsconfig-core.json"
 }
\ No newline at end of file
Index: aqua/tsconfig-aqua-node.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/tsconfig-aqua-node.json b/aqua/tsconfig-aqua-node.json
--- a/aqua/tsconfig-aqua-node.json	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/tsconfig-aqua-node.json	(date 1734452539788)
@@ -12,7 +12,7 @@
       "path": "../perennial/tsconfig.json"
     },
     {
-      "path": "../phet-core/tsconfig-browser-and-node.json"
+      "path": "../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: phet-core/js/EnumerationDeprecated.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationDeprecated.js b/phet-core/js/EnumerationDeprecated.js
--- a/phet-core/js/EnumerationDeprecated.js	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationDeprecated.js	(date 1734457041965)
@@ -52,7 +52,7 @@
  *
  *     // @param {Scene} mode - value from Scene EnumerationDeprecated
  *     setSceneMode( mode ) {
- *       assert && assert( Scene.includes( mode ) );
+ *       affirm( Scene.includes( mode ) );
  *       //...
  *     }
  *
@@ -62,6 +62,8 @@
 import deprecationWarning from './deprecationWarning.js';
 import merge from './merge.js';
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
 
 /**
  * @deprecated
@@ -77,11 +79,11 @@
   constructor( config ) {
     deprecationWarning( 'EnumerationDeprecated should be exchanged for classes that extend EnumerationValue, see WilderEnumerationPatterns for examples.' );
 
-    assert && assert( config, 'config must be provided' );
+    affirm( config, 'config must be provided' );
 
     const keysProvided = !!config.keys;
     const mapProvided = !!config.map;
-    assert && assert( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
+    affirm( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
 
     const keys = config.keys || Object.keys( config.map );
     const map = config.map || {};
@@ -99,16 +101,16 @@
       beforeFreeze: null
     }, config );
 
-    assert && assert( Array.isArray( keys ), 'Values should be an array' );
-    assert && assert( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
+    affirm( Array.isArray( keys ), 'Values should be an array' );
+    affirm( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
     assert && keys.forEach( value => assert( typeof value === 'string', 'Each value should be a string' ) );
     assert && keys.forEach( value => assert( /^[A-Z][A-Z0-9_]*$/g.test( value ),
       'EnumerationDeprecated values should be uppercase alphanumeric with underscores and begin with a letter' ) );
-    assert && assert( !_.includes( keys, 'VALUES' ),
+    affirm( !_.includes( keys, 'VALUES' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'KEYS' ),
+    affirm( !_.includes( keys, 'KEYS' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'includes' ),
+    affirm( !_.includes( keys, 'includes' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
 
     // @public (phet-io) - provides additional documentation for PhET-iO which can be viewed in studio
@@ -125,8 +127,8 @@
       const value = map[ key ] || {};
 
       // Set attributes of the enumeration value
-      assert && assert( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
-      assert && assert( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
+      affirm( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
+      affirm( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
 
       // @public {string} (read-only) - PhET-iO public API relies on this mapping, do not change it lightly
       value.name = key;
@@ -223,8 +225,8 @@
    * @public
    */
   static byKeys( keys, options ) {
-    assert && assert( Array.isArray( keys ), 'keys must be an array' );
-    assert && assert( !options || options.keys === undefined );
+    affirm( Array.isArray( keys ), 'keys must be an array' );
+    affirm( !options || options.keys === undefined );
     return new EnumerationDeprecated( merge( { keys: keys }, options ) );
   }
 
@@ -236,11 +238,11 @@
    * @public
    */
   static byMap( map, options ) {
-    assert && assert( !options || options.map === undefined );
+    affirm( !options || options.map === undefined );
     if ( assert ) {
       const values = _.values( map );
-      assert && assert( values.length >= 1, 'must have at least 2 entries in an enumeration' );
-      assert && assert( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
+      affirm( values.length >= 1, 'must have at least 2 entries in an enumeration' );
+      affirm( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
     }
     return new EnumerationDeprecated( merge( { map: map }, options ) );
   }
Index: phet-core/js/Enumeration.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Enumeration.ts b/phet-core/js/Enumeration.ts
--- a/phet-core/js/Enumeration.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Enumeration.ts	(date 1734457903958)
@@ -25,12 +25,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import EnumerationValue from './EnumerationValue.js';
 import inheritance from './inheritance.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
 import TEnumeration from './TEnumeration.js';
 import Constructor from './types/Constructor.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
+import IntentionalAny from './types/IntentionalAny.js';
+
+_.assignIn();
 
 export type EnumerationOptions<T extends EnumerationValue> = {
   phetioDocumentation?: string;
@@ -61,7 +66,7 @@
     // values appear after previously existing enumeration values
     const types = _.reverse( inheritance( Enumeration ) );
 
-    assert && assert( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
+    affirm( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
 
     this.keys = [];
     this.values = [];
@@ -69,7 +74,7 @@
       Object.keys( type ).forEach( key => {
         const value = type[ key ];
         if ( value instanceof instanceType ) {
-          assert && assert( key === key.toUpperCase(), 'keys should be upper case by convention' );
+          affirm( key === key.toUpperCase(), 'keys should be upper case by convention' );
           this.keys.push( key );
           this.values.push( value );
 
@@ -83,8 +88,8 @@
       } );
     } );
 
-    assert && assert( this.keys.length > 0, 'no keys found' );
-    assert && assert( this.values.length > 0, 'no values found' );
+    affirm( this.keys.length > 0, 'no keys found' );
+    affirm( this.values.length > 0, 'no values found' );
 
     this.Enumeration = Enumeration as Constructor<T> & Record<string, T>;
     EnumerationValue.sealedCache.add( Enumeration );
Index: phet-core/js/asyncLoader.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/asyncLoader.ts b/phet-core/js/asyncLoader.ts
--- a/phet-core/js/asyncLoader.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/asyncLoader.ts	(date 1734452746599)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import arrayRemove from '../../phet-core/js/arrayRemove.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
@@ -50,7 +51,7 @@
    */
   private proceedIfReady(): void {
     if ( this.pendingLocks.length === 0 ) {
-      assert && assert( !this.loadComplete, 'cannot complete load twice' );
+      affirm( !this.loadComplete, 'cannot complete load twice' );
       this.loadComplete = true;
 
       this.listeners.forEach( listener => listener() );
@@ -61,10 +62,10 @@
    * Creates a lock, which is a callback that needs to be run before we can proceed.
    */
   public createLock( object?: IntentionalAny ): AsyncLoaderLock {
-    assert && assert( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
+    affirm( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
     this.pendingLocks.push( object );
     return () => {
-      assert && assert( this.pendingLocks.includes( object ), 'invalid lock' );
+      affirm( this.pendingLocks.includes( object ), 'invalid lock' );
       arrayRemove( this.pendingLocks, object );
       this.proceedIfReady();
     };
Index: phet-core/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig.json b/phet-core/tsconfig.json
--- a/phet-core/tsconfig.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/tsconfig.json	(date 1734453658556)
@@ -1,14 +1,11 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
-  "include": [
-    "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
-  ],
+  "files": [],
   "references": [
     {
       "path": "./tsconfig-module.json"
+    },
+    {
+      "path": "./tsconfig-tests.json"
     }
   ]
 }
Index: phet-core/js/extend.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extend.ts b/phet-core/js/extend.ts
--- a/phet-core/js/extend.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/extend.ts	(date 1734457970231)
@@ -11,6 +11,9 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodashModule.js';
+
+_.assignIn();
 
 function extend<T>( obj: T, ...sources: Array<object | undefined> ): T {
   _.each( sources, source => {

samreid added a commit to phetsims/phet-core that referenced this issue Dec 17, 2024
@zepumph
Copy link
Member

zepumph commented Dec 17, 2024

Current progress. We have a meeting tomorrow with @jonathanolson to discuss if this is every going to work with our transpiler/build process.

Subject: [PATCH] Move MipmapElement to chipper, see https://github.com/phetsims/chipper/issues/1523
---
Index: phet-core/js/tsconfig.json
===================================================================
diff --git a/phet-core/js/tsconfig.json b/phet-core/js/tsconfig.json
deleted file mode 100644
--- a/phet-core/js/tsconfig.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ /dev/null	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
@@ -1,8 +0,0 @@
-{
-  "extends": "../tsconfig-browser-and-node.json",
-  "references": [
-    {
-      "path": "../../perennial-alias/js/browser-and-node/tsconfig.json"
-    }
-  ]
-}
Index: phet-core/tsconfig-browser-and-node.json
===================================================================
diff --git a/phet-core/tsconfig-browser-and-node.json b/phet-core/tsconfig-browser-and-node.json
deleted file mode 100644
--- a/phet-core/tsconfig-browser-and-node.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ /dev/null	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
@@ -1,15 +0,0 @@
-{
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
-  "include": [
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts"
-  ],
-  "references": [
-    {
-      "path": "../perennial-alias/js/browser-and-node/tsconfig.json"
-    }
-  ]
-}
Index: phet-core/tsconfig-module.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-module.json b/phet-core/tsconfig-module.json
--- a/phet-core/tsconfig-module.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/tsconfig-module.json	(date 1734459040974)
@@ -1,27 +1,22 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
+  "compilerOptions": {
+  },
   "include": [
-    "js/**/*",
-    "images/**/*",
-    "mipmaps/**/*",
-    "sounds/**/*"
+    "js/**/*"
   ],
   "exclude": [
-    // Browser-and-node
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts",
-    // Tests
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
     "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
+    "js/qunitStartWithoutPhetio.ts"
   ],
   "references": [
     {
-      "path": "./tsconfig-browser-and-node.json"
+      "path": "../perennial-alias/tsconfig.json"
+    },
+    {
+      "path": "../perennial-alias/js/npm-dependencies/tsconfig-lodash.json"
     }
   ]
 }
Index: phet-core/js/EventTimer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EventTimer.ts b/phet-core/js/EventTimer.ts
--- a/phet-core/js/EventTimer.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EventTimer.ts	(date 1734458115984)
@@ -75,6 +75,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 export default class EventTimer {
@@ -132,7 +133,7 @@
    * Event model that will fire events at a constant rate. An event will occur every 1/rate time units.
    */
   public constructor( private readonly rate: number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
@@ -149,12 +150,12 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
@@ -169,7 +170,7 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0,
+    affirm( rate > 0,
       'We need to have a strictly positive poisson rate in order to prevent infinite loops.' );
   }
 
@@ -181,7 +182,7 @@
     // http://en.wikipedia.org/wiki/Poisson_process
 
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
Index: perennial-alias/js/browser-and-node/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/browser-and-node/tsconfig.json b/perennial-alias/js/browser-and-node/tsconfig.json
--- a/perennial-alias/js/browser-and-node/tsconfig.json	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/js/browser-and-node/tsconfig.json	(date 1734459207667)
@@ -1,3 +1,11 @@
 {
-  "extends": "../../tsconfig/tsconfig-browser-and-node.json"
+  "include": [
+    "**/*"
+  ],
+  "extends": "../../tsconfig/tsconfig-browser-and-node.json",
+  "references": [
+    {
+      "path": "../npm-dependencies/tsconfig-lodash.json"
+    }
+  ]
 }
\ No newline at end of file
Index: phet-core/js/isPhetioEnabled.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/isPhetioEnabled.ts b/phet-core/js/isPhetioEnabled.ts
--- a/phet-core/js/isPhetioEnabled.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/isPhetioEnabled.ts	(date 1734458673276)
@@ -1,5 +1,7 @@
 // Copyright 2024, University of Colorado Boulder
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
+
 /**
  * @author Sam Reid (PhET Interactive Simulations)
  */
Index: phet-core/js/mutate.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mutate.ts b/phet-core/js/mutate.ts
--- a/phet-core/js/mutate.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/mutate.ts	(date 1734464499711)
@@ -6,7 +6,9 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash-global.js';
 
 /**
  * For example:
@@ -21,21 +23,21 @@
  * First param will be mutated
  */
 function mutate( target: object, orderedKeys: string[], options?: object ): void {
-  assert && assert( target );
-  assert && assert( Array.isArray( orderedKeys ) );
+  affirm( target );
+  affirm( Array.isArray( orderedKeys ) );
 
   if ( !options ) {
     return;
   }
 
-  assert && assert( Object.getPrototypeOf( options ) === Object.prototype,
+  affirm( Object.getPrototypeOf( options ) === Object.prototype,
     'Extra prototype on options object is a code smell' );
 
   _.each( orderedKeys, key => {
 
     // See https://github.com/phetsims/scenery/issues/580 for more about passing undefined.
     // @ts-expect-error
-    assert && assert( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
+    affirm( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
       `Undefined not allowed for key: ${key}` );
 
     // @ts-expect-error
Index: phet-core/js/qunitStartWithoutPhetio.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/qunitStartWithoutPhetio.ts b/phet-core/js/qunitStartWithoutPhetio.ts
--- a/phet-core/js/qunitStartWithoutPhetio.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/qunitStartWithoutPhetio.ts	(date 1734458673290)
@@ -7,6 +7,7 @@
  */
 
 import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 
 affirm( QUnit, 'QUnit global needed to start QUnit' );
 
Index: phet-core/js/EnumerationValue.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationValue.ts b/phet-core/js/EnumerationValue.ts
--- a/phet-core/js/EnumerationValue.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationValue.ts	(date 1734458115980)
@@ -17,7 +17,7 @@
  * // Usage
  * console.log( MyEnumeration.VALUE_1 );
  * const printValue = enumValue => {
- *   assert && assert( enumValue.enumeration.values.includes(enumValue));
+ *   affirm( enumValue.enumeration.values.includes(enumValue));
  *   console.log( enumValue );
  * };
  * printValue( MyEnumeration.VALUE_2 );
@@ -26,6 +26,7 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import Enumeration from './Enumeration.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -50,29 +51,29 @@
 
   public constructor() {
     const c = this.constructor as Constructor<EnumerationValue>;
-    assert && assert( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
+    affirm( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
 
     this._name = null;
     this._enumeration = null;
   }
 
   public set name( name: string ) {
-    assert && assert( !this._name, 'name cannot be changed once defined.' );
+    affirm( !this._name, 'name cannot be changed once defined.' );
     this._name = name;
   }
 
   public get name(): string {
-    assert && assert( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
     return this._name!;
   }
 
   public set enumeration( enumeration: Enumeration<this> ) {
-    assert && assert( !this._enumeration, 'enumeration cannot be changed once defined.' );
+    affirm( !this._enumeration, 'enumeration cannot be changed once defined.' );
     this._enumeration = enumeration;
   }
 
   public get enumeration(): Enumeration<this> {
-    assert && assert( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
     return this._enumeration!;
   }
 }
Index: phet-core/js/assertHasPropertiesTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasPropertiesTests.ts b/phet-core/js/assertHasPropertiesTests.ts
--- a/phet-core/js/assertHasPropertiesTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertHasPropertiesTests.ts	(date 1734458115929)
@@ -7,13 +7,14 @@
  */
 
 import assertHasProperties from './assertHasProperties.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'assertHasProperties' );
 
 QUnit.test( 'assertHasProperties', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     class MyObject {
 
Index: phet-core/js/Poolable.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Poolable.ts b/phet-core/js/Poolable.ts
--- a/phet-core/js/Poolable.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Poolable.ts	(date 1734458116040)
@@ -10,6 +10,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import extend from './extend.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
@@ -72,8 +73,8 @@
       useDefaultConstruction: false
     }, providedOptions ) as Required<PoolableOptions<Type>>;
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     // The actual array we store things in. Always push/pop.
     const pool: InstanceType<Type>[] = [];
@@ -142,7 +143,7 @@
        * Sets the maximum pool size.
        */
       set maxPoolSize( value: number ) {
-        assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+        affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
         maxPoolSize = value;
       },
Index: phet-core/js/assertHasProperties.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasProperties.ts b/phet-core/js/assertHasProperties.ts
--- a/phet-core/js/assertHasProperties.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertHasProperties.ts	(date 1734458673235)
@@ -14,17 +14,19 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import inheritance from './inheritance.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
 
 const assertHasProperties = ( object: IntentionalAny, properties: string[] ): void => {
-  if ( assert && object ) {
+  if ( isAffirmEnabled() && object ) {
 
 
     properties.forEach( property => {
 
-      assert && assert( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
+      affirm( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
 
                         // test up the class hierarchy for if the property is defined on a prototype.
                         _.some( inheritance( object.constructor ).map( type => Object.getOwnPropertyDescriptor( type.prototype, property ) ) ),
Index: phet-core/js/extendDefined.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extendDefined.ts b/phet-core/js/extendDefined.ts
--- a/phet-core/js/extendDefined.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/extendDefined.ts	(date 1734458673280)
@@ -11,6 +11,7 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 
 function extendDefined<T>( obj: T, ...sources: Array<T | undefined> ): T {
   _.each( sources, source => {
Index: phet-core/js/interleave.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/interleave.ts b/phet-core/js/interleave.ts
--- a/phet-core/js/interleave.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/interleave.ts	(date 1734458116006)
@@ -8,6 +8,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -17,7 +18,7 @@
  * @returns - The interleaved array
  */
 function interleave<T>( arr: readonly T[], generator: ( element: number ) => T ): T[] {
-  assert && assert( Array.isArray( arr ) );
+  affirm( Array.isArray( arr ) );
 
   const result = [];
   const finalLength = arr.length * 2 - 1;
Index: phet-core/js/Enumeration.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Enumeration.ts b/phet-core/js/Enumeration.ts
--- a/phet-core/js/Enumeration.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Enumeration.ts	(date 1734458643983)
@@ -25,6 +25,8 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import EnumerationValue from './EnumerationValue.js';
 import inheritance from './inheritance.js';
 import optionize from './optionize.js';
@@ -32,6 +34,8 @@
 import TEnumeration from './TEnumeration.js';
 import Constructor from './types/Constructor.js';
 
+_.assignIn();
+
 export type EnumerationOptions<T extends EnumerationValue> = {
   phetioDocumentation?: string;
   instanceType?: Constructor<T>;
@@ -61,7 +65,7 @@
     // values appear after previously existing enumeration values
     const types = _.reverse( inheritance( Enumeration ) );
 
-    assert && assert( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
+    affirm( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
 
     this.keys = [];
     this.values = [];
@@ -69,7 +73,7 @@
       Object.keys( type ).forEach( key => {
         const value = type[ key ];
         if ( value instanceof instanceType ) {
-          assert && assert( key === key.toUpperCase(), 'keys should be upper case by convention' );
+          affirm( key === key.toUpperCase(), 'keys should be upper case by convention' );
           this.keys.push( key );
           this.values.push( value );
 
@@ -83,8 +87,8 @@
       } );
     } );
 
-    assert && assert( this.keys.length > 0, 'no keys found' );
-    assert && assert( this.values.length > 0, 'no values found' );
+    affirm( this.keys.length > 0, 'no keys found' );
+    affirm( this.values.length > 0, 'no values found' );
 
     this.Enumeration = Enumeration as Constructor<T> & Record<string, T>;
     EnumerationValue.sealedCache.add( Enumeration );
Index: phet-core/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig.json b/phet-core/tsconfig.json
--- a/phet-core/tsconfig.json	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/tsconfig.json	(date 1734458116057)
@@ -1,14 +1,11 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
-  "include": [
-    "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
-  ],
+  "files": [],
   "references": [
     {
       "path": "./tsconfig-module.json"
+    },
+    {
+      "path": "./tsconfig-tests.json"
     }
   ]
 }
Index: phet-core/js/Pool.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Pool.ts b/phet-core/js/Pool.ts
--- a/phet-core/js/Pool.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/Pool.ts	(date 1734458116031)
@@ -22,6 +22,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -87,8 +88,8 @@
       useDefaultConstruction: false
     }, providedOptionsSpread[ 0 ] );
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     this._maxPoolSize = options.maxSize;
 
@@ -103,7 +104,7 @@
     this.DefaultConstructor = this.partialConstructor( ...options.defaultArguments! ); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
 
     this.initialize = options.initialize;
-    assert && assert( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
+    affirm( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
 
     this.useDefaultConstruction = options.useDefaultConstruction;
 
@@ -157,7 +158,7 @@
    * Sets the maximum pool size.
    */
   public set maxPoolSize( value: number ) {
-    assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+    affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
     this._maxPoolSize = value;
   }
Index: phet-core/js/extend.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extend.ts b/phet-core/js/extend.ts
--- a/phet-core/js/extend.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/extend.ts	(date 1734458673255)
@@ -11,6 +11,9 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
+
+_.assignIn();
 
 function extend<T>( obj: T, ...sources: Array<object | undefined> ): T {
   _.each( sources, source => {
Index: perennial-alias/js/npm-dependencies/lodash.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/npm-dependencies/lodash.js b/perennial-alias/js/npm-dependencies/lodash-global.ts
rename from perennial-alias/js/npm-dependencies/lodash.js
rename to perennial-alias/js/npm-dependencies/lodash-global.ts
--- a/perennial-alias/js/npm-dependencies/lodash.js	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/js/npm-dependencies/lodash-global.ts	(date 1734464659242)
@@ -5,6 +5,9 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
-import lodash from 'lodash';
+// TODO ????????
 
-export default lodash;
\ No newline at end of file
+import type * as lodash from 'lodash';
+
+// @ts-expect-error - Please hold, this will be better soon.
+export default window._ as lodash; // eslint-disable-line
\ No newline at end of file
Index: phet-core/js/arrayDifference.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifference.ts b/phet-core/js/arrayDifference.ts
--- a/phet-core/js/arrayDifference.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayDifference.ts	(date 1734458673258)
@@ -6,7 +6,14 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
+
+_.test;
+_.uniq;
+_.assignIn;
+
 
 /**
  * Given two arrays, find the items that are only in one of them (mutates the aOnly/bOnly/inBoth parameters)
@@ -35,16 +42,16 @@
  * @returns - Returns the value of aOnly (the classic definition of difference)
  */
 function arrayDifference<T>( a: T[], b: T[], aOnly?: T[], bOnly?: T[], inBoth?: T[] ): T[] {
-  assert && assert( Array.isArray( a ) && _.uniq( a ).length === a.length, 'a is not an array of unique items' );
-  assert && assert( Array.isArray( b ) && _.uniq( b ).length === b.length, 'b is not an array of unique items' );
+  affirm( Array.isArray( a ) && _.uniq( a ).length === a.length, 'a is not an array of unique items' );
+  affirm( Array.isArray( b ) && _.uniq( b ).length === b.length, 'b is not an array of unique items' );
 
   aOnly = aOnly || [];
   bOnly = bOnly || [];
   inBoth = inBoth || [];
 
-  assert && assert( Array.isArray( aOnly ) && aOnly.length === 0 );
-  assert && assert( Array.isArray( bOnly ) && bOnly.length === 0 );
-  assert && assert( Array.isArray( inBoth ) && inBoth.length === 0 );
+  affirm( Array.isArray( aOnly ) && aOnly.length === 0 );
+  affirm( Array.isArray( bOnly ) && bOnly.length === 0 );
+  affirm( Array.isArray( inBoth ) && inBoth.length === 0 );
 
   Array.prototype.push.apply( aOnly, a );
   Array.prototype.push.apply( bOnly, b );
Index: phet-core/js/gracefulBind.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/gracefulBind.ts b/phet-core/js/gracefulBind.ts
--- a/phet-core/js/gracefulBind.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/gracefulBind.ts	(date 1734458673267)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 
 /**
  * If the path exists on the window global, return it as a bound function, otherwise returns null
  * @param path a path to a method, dot-separated, including the method, such as 'phet.joist.sim.showPopup'
  */
 const gracefulBind = ( path: string ): null | VoidFunction => {
-  assert && assert( path.split( '.' ).length > 1, 'path must have multiple parts' );
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.split( '.' ).length > 1, 'path must have multiple parts' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const terms = path.split( '.' );
   const method = terms.pop()!; // mutates terms to become the method container
   const object = _.get( window, terms );
Index: phet-core/js/partition.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/partition.ts b/phet-core/js/partition.ts
--- a/phet-core/js/partition.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/partition.ts	(date 1734458116024)
@@ -9,10 +9,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function partition<T>( array: T[], predicate: ( element: T ) => boolean ): readonly[ T[], T[] ] {
-  assert && assert( Array.isArray( array ) );
+  affirm( Array.isArray( array ) );
 
   const satisfied = [];
   const unsatisfied = [];
Index: phet-core/js/EnumerationMap.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationMap.ts b/phet-core/js/EnumerationMap.ts
--- a/phet-core/js/EnumerationMap.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationMap.ts	(date 1734458115975)
@@ -7,6 +7,7 @@
  */
 
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 type TEnumeration<T> = {
   enumeration: {
@@ -31,7 +32,7 @@
 
     this._values = enumeration.enumeration.values;
     this._values.forEach( entry => {
-      assert && assert( !this._map.has( entry ), 'Enumeration key override problem' );
+      affirm( !this._map.has( entry ), 'Enumeration key override problem' );
       this._map.set( entry, factory( entry ) );
     } );
   }
@@ -40,8 +41,8 @@
    * Returns the value associated with the given enumeration entry.
    */
   public get( entry: T ): U {
-    assert && assert( this._values.includes( entry ) );
-    assert && assert( this._map.has( entry ) );
+    affirm( this._values.includes( entry ) );
+    affirm( this._map.has( entry ) );
     return this._map.get( entry )!;
   }
 
@@ -49,7 +50,7 @@
    * Sets the value associated with the given enumeration entry.
    */
   public set( entry: T, value: U ): void {
-    assert && assert( this._values.includes( entry ) );
+    affirm( this._values.includes( entry ) );
     this._map.set( entry, value );
   }
 
Index: phet-core/js/EnumerationDeprecated.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationDeprecated.js b/phet-core/js/EnumerationDeprecated.js
--- a/phet-core/js/EnumerationDeprecated.js	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/EnumerationDeprecated.js	(date 1734458673283)
@@ -52,7 +52,7 @@
  *
  *     // @param {Scene} mode - value from Scene EnumerationDeprecated
  *     setSceneMode( mode ) {
- *       assert && assert( Scene.includes( mode ) );
+ *       affirm( Scene.includes( mode ) );
  *       //...
  *     }
  *
@@ -62,6 +62,8 @@
 import deprecationWarning from './deprecationWarning.js';
 import merge from './merge.js';
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 
 /**
  * @deprecated
@@ -77,11 +79,11 @@
   constructor( config ) {
     deprecationWarning( 'EnumerationDeprecated should be exchanged for classes that extend EnumerationValue, see WilderEnumerationPatterns for examples.' );
 
-    assert && assert( config, 'config must be provided' );
+    affirm( config, 'config must be provided' );
 
     const keysProvided = !!config.keys;
     const mapProvided = !!config.map;
-    assert && assert( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
+    affirm( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
 
     const keys = config.keys || Object.keys( config.map );
     const map = config.map || {};
@@ -99,16 +101,16 @@
       beforeFreeze: null
     }, config );
 
-    assert && assert( Array.isArray( keys ), 'Values should be an array' );
-    assert && assert( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
+    affirm( Array.isArray( keys ), 'Values should be an array' );
+    affirm( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
     assert && keys.forEach( value => assert( typeof value === 'string', 'Each value should be a string' ) );
     assert && keys.forEach( value => assert( /^[A-Z][A-Z0-9_]*$/g.test( value ),
       'EnumerationDeprecated values should be uppercase alphanumeric with underscores and begin with a letter' ) );
-    assert && assert( !_.includes( keys, 'VALUES' ),
+    affirm( !_.includes( keys, 'VALUES' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'KEYS' ),
+    affirm( !_.includes( keys, 'KEYS' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'includes' ),
+    affirm( !_.includes( keys, 'includes' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
 
     // @public (phet-io) - provides additional documentation for PhET-iO which can be viewed in studio
@@ -125,8 +127,8 @@
       const value = map[ key ] || {};
 
       // Set attributes of the enumeration value
-      assert && assert( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
-      assert && assert( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
+      affirm( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
+      affirm( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
 
       // @public {string} (read-only) - PhET-iO public API relies on this mapping, do not change it lightly
       value.name = key;
@@ -223,8 +225,8 @@
    * @public
    */
   static byKeys( keys, options ) {
-    assert && assert( Array.isArray( keys ), 'keys must be an array' );
-    assert && assert( !options || options.keys === undefined );
+    affirm( Array.isArray( keys ), 'keys must be an array' );
+    affirm( !options || options.keys === undefined );
     return new EnumerationDeprecated( merge( { keys: keys }, options ) );
   }
 
@@ -236,11 +238,11 @@
    * @public
    */
   static byMap( map, options ) {
-    assert && assert( !options || options.map === undefined );
+    affirm( !options || options.map === undefined );
     if ( assert ) {
       const values = _.values( map );
-      assert && assert( values.length >= 1, 'must have at least 2 entries in an enumeration' );
-      assert && assert( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
+      affirm( values.length >= 1, 'must have at least 2 entries in an enumeration' );
+      affirm( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
     }
     return new EnumerationDeprecated( merge( { map: map }, options ) );
   }
Index: phet-core/js/cleanArray.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/cleanArray.ts b/phet-core/js/cleanArray.ts
--- a/phet-core/js/cleanArray.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/cleanArray.ts	(date 1734458115948)
@@ -7,10 +7,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function cleanArray<T>( arr?: T[] | null | undefined ): T[] {
-  assert && assert( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
+  affirm( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
 
   if ( arr ) {
     // fastest way to clear an array (http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript, http://jsperf.com/array-destroy/32)
Index: phet-core/js/asyncLoader.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/asyncLoader.ts b/phet-core/js/asyncLoader.ts
--- a/phet-core/js/asyncLoader.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/asyncLoader.ts	(date 1734458115943)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import arrayRemove from '../../phet-core/js/arrayRemove.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
@@ -50,7 +51,7 @@
    */
   private proceedIfReady(): void {
     if ( this.pendingLocks.length === 0 ) {
-      assert && assert( !this.loadComplete, 'cannot complete load twice' );
+      affirm( !this.loadComplete, 'cannot complete load twice' );
       this.loadComplete = true;
 
       this.listeners.forEach( listener => listener() );
@@ -61,10 +62,10 @@
    * Creates a lock, which is a callback that needs to be run before we can proceed.
    */
   public createLock( object?: IntentionalAny ): AsyncLoaderLock {
-    assert && assert( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
+    affirm( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
     this.pendingLocks.push( object );
     return () => {
-      assert && assert( this.pendingLocks.includes( object ), 'invalid lock' );
+      affirm( this.pendingLocks.includes( object ), 'invalid lock' );
       arrayRemove( this.pendingLocks, object );
       this.proceedIfReady();
     };
Index: perennial-alias/package.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/package.json b/perennial-alias/package.json
--- a/perennial-alias/package.json	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/package.json	(date 1734461423794)
@@ -12,6 +12,11 @@
     "type-check-all": "grunt type-check --all --clean",
     "lint-all": "grunt lint --all --clean"
   },
+  "dependencies": {
+    "nodemailer": "^6.9.1",
+    "pug": "^3.0.2",
+    "lodash": "~4.17.10"
+  },
   "devDependencies": {
     "@octokit/rest": "~16.33.1",
     "@stylistic/eslint-plugin": "~2.11.0",
@@ -44,7 +49,6 @@
     "graceful-fs": "~4.1.11",
     "grunt": "~1.5.3",
     "html-differ": "~1.4.0",
-    "lodash": "~4.17.10",
     "mimelib": "~0.2.19",
     "minimist": "~1.2.8",
     "ncp": "~2.0.0",
@@ -61,9 +65,5 @@
     "winston": "~2.4.5",
     "winston-loggly": "~1.3.1",
     "xml2js": "~0.4.15"
-  },
-  "dependencies": {
-    "nodemailer": "^6.9.1",
-    "pug": "^3.0.2"
   }
 }
Index: chipper/js/browser-and-node/tsconfig-dependencies.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/chipper/js/browser-and-node/tsconfig-dependencies.json b/chipper/js/browser-and-node/tsconfig-dependencies.json
--- a/chipper/js/browser-and-node/tsconfig-dependencies.json	(revision dd302e2dfbe14734bf900750868793eab4cb025d)
+++ b/chipper/js/browser-and-node/tsconfig-dependencies.json	(date 1734458115849)
@@ -8,7 +8,7 @@
       "path": "../../../perennial-alias/js/browser-and-node/tsconfig.json"
     },
     {
-      "path": "../../../phet-core/tsconfig-browser-and-node.json"
+      "path": "../../../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: perennial-alias/js/browser-and-node/affirm.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/browser-and-node/affirm.ts b/perennial-alias/js/browser-and-node/affirm.ts
--- a/perennial-alias/js/browser-and-node/affirm.ts	(revision 24b1e276899fdbcddec7a3d02c7409af2e0da24d)
+++ b/perennial-alias/js/browser-and-node/affirm.ts	(date 1734459586702)
@@ -1,12 +1,14 @@
 // Copyright 2024, University of Colorado Boulder
 
 import { IntentionalPerennialAny } from './PerennialTypes.js';
-
+import _ from '../npm-dependencies/lodash.js';
 // Define an interface that includes the optional 'assert' property
 type GlobalWithAssert = {
   assert?: boolean;
 };
 
+_.assignIn();
+
 // Respect the global assert flag, which can be set to false to disable all assertions
 const isBrowser = globalThis.hasOwnProperty( 'window' );
 const isNode = !isBrowser;
Index: aqua/tsconfig-aqua-node.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/tsconfig-aqua-node.json b/aqua/tsconfig-aqua-node.json
--- a/aqua/tsconfig-aqua-node.json	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/tsconfig-aqua-node.json	(date 1734458115844)
@@ -12,7 +12,7 @@
       "path": "../perennial/tsconfig.json"
     },
     {
-      "path": "../phet-core/tsconfig-browser-and-node.json"
+      "path": "../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: phet-core/js/mergeTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mergeTests.ts b/phet-core/js/mergeTests.ts
--- a/phet-core/js/mergeTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/mergeTests.ts	(date 1734458673249)
@@ -4,6 +4,8 @@
 
 import merge from './merge.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'merge' );
 
@@ -215,7 +217,7 @@
     }
   };
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
     assert.throws( () => merge( original, merges.a ), 'merge should not allow arrays to be merged' );
     assert.throws( () => merge( original, merges.b ), 'merge should not allow inherited objects to be merged' );
     assert.throws( () => merge( original, merges.f ), 'merge should not allow instances to be merged' );
@@ -372,7 +374,7 @@
 } );
 
 QUnit.test( 'test wrong args', assert => {
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // in first arg
     assert.throws( () => merge( undefined, {} ), 'unsupported first arg "undefined"' );
Index: phet-core/js/dimensionMapTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionMapTests.ts b/phet-core/js/dimensionMapTests.ts
--- a/phet-core/js/dimensionMapTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/dimensionMapTests.ts	(date 1734458673264)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import dimensionMap from './dimensionMap.js';
 
 QUnit.module( 'dimensionMap' );
Index: phet-core/js/required.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/required.ts b/phet-core/js/required.ts
--- a/phet-core/js/required.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/required.ts	(date 1734458116050)
@@ -6,13 +6,14 @@
  * @author Denzell Barnett (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
  * Checks if the value passed is defined
  */
 function required<T>( entry: T ): T {
-  assert && assert( entry !== undefined, 'Required field is undefined.' );
+  affirm( entry !== undefined, 'Required field is undefined.' );
   return entry;
 }
 
Index: phet-core/js/assertMutuallyExclusiveOptionsTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts
--- a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(date 1734458115938)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 import assertMutuallyExclusiveOptions from './assertMutuallyExclusiveOptions.js';
 
 QUnit.module( 'assertMutuallyExclusiveOptions' );
@@ -13,7 +14,7 @@
 QUnit.test( 'assertMutuallyExclusiveOptions', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // Should not throw error because options are all from one set.
     assertMutuallyExclusiveOptions( { a: true, b: false }, [ 'a', 'b' ], [ 'c' ] );
Index: perennial-alias/js/npm-dependencies/tsconfig-lodash.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/npm-dependencies/tsconfig-lodash.json b/perennial-alias/js/npm-dependencies/tsconfig-lodash.json
new file mode 100644
--- /dev/null	(date 1734461410977)
+++ b/perennial-alias/js/npm-dependencies/tsconfig-lodash.json	(date 1734461410977)
@@ -0,0 +1,10 @@
+{
+  "extends": "../../tsconfig/tsconfig-core.json",
+  "compilerOptions": {
+    // TODO
+    "module": "commonjs"
+  },
+  "files": [
+    "./lodash.ts"
+  ]
+}
\ No newline at end of file
Index: phet-core/js/getGlobal.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/getGlobal.ts b/phet-core/js/getGlobal.ts
--- a/phet-core/js/getGlobal.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/getGlobal.ts	(date 1734458673239)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 
 /**
  * If the path exists on the window global, return it, otherwise returns null
  * @param path a path to global, such as 'phet.joist.sim'
  */
 const getGlobal = ( path: string ): IntentionalAny | null => {
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const global = _.get( window, path );
   return global !== undefined ? global : null;
 };
Index: phet-core/js/dimensionForEachTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionForEachTests.ts b/phet-core/js/dimensionForEachTests.ts
--- a/phet-core/js/dimensionForEachTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/dimensionForEachTests.ts	(date 1734458673261)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import dimensionForEach from './dimensionForEach.js';
 
 QUnit.module( 'dimensionForEach' );
Index: phet-core/tsconfig-tests.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-tests.json b/phet-core/tsconfig-tests.json
new file mode 100644
--- /dev/null	(date 1734459040969)
+++ b/phet-core/tsconfig-tests.json	(date 1734459040969)
@@ -0,0 +1,14 @@
+{
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
+  "include": [
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
+    "js/phet-core-tests.ts",
+    "js/qunitStartWithoutPhetio.ts"
+  ],
+  "references": [
+    {
+      "path": "../perennial-alias/tsconfig/buildjson/tsconfig.json"
+    }
+  ]
+}
Index: phet-core/js/arrayRemove.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayRemove.ts b/phet-core/js/arrayRemove.ts
--- a/phet-core/js/arrayRemove.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayRemove.ts	(date 1734458673270)
@@ -6,13 +6,15 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import phetCore from './phetCore.js';
 
 function arrayRemove<T>( array: T[], toRemove: T ): void {
-  assert && assert( Array.isArray( array ), 'arrayRemove takes an Array' );
+  affirm( Array.isArray( array ), 'arrayRemove takes an Array' );
 
   const index = _.indexOf( array, toRemove );
-  assert && assert( index >= 0, 'item not found in Array' );
+  affirm( index >= 0, 'item not found in Array' );
 
   array.splice( index, 1 );
 }
Index: phet-core/js/arrayDifferenceTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifferenceTests.ts b/phet-core/js/arrayDifferenceTests.ts
--- a/phet-core/js/arrayDifferenceTests.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/arrayDifferenceTests.ts	(date 1734458673243)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import arrayDifference from './arrayDifference.js';
 import arrayRemove from './arrayRemove.js';
 
Index: perennial-alias/js/npm-dependencies/lodash.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/npm-dependencies/lodash.ts b/perennial-alias/js/npm-dependencies/lodash.ts
new file mode 100644
--- /dev/null	(date 1734461410985)
+++ b/perennial-alias/js/npm-dependencies/lodash.ts	(date 1734461410985)
@@ -0,0 +1,10 @@
+// Copyright 2019, University of Colorado Boulder
+
+/**
+ * To be used when trying to import lodash from other repos. This way we don't propagate the devDependency: "@types/lodash"
+ * @author Michael Kauzmann (PhET Interactive Simulations)
+ */
+
+import * as lodash from 'lodash';
+
+export default lodash;
\ No newline at end of file
Index: phet-core/js/assertMutuallyExclusiveOptions.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptions.ts b/phet-core/js/assertMutuallyExclusiveOptions.ts
--- a/phet-core/js/assertMutuallyExclusiveOptions.ts	(revision c4d20331ba86007f58ece9c6ff8416f0c0ef76f3)
+++ b/phet-core/js/assertMutuallyExclusiveOptions.ts	(date 1734458673252)
@@ -12,6 +12,8 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../perennial-alias/js/npm-dependencies/lodash.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -20,7 +22,7 @@
  * @param sets - families of mutually exclusive option keys, see examples above.
  */
 const assertMutuallyExclusiveOptions = function( options: object | null | undefined, ...sets: string[][] ): void {
-  if ( assert && options ) {
+  if ( isAffirmEnabled() && options ) {
 
     // Determine which options are used from each set
     const usedElementsFromEachSet = sets.map( set => Object.keys( _.pick( options, ...set ) ) );
@@ -29,7 +31,7 @@
     if ( usedElementsFromEachSet.filter( usedElements => usedElements.length > 0 ).length > 1 ) {
 
       // Output the errant options.
-      assert && assert( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
+      affirm( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
     }
   }
 };

@zepumph
Copy link
Member

zepumph commented Dec 18, 2024

Another attempt for module based sherpa lib. It doesn't support type checking for the _ default import. It also doesn't work with swc just yet.

  1. cd perennial-alias; npm install --save-dev lodash-es
  2. `mv perennial-alias/node_modules/lodash-es sherpa/
  3. Apply this patch
Subject: [PATCH] Move MipmapElement to chipper, see https://github.com/phetsims/chipper/issues/1523
---
Index: phet-core/js/tsconfig.json
===================================================================
diff --git a/phet-core/js/tsconfig.json b/phet-core/js/tsconfig.json
deleted file mode 100644
--- a/phet-core/js/tsconfig.json	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ /dev/null	(revision e86e414f30cbc72142a797965118dfcc00e46618)
@@ -1,8 +0,0 @@
-{
-  "extends": "../tsconfig-browser-and-node.json",
-  "references": [
-    {
-      "path": "../../perennial-alias/js/browser-and-node/tsconfig.json"
-    }
-  ]
-}
Index: phet-core/tsconfig-browser-and-node.json
===================================================================
diff --git a/phet-core/tsconfig-browser-and-node.json b/phet-core/tsconfig-browser-and-node.json
deleted file mode 100644
--- a/phet-core/tsconfig-browser-and-node.json	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ /dev/null	(revision e86e414f30cbc72142a797965118dfcc00e46618)
@@ -1,15 +0,0 @@
-{
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
-  "include": [
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts"
-  ],
-  "references": [
-    {
-      "path": "../perennial-alias/js/browser-and-node/tsconfig.json"
-    }
-  ]
-}
Index: phet-core/tsconfig-module.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-module.json b/phet-core/tsconfig-module.json
--- a/phet-core/tsconfig-module.json	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/tsconfig-module.json	(date 1734561444643)
@@ -1,27 +1,25 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser-and-node.json",
+  "compilerOptions": {
+    "paths": {
+      "sherpa/lodash": [
+        "../sherpa/lodash-es/lodash.js"
+      ]
+    }
+  },
   "include": [
     "js/**/*",
-    "images/**/*",
-    "mipmaps/**/*",
-    "sounds/**/*"
+    "../sherpa/lodash-es/**/*"
   ],
   "exclude": [
-    // Browser-and-node
-    "js/types/**/*",
-    "js/optionize.ts",
-    "js/merge.ts",
-    "js/phetCore.ts",
-    "js/Namespace.ts",
-    // Tests
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
     "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
+    "js/qunitStartWithoutPhetio.ts"
   ],
   "references": [
     {
-      "path": "./tsconfig-browser-and-node.json"
+      "path": "../perennial-alias/js/browser-and-node/tsconfig.json"
     }
   ]
-}
+}
\ No newline at end of file
Index: phet-core/js/EventTimer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EventTimer.ts b/phet-core/js/EventTimer.ts
--- a/phet-core/js/EventTimer.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/EventTimer.ts	(date 1734556810753)
@@ -75,6 +75,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 export default class EventTimer {
@@ -132,7 +133,7 @@
    * Event model that will fire events at a constant rate. An event will occur every 1/rate time units.
    */
   public constructor( private readonly rate: number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
@@ -149,12 +150,12 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
+    affirm( rate > 0, 'We need to have a strictly positive rate in order to prevent infinite loops.' );
   }
 
   public getPeriodBeforeNextEvent(): number {
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
@@ -169,7 +170,7 @@
    * The pseudoRandomNumberSource, when called, should generate uniformly distributed random numbers in the range [0,1).
    */
   public constructor( private readonly rate: number, private readonly pseudoRandomNumberSource: () => number ) {
-    assert && assert( rate > 0,
+    affirm( rate > 0,
       'We need to have a strictly positive poisson rate in order to prevent infinite loops.' );
   }
 
@@ -181,7 +182,7 @@
     // http://en.wikipedia.org/wiki/Poisson_process
 
     const uniformRandomNumber = this.pseudoRandomNumberSource();
-    assert && assert( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
+    affirm( uniformRandomNumber >= 0 && uniformRandomNumber < 1,
       `Our uniform random number is outside of its expected range with a value of ${uniformRandomNumber}` );
 
     // sample the exponential distribution
Index: perennial-alias/js/browser-and-node/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/browser-and-node/tsconfig.json b/perennial-alias/js/browser-and-node/tsconfig.json
--- a/perennial-alias/js/browser-and-node/tsconfig.json	(revision ebba8e376ba3c5702dd45674135f9d9e34ce6d41)
+++ b/perennial-alias/js/browser-and-node/tsconfig.json	(date 1734556947146)
@@ -1,3 +1,6 @@
 {
-  "extends": "../../tsconfig/tsconfig-browser-and-node.json"
+  "extends": "../../tsconfig/tsconfig-browser-and-node.json",
+  "include": [
+    "**/*"
+  ]
 }
\ No newline at end of file
Index: phet-core/js/isPhetioEnabled.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/isPhetioEnabled.ts b/phet-core/js/isPhetioEnabled.ts
--- a/phet-core/js/isPhetioEnabled.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/isPhetioEnabled.ts	(date 1734561683602)
@@ -1,7 +1,9 @@
 // Copyright 2024, University of Colorado Boulder
 
+import { hasIn } from '../../sherpa/lodash-es/lodash.js';
+
 /**
  * @author Sam Reid (PhET Interactive Simulations)
  */
-const isPhetioEnabled = _.hasIn( window, 'phet.preloads.phetio' );
+const isPhetioEnabled = hasIn( window, 'phet.preloads.phetio' );
 export default isPhetioEnabled;
\ No newline at end of file
Index: phet-core/js/mutate.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mutate.ts b/phet-core/js/mutate.ts
--- a/phet-core/js/mutate.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/mutate.ts	(date 1734561140941)
@@ -6,7 +6,9 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from 'lodash';
 
 /**
  * For example:
@@ -21,21 +23,21 @@
  * First param will be mutated
  */
 function mutate( target: object, orderedKeys: string[], options?: object ): void {
-  assert && assert( target );
-  assert && assert( Array.isArray( orderedKeys ) );
+  affirm( target );
+  affirm( Array.isArray( orderedKeys ) );
 
   if ( !options ) {
     return;
   }
 
-  assert && assert( Object.getPrototypeOf( options ) === Object.prototype,
+  affirm( Object.getPrototypeOf( options ) === Object.prototype,
     'Extra prototype on options object is a code smell' );
 
   _.each( orderedKeys, key => {
 
     // See https://github.com/phetsims/scenery/issues/580 for more about passing undefined.
     // @ts-expect-error
-    assert && assert( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
+    affirm( !options.hasOwnProperty( key ) || options[ key ] !== undefined,
       `Undefined not allowed for key: ${key}` );
 
     // @ts-expect-error
Index: phet-core/js/EnumerationValue.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationValue.ts b/phet-core/js/EnumerationValue.ts
--- a/phet-core/js/EnumerationValue.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/EnumerationValue.ts	(date 1734556810721)
@@ -17,7 +17,7 @@
  * // Usage
  * console.log( MyEnumeration.VALUE_1 );
  * const printValue = enumValue => {
- *   assert && assert( enumValue.enumeration.values.includes(enumValue));
+ *   affirm( enumValue.enumeration.values.includes(enumValue));
  *   console.log( enumValue );
  * };
  * printValue( MyEnumeration.VALUE_2 );
@@ -26,6 +26,7 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import type Enumeration from './Enumeration.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -50,29 +51,29 @@
 
   public constructor() {
     const c = this.constructor as Constructor<EnumerationValue>;
-    assert && assert( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
+    affirm( !EnumerationValue.sealedCache.has( c ), 'cannot create instanceof of a sealed constructor' );
 
     this._name = null;
     this._enumeration = null;
   }
 
   public set name( name: string ) {
-    assert && assert( !this._name, 'name cannot be changed once defined.' );
+    affirm( !this._name, 'name cannot be changed once defined.' );
     this._name = name;
   }
 
   public get name(): string {
-    assert && assert( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._name, 'name cannot be retrieved until it has been filled in by Enumeration.' );
     return this._name!;
   }
 
   public set enumeration( enumeration: Enumeration<this> ) {
-    assert && assert( !this._enumeration, 'enumeration cannot be changed once defined.' );
+    affirm( !this._enumeration, 'enumeration cannot be changed once defined.' );
     this._enumeration = enumeration;
   }
 
   public get enumeration(): Enumeration<this> {
-    assert && assert( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
+    affirm( this._enumeration, 'enumeration cannot be retrieved until it has been filled in by Enumeration.' );
     return this._enumeration!;
   }
 }
Index: perennial-alias/js/common/callbackOnWorkers.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/js/common/callbackOnWorkers.ts b/perennial-alias/js/common/callbackOnWorkers.ts
--- a/perennial-alias/js/common/callbackOnWorkers.ts	(revision ebba8e376ba3c5702dd45674135f9d9e34ce6d41)
+++ b/perennial-alias/js/common/callbackOnWorkers.ts	(date 1734557107700)
@@ -1,11 +1,13 @@
 // Copyright 2024, University of Colorado Boulder
 
-import _ from 'lodash';
+import _ from '../npm-dependencies/lodash.js';
 
 type CallbackOnWorkers = {
   workers?: number; // How many workers to use
 };
 
+_.assignIn();
+
 
 /**
  * General function for running N async callbacks at once, one per item provided in the list. The callback will
Index: phet-core/js/assertHasPropertiesTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasPropertiesTests.ts b/phet-core/js/assertHasPropertiesTests.ts
--- a/phet-core/js/assertHasPropertiesTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/assertHasPropertiesTests.ts	(date 1734556733070)
@@ -7,13 +7,14 @@
  */
 
 import assertHasProperties from './assertHasProperties.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'assertHasProperties' );
 
 QUnit.test( 'assertHasProperties', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     class MyObject {
 
Index: phet-core/js/Poolable.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Poolable.ts b/phet-core/js/Poolable.ts
--- a/phet-core/js/Poolable.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/Poolable.ts	(date 1734556810815)
@@ -10,6 +10,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import extend from './extend.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
@@ -72,8 +73,8 @@
       useDefaultConstruction: false
     }, providedOptions ) as Required<PoolableOptions<Type>>;
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     // The actual array we store things in. Always push/pop.
     const pool: InstanceType<Type>[] = [];
@@ -142,7 +143,7 @@
        * Sets the maximum pool size.
        */
       set maxPoolSize( value: number ) {
-        assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+        affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
         maxPoolSize = value;
       },
Index: phet-core/js/assertHasProperties.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertHasProperties.ts b/phet-core/js/assertHasProperties.ts
--- a/phet-core/js/assertHasProperties.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/assertHasProperties.ts	(date 1734561683571)
@@ -14,17 +14,19 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 import inheritance from './inheritance.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
 
 const assertHasProperties = ( object: IntentionalAny, properties: string[] ): void => {
-  if ( assert && object ) {
+  if ( isAffirmEnabled() && object ) {
 
 
     properties.forEach( property => {
 
-      assert && assert( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
+      affirm( Object.getOwnPropertyDescriptor( object, property ) || // support fields directly on the object
 
                         // test up the class hierarchy for if the property is defined on a prototype.
                         _.some( inheritance( object.constructor ).map( type => Object.getOwnPropertyDescriptor( type.prototype, property ) ) ),
Index: phet-core/js/extendDefined.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extendDefined.ts b/phet-core/js/extendDefined.ts
--- a/phet-core/js/extendDefined.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/extendDefined.ts	(date 1734561683553)
@@ -11,6 +11,7 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 
 function extendDefined<T>( obj: T, ...sources: Array<T | undefined> ): T {
   _.each( sources, source => {
Index: phet-core/js/interleave.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/interleave.ts b/phet-core/js/interleave.ts
--- a/phet-core/js/interleave.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/interleave.ts	(date 1734556810771)
@@ -8,6 +8,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -17,7 +18,7 @@
  * @returns - The interleaved array
  */
 function interleave<T>( arr: readonly T[], generator: ( element: number ) => T ): T[] {
-  assert && assert( Array.isArray( arr ) );
+  affirm( Array.isArray( arr ) );
 
   const result = [];
   const finalLength = arr.length * 2 - 1;
Index: phet-core/js/Enumeration.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Enumeration.ts b/phet-core/js/Enumeration.ts
--- a/phet-core/js/Enumeration.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/Enumeration.ts	(date 1734561683579)
@@ -25,6 +25,8 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 import EnumerationValue from './EnumerationValue.js';
 import inheritance from './inheritance.js';
 import optionize from './optionize.js';
@@ -32,6 +34,8 @@
 import TEnumeration from './TEnumeration.js';
 import Constructor from './types/Constructor.js';
 
+_.assignIn();
+
 export type EnumerationOptions<T extends EnumerationValue> = {
   phetioDocumentation?: string;
   instanceType?: Constructor<T>;
@@ -61,7 +65,7 @@
     // values appear after previously existing enumeration values
     const types = _.reverse( inheritance( Enumeration ) );
 
-    assert && assert( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
+    affirm( types.includes( instanceType ), 'the specified type should be in its own hierarchy' );
 
     this.keys = [];
     this.values = [];
@@ -69,7 +73,7 @@
       Object.keys( type ).forEach( key => {
         const value = type[ key ];
         if ( value instanceof instanceType ) {
-          assert && assert( key === key.toUpperCase(), 'keys should be upper case by convention' );
+          affirm( key === key.toUpperCase(), 'keys should be upper case by convention' );
           this.keys.push( key );
           this.values.push( value );
 
@@ -83,8 +87,8 @@
       } );
     } );
 
-    assert && assert( this.keys.length > 0, 'no keys found' );
-    assert && assert( this.values.length > 0, 'no values found' );
+    affirm( this.keys.length > 0, 'no keys found' );
+    affirm( this.values.length > 0, 'no values found' );
 
     this.Enumeration = Enumeration as Constructor<T> & Record<string, T>;
     EnumerationValue.sealedCache.add( Enumeration );
Index: phet-core/tsconfig.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig.json b/phet-core/tsconfig.json
--- a/phet-core/tsconfig.json	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/tsconfig.json	(date 1734556810834)
@@ -1,14 +1,11 @@
 {
-  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
-  "include": [
-    "js/phet-core-tests.ts",
-    "js/qunitStartWithoutPhetio.js",
-    "*.Tests.js",
-    "*.Tests.ts"
-  ],
+  "files": [],
   "references": [
     {
       "path": "./tsconfig-module.json"
+    },
+    {
+      "path": "./tsconfig-tests.json"
     }
   ]
 }
Index: phet-core/js/Pool.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/Pool.ts b/phet-core/js/Pool.ts
--- a/phet-core/js/Pool.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/Pool.ts	(date 1734556810808)
@@ -22,6 +22,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import optionize from './optionize.js';
 import phetCore from './phetCore.js';
 import Constructor from './types/Constructor.js';
@@ -87,8 +88,8 @@
       useDefaultConstruction: false
     }, providedOptionsSpread[ 0 ] );
 
-    assert && assert( options.maxSize >= 0 );
-    assert && assert( options.initialSize >= 0 );
+    affirm( options.maxSize >= 0 );
+    affirm( options.initialSize >= 0 );
 
     this._maxPoolSize = options.maxSize;
 
@@ -103,7 +104,7 @@
     this.DefaultConstructor = this.partialConstructor( ...options.defaultArguments! ); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
 
     this.initialize = options.initialize;
-    assert && assert( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
+    affirm( this.initialize, 'Either pass in an initialize option, or provide a method named initialize on the type with the proper signature' );
 
     this.useDefaultConstruction = options.useDefaultConstruction;
 
@@ -157,7 +158,7 @@
    * Sets the maximum pool size.
    */
   public set maxPoolSize( value: number ) {
-    assert && assert( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
+    affirm( value === Number.POSITIVE_INFINITY || ( Number.isInteger( value ) && value >= 0 ), 'maxPoolSize should be a non-negative integer or infinity' );
 
     this._maxPoolSize = value;
   }
Index: phet-core/js/extend.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/extend.ts b/phet-core/js/extend.ts
--- a/phet-core/js/extend.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/extend.ts	(date 1734561683541)
@@ -11,6 +11,9 @@
  */
 
 import phetCore from './phetCore.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
+
+_.assignIn();
 
 function extend<T>( obj: T, ...sources: Array<object | undefined> ): T {
   _.each( sources, source => {
Index: phet-core/js/arrayDifference.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifference.ts b/phet-core/js/arrayDifference.ts
--- a/phet-core/js/arrayDifference.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/arrayDifference.ts	(date 1734561683591)
@@ -6,7 +6,9 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import { uniq } from '../../sherpa/lodash-es/lodash.js';
 
 /**
  * Given two arrays, find the items that are only in one of them (mutates the aOnly/bOnly/inBoth parameters)
@@ -35,16 +37,16 @@
  * @returns - Returns the value of aOnly (the classic definition of difference)
  */
 function arrayDifference<T>( a: T[], b: T[], aOnly?: T[], bOnly?: T[], inBoth?: T[] ): T[] {
-  assert && assert( Array.isArray( a ) && _.uniq( a ).length === a.length, 'a is not an array of unique items' );
-  assert && assert( Array.isArray( b ) && _.uniq( b ).length === b.length, 'b is not an array of unique items' );
+  affirm( Array.isArray( a ) && uniq( a ).length === a.length, 'a is not an array of unique items' );
+  affirm( Array.isArray( b ) && uniq( b ).length === b.length, 'b is not an array of unique items' );
 
   aOnly = aOnly || [];
   bOnly = bOnly || [];
   inBoth = inBoth || [];
 
-  assert && assert( Array.isArray( aOnly ) && aOnly.length === 0 );
-  assert && assert( Array.isArray( bOnly ) && bOnly.length === 0 );
-  assert && assert( Array.isArray( inBoth ) && inBoth.length === 0 );
+  affirm( Array.isArray( aOnly ) && aOnly.length === 0 );
+  affirm( Array.isArray( bOnly ) && bOnly.length === 0 );
+  affirm( Array.isArray( inBoth ) && inBoth.length === 0 );
 
   Array.prototype.push.apply( aOnly, a );
   Array.prototype.push.apply( bOnly, b );
Index: phet-core/js/gracefulBind.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/gracefulBind.ts b/phet-core/js/gracefulBind.ts
--- a/phet-core/js/gracefulBind.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/gracefulBind.ts	(date 1734561683607)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 
 /**
  * If the path exists on the window global, return it as a bound function, otherwise returns null
  * @param path a path to a method, dot-separated, including the method, such as 'phet.joist.sim.showPopup'
  */
 const gracefulBind = ( path: string ): null | VoidFunction => {
-  assert && assert( path.split( '.' ).length > 1, 'path must have multiple parts' );
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.split( '.' ).length > 1, 'path must have multiple parts' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const terms = path.split( '.' );
   const method = terms.pop()!; // mutates terms to become the method container
   const object = _.get( window, terms );
Index: phet-core/js/partition.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/partition.ts b/phet-core/js/partition.ts
--- a/phet-core/js/partition.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/partition.ts	(date 1734556810798)
@@ -9,10 +9,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function partition<T>( array: T[], predicate: ( element: T ) => boolean ): readonly[ T[], T[] ] {
-  assert && assert( Array.isArray( array ) );
+  affirm( Array.isArray( array ) );
 
   const satisfied = [];
   const unsatisfied = [];
Index: phet-core/js/EnumerationMap.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationMap.ts b/phet-core/js/EnumerationMap.ts
--- a/phet-core/js/EnumerationMap.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/EnumerationMap.ts	(date 1734556733115)
@@ -7,6 +7,7 @@
  */
 
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 type TEnumeration<T> = {
   enumeration: {
@@ -31,7 +32,7 @@
 
     this._values = enumeration.enumeration.values;
     this._values.forEach( entry => {
-      assert && assert( !this._map.has( entry ), 'Enumeration key override problem' );
+      affirm( !this._map.has( entry ), 'Enumeration key override problem' );
       this._map.set( entry, factory( entry ) );
     } );
   }
@@ -40,8 +41,8 @@
    * Returns the value associated with the given enumeration entry.
    */
   public get( entry: T ): U {
-    assert && assert( this._values.includes( entry ) );
-    assert && assert( this._map.has( entry ) );
+    affirm( this._values.includes( entry ) );
+    affirm( this._map.has( entry ) );
     return this._map.get( entry )!;
   }
 
@@ -49,7 +50,7 @@
    * Sets the value associated with the given enumeration entry.
    */
   public set( entry: T, value: U ): void {
-    assert && assert( this._values.includes( entry ) );
+    affirm( this._values.includes( entry ) );
     this._map.set( entry, value );
   }
 
Index: phet-core/js/EnumerationDeprecated.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/EnumerationDeprecated.js b/phet-core/js/EnumerationDeprecated.js
--- a/phet-core/js/EnumerationDeprecated.js	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/EnumerationDeprecated.js	(date 1734561683597)
@@ -52,7 +52,7 @@
  *
  *     // @param {Scene} mode - value from Scene EnumerationDeprecated
  *     setSceneMode( mode ) {
- *       assert && assert( Scene.includes( mode ) );
+ *       affirm( Scene.includes( mode ) );
  *       //...
  *     }
  *
@@ -62,6 +62,8 @@
 import deprecationWarning from './deprecationWarning.js';
 import merge from './merge.js';
 import phetCore from './phetCore.js';
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 
 /**
  * @deprecated
@@ -77,11 +79,11 @@
   constructor( config ) {
     deprecationWarning( 'EnumerationDeprecated should be exchanged for classes that extend EnumerationValue, see WilderEnumerationPatterns for examples.' );
 
-    assert && assert( config, 'config must be provided' );
+    affirm( config, 'config must be provided' );
 
     const keysProvided = !!config.keys;
     const mapProvided = !!config.map;
-    assert && assert( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
+    affirm( keysProvided !== mapProvided, 'must provide one or the other but not both of keys/map' );
 
     const keys = config.keys || Object.keys( config.map );
     const map = config.map || {};
@@ -99,16 +101,16 @@
       beforeFreeze: null
     }, config );
 
-    assert && assert( Array.isArray( keys ), 'Values should be an array' );
-    assert && assert( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
+    affirm( Array.isArray( keys ), 'Values should be an array' );
+    affirm( _.uniq( keys ).length === keys.length, 'There should be no duplicated values provided' );
     assert && keys.forEach( value => assert( typeof value === 'string', 'Each value should be a string' ) );
     assert && keys.forEach( value => assert( /^[A-Z][A-Z0-9_]*$/g.test( value ),
       'EnumerationDeprecated values should be uppercase alphanumeric with underscores and begin with a letter' ) );
-    assert && assert( !_.includes( keys, 'VALUES' ),
+    affirm( !_.includes( keys, 'VALUES' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'KEYS' ),
+    affirm( !_.includes( keys, 'KEYS' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
-    assert && assert( !_.includes( keys, 'includes' ),
+    affirm( !_.includes( keys, 'includes' ),
       'This is the name of a built-in provided value, so it cannot be included as an enumeration value' );
 
     // @public (phet-io) - provides additional documentation for PhET-iO which can be viewed in studio
@@ -125,8 +127,8 @@
       const value = map[ key ] || {};
 
       // Set attributes of the enumeration value
-      assert && assert( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
-      assert && assert( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
+      affirm( value.name === undefined, '"rich" enumeration values cannot provide their own name attribute' );
+      affirm( value.toString === Object.prototype.toString, '"rich" enumeration values cannot provide their own toString' );
 
       // @public {string} (read-only) - PhET-iO public API relies on this mapping, do not change it lightly
       value.name = key;
@@ -223,8 +225,8 @@
    * @public
    */
   static byKeys( keys, options ) {
-    assert && assert( Array.isArray( keys ), 'keys must be an array' );
-    assert && assert( !options || options.keys === undefined );
+    affirm( Array.isArray( keys ), 'keys must be an array' );
+    affirm( !options || options.keys === undefined );
     return new EnumerationDeprecated( merge( { keys: keys }, options ) );
   }
 
@@ -236,11 +238,11 @@
    * @public
    */
   static byMap( map, options ) {
-    assert && assert( !options || options.map === undefined );
+    affirm( !options || options.map === undefined );
     if ( assert ) {
       const values = _.values( map );
-      assert && assert( values.length >= 1, 'must have at least 2 entries in an enumeration' );
-      assert && assert( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
+      affirm( values.length >= 1, 'must have at least 2 entries in an enumeration' );
+      affirm( _.every( values, value => value.constructor === values[ 0 ].constructor ), 'Values must have same constructor' );
     }
     return new EnumerationDeprecated( merge( { map: map }, options ) );
   }
Index: phet-core/js/cleanArray.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/cleanArray.ts b/phet-core/js/cleanArray.ts
--- a/phet-core/js/cleanArray.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/cleanArray.ts	(date 1734556733086)
@@ -7,10 +7,11 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 function cleanArray<T>( arr?: T[] | null | undefined ): T[] {
-  assert && assert( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
+  affirm( !arr || ( Array.isArray( arr ) ), 'cleanArray either takes an Array' );
 
   if ( arr ) {
     // fastest way to clear an array (http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript, http://jsperf.com/array-destroy/32)
Index: phet-core/js/asyncLoader.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/asyncLoader.ts b/phet-core/js/asyncLoader.ts
--- a/phet-core/js/asyncLoader.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/asyncLoader.ts	(date 1734556733083)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import arrayRemove from '../../phet-core/js/arrayRemove.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
@@ -50,7 +51,7 @@
    */
   private proceedIfReady(): void {
     if ( this.pendingLocks.length === 0 ) {
-      assert && assert( !this.loadComplete, 'cannot complete load twice' );
+      affirm( !this.loadComplete, 'cannot complete load twice' );
       this.loadComplete = true;
 
       this.listeners.forEach( listener => listener() );
@@ -61,10 +62,10 @@
    * Creates a lock, which is a callback that needs to be run before we can proceed.
    */
   public createLock( object?: IntentionalAny ): AsyncLoaderLock {
-    assert && assert( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
+    affirm( !this.loadComplete, 'Cannot create more locks after load-step has completed' );
     this.pendingLocks.push( object );
     return () => {
-      assert && assert( this.pendingLocks.includes( object ), 'invalid lock' );
+      affirm( this.pendingLocks.includes( object ), 'invalid lock' );
       arrayRemove( this.pendingLocks, object );
       this.proceedIfReady();
     };
Index: perennial-alias/package.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/perennial-alias/package.json b/perennial-alias/package.json
--- a/perennial-alias/package.json	(revision ebba8e376ba3c5702dd45674135f9d9e34ce6d41)
+++ b/perennial-alias/package.json	(date 1734557107709)
@@ -12,6 +12,10 @@
     "type-check-all": "grunt type-check --all --clean",
     "lint-all": "grunt lint --all --clean"
   },
+  "dependencies": {
+    "nodemailer": "^6.9.1",
+    "pug": "^3.0.2"
+  },
   "devDependencies": {
     "@octokit/rest": "~16.33.1",
     "@stylistic/eslint-plugin": "~2.11.0",
@@ -61,9 +65,5 @@
     "winston": "~2.4.5",
     "winston-loggly": "~1.3.1",
     "xml2js": "~0.4.15"
-  },
-  "dependencies": {
-    "nodemailer": "^6.9.1",
-    "pug": "^3.0.2"
   }
 }
Index: chipper/js/browser-and-node/tsconfig-dependencies.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/chipper/js/browser-and-node/tsconfig-dependencies.json b/chipper/js/browser-and-node/tsconfig-dependencies.json
--- a/chipper/js/browser-and-node/tsconfig-dependencies.json	(revision f3bf68da6da9108079427af58a99522d0d1ad119)
+++ b/chipper/js/browser-and-node/tsconfig-dependencies.json	(date 1734556732978)
@@ -8,7 +8,7 @@
       "path": "../../../perennial-alias/js/browser-and-node/tsconfig.json"
     },
     {
-      "path": "../../../phet-core/tsconfig-browser-and-node.json"
+      "path": "../../../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: phet-core/tsconfig-tests.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/tsconfig-tests.json b/phet-core/tsconfig-tests.json
new file mode 100644
--- /dev/null	(date 1734556810844)
+++ b/phet-core/tsconfig-tests.json	(date 1734556810844)
@@ -0,0 +1,14 @@
+{
+  "extends": "../perennial-alias/tsconfig/tsconfig-browser.json",
+  "include": [
+    "js/**/*Tests.ts",
+    "js/**/*Tests.js",
+    "js/phet-core-tests.ts",
+    "js/qunitStartWithoutPhetio.ts"
+  ],
+  "references": [
+    {
+      "path": "../perennial-alias/tsconfig/buildjson/tsconfig.json"
+    }
+  ]
+}
Index: aqua/tsconfig-aqua-node.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/tsconfig-aqua-node.json b/aqua/tsconfig-aqua-node.json
--- a/aqua/tsconfig-aqua-node.json	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/tsconfig-aqua-node.json	(date 1734556732971)
@@ -12,7 +12,7 @@
       "path": "../perennial/tsconfig.json"
     },
     {
-      "path": "../phet-core/tsconfig-browser-and-node.json"
+      "path": "../phet-core/tsconfig-module.json"
     }
   ]
 }
\ No newline at end of file
Index: phet-core/js/mergeTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/mergeTests.ts b/phet-core/js/mergeTests.ts
--- a/phet-core/js/mergeTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/mergeTests.ts	(date 1734561683558)
@@ -4,6 +4,8 @@
 
 import merge from './merge.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 
 QUnit.module( 'merge' );
 
@@ -215,7 +217,7 @@
     }
   };
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
     assert.throws( () => merge( original, merges.a ), 'merge should not allow arrays to be merged' );
     assert.throws( () => merge( original, merges.b ), 'merge should not allow inherited objects to be merged' );
     assert.throws( () => merge( original, merges.f ), 'merge should not allow instances to be merged' );
@@ -372,7 +374,7 @@
 } );
 
 QUnit.test( 'test wrong args', assert => {
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // in first arg
     assert.throws( () => merge( undefined, {} ), 'unsupported first arg "undefined"' );
Index: phet-core/js/dimensionMapTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionMapTests.ts b/phet-core/js/dimensionMapTests.ts
--- a/phet-core/js/dimensionMapTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/dimensionMapTests.ts	(date 1734561683562)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../sherpa/lodash-es/lodash.js';
 import dimensionMap from './dimensionMap.js';
 
 QUnit.module( 'dimensionMap' );
Index: phet-core/js/required.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/required.ts b/phet-core/js/required.ts
--- a/phet-core/js/required.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/required.ts	(date 1734556810826)
@@ -6,13 +6,14 @@
  * @author Denzell Barnett (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 
 /**
  * Checks if the value passed is defined
  */
 function required<T>( entry: T ): T {
-  assert && assert( entry !== undefined, 'Required field is undefined.' );
+  affirm( entry !== undefined, 'Required field is undefined.' );
   return entry;
 }
 
Index: phet-core/js/assertMutuallyExclusiveOptionsTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts
--- a/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/assertMutuallyExclusiveOptionsTests.ts	(date 1734556733079)
@@ -6,6 +6,7 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
 import assertMutuallyExclusiveOptions from './assertMutuallyExclusiveOptions.js';
 
 QUnit.module( 'assertMutuallyExclusiveOptions' );
@@ -13,7 +14,7 @@
 QUnit.test( 'assertMutuallyExclusiveOptions', assert => {
   assert.ok( true, 'one test whether or not assertions are enabled' );
 
-  if ( window.assert ) {
+  if ( isAffirmEnabled() ) {
 
     // Should not throw error because options are all from one set.
     assertMutuallyExclusiveOptions( { a: true, b: false }, [ 'a', 'b' ], [ 'c' ] );
Index: phet-core/js/getGlobal.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/getGlobal.ts b/phet-core/js/getGlobal.ts
--- a/phet-core/js/getGlobal.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/getGlobal.ts	(date 1734561683534)
@@ -6,15 +6,17 @@
  * @author Michael Kauzmann (PhET Interactive Simulations)
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
 import phetCore from './phetCore.js';
 import IntentionalAny from './types/IntentionalAny.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 
 /**
  * If the path exists on the window global, return it, otherwise returns null
  * @param path a path to global, such as 'phet.joist.sim'
  */
 const getGlobal = ( path: string ): IntentionalAny | null => {
-  assert && assert( path.trim() === path, 'path must be trimmed' );
+  affirm( path.trim() === path, 'path must be trimmed' );
   const global = _.get( window, path );
   return global !== undefined ? global : null;
 };
Index: chipper/js/common/transpile.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/chipper/js/common/transpile.ts b/chipper/js/common/transpile.ts
--- a/chipper/js/common/transpile.ts	(revision f3bf68da6da9108079427af58a99522d0d1ad119)
+++ b/chipper/js/common/transpile.ts	(date 1734561740995)
@@ -5,9 +5,9 @@
 import fs from 'fs';
 import _ from 'lodash';
 import path from 'path';
+import { Repo } from '../../../perennial-alias/js/browser-and-node/PerennialTypes.js';
 import dirname from '../../../perennial-alias/js/common/dirname.js';
 import getActiveRepos from '../../../perennial-alias/js/common/getActiveRepos.js';
-import { Repo } from '../../../perennial-alias/js/browser-and-node/PerennialTypes.js';
 import getOption, { isOptionKeyProvided } from '../../../perennial-alias/js/grunt/tasks/util/getOption.js';
 import getRepo from '../../../perennial-alias/js/grunt/tasks/util/getRepo.js';
 
@@ -143,7 +143,7 @@
   repo === 'phet-io-sim-specific' && subdirs.push( 'repos' );
   repo === 'my-solar-system' && subdirs.push( 'shaders' );
   repo === 'alpenglow' && subdirs.push( 'wgsl' );
-  repo === 'sherpa' && subdirs.push( 'lib' );
+  repo === 'sherpa' && subdirs.push( 'lib', 'lodash-es' );
   repo === 'brand' && subdirs.push( ...getBrands() );
 
   return subdirs.map( subdir => `${repo}/${subdir}/` );
Index: phet-core/js/dimensionForEachTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/dimensionForEachTests.ts b/phet-core/js/dimensionForEachTests.ts
--- a/phet-core/js/dimensionForEachTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/dimensionForEachTests.ts	(date 1734561683567)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../sherpa/lodash-es/lodash.js';
 import dimensionForEach from './dimensionForEach.js';
 
 QUnit.module( 'dimensionForEach' );
Index: phet-core/js/arrayRemove.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayRemove.ts b/phet-core/js/arrayRemove.ts
--- a/phet-core/js/arrayRemove.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/arrayRemove.ts	(date 1734561683575)
@@ -6,13 +6,15 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import affirm from '../../perennial-alias/js/browser-and-node/affirm.js';
+import { indexOf } from '../../sherpa/lodash-es/lodash.js';
 import phetCore from './phetCore.js';
 
 function arrayRemove<T>( array: T[], toRemove: T ): void {
-  assert && assert( Array.isArray( array ), 'arrayRemove takes an Array' );
+  affirm( Array.isArray( array ), 'arrayRemove takes an Array' );
 
-  const index = _.indexOf( array, toRemove );
-  assert && assert( index >= 0, 'item not found in Array' );
+  const index = indexOf( array, toRemove );
+  affirm( index >= 0, 'item not found in Array' );
 
   array.splice( index, 1 );
 }
Index: phet-core/js/arrayDifferenceTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/arrayDifferenceTests.ts b/phet-core/js/arrayDifferenceTests.ts
--- a/phet-core/js/arrayDifferenceTests.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/arrayDifferenceTests.ts	(date 1734561683585)
@@ -6,6 +6,7 @@
  * @author Jonathan Olson <[email protected]>
  */
 
+import _ from '../../sherpa/lodash-es/lodash.js';
 import arrayDifference from './arrayDifference.js';
 import arrayRemove from './arrayRemove.js';
 
Index: aqua/js/server/QuickServer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/aqua/js/server/QuickServer.ts b/aqua/js/server/QuickServer.ts
--- a/aqua/js/server/QuickServer.ts	(revision deed6230cfa5524501546d0129a9a798fb81f1be)
+++ b/aqua/js/server/QuickServer.ts	(date 1734557181163)
@@ -24,12 +24,12 @@
 import npmUpdate from '../../../perennial/js/common/npmUpdate.js';
 import puppeteerLoad from '../../../perennial/js/common/puppeteerLoad.js';
 import withServer from '../../../perennial/js/common/withServer.js';
-import _ from '../../../perennial/js/npm-dependencies/lodash.js';
+import _ from '../../../perennial-alias/js/npm-dependencies/lodash.js';
 import puppeteer from '../../../perennial/js/npm-dependencies/puppeteer.js';
 import winston from '../../../perennial/js/npm-dependencies/winston.js';
 import sendSlackMessage from './sendSlackMessage.js';
 
-
+_.assignIn();
 type TestData = {
   passed: boolean;
 
Index: phet-core/js/assertMutuallyExclusiveOptions.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/phet-core/js/assertMutuallyExclusiveOptions.ts b/phet-core/js/assertMutuallyExclusiveOptions.ts
--- a/phet-core/js/assertMutuallyExclusiveOptions.ts	(revision e86e414f30cbc72142a797965118dfcc00e46618)
+++ b/phet-core/js/assertMutuallyExclusiveOptions.ts	(date 1734561683547)
@@ -12,6 +12,8 @@
  * @author Sam Reid (PhET Interactive Simulations)
  */
 
+import affirm, { isAffirmEnabled } from '../../perennial-alias/js/browser-and-node/affirm.js';
+import _ from '../../sherpa/lodash-es/lodash.js';
 import phetCore from './phetCore.js';
 
 /**
@@ -20,7 +22,7 @@
  * @param sets - families of mutually exclusive option keys, see examples above.
  */
 const assertMutuallyExclusiveOptions = function( options: object | null | undefined, ...sets: string[][] ): void {
-  if ( assert && options ) {
+  if ( isAffirmEnabled() && options ) {
 
     // Determine which options are used from each set
     const usedElementsFromEachSet = sets.map( set => Object.keys( _.pick( options, ...set ) ) );
@@ -29,7 +31,7 @@
     if ( usedElementsFromEachSet.filter( usedElements => usedElements.length > 0 ).length > 1 ) {
 
       // Output the errant options.
-      assert && assert( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
+      affirm( false, `Cannot simultaneously specify ${usedElementsFromEachSet.join( ' and ' )}` );
     }
   }
 };
diff --git a/perennial-alias/js/npm-dependencies/lodash.js b/perennial-alias/js/npm-dependencies/lodash.ts
rename from perennial-alias/js/npm-dependencies/lodash.js
rename to perennial-alias/js/npm-dependencies/lodash.ts

jessegreenberg pushed a commit to phetsims/greenhouse-effect that referenced this issue Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants