Repository: solana-labs/solana-program-library
Pull request #5819 (2023-11-13 22:08:35)

js: Add top-level package.json and turbo build
#### Problem

There's no top-level `package.json`, which means that dependabot doesn't know how to handle the sub-packages in the SPL repo.

#### Solution

Add a top-level package.json! This follows a very similar model to https://github.com/solana-labs/solana-web3.js/blob/master/package.json, and also adds turbo for building, which means that we don't need to declare the dependencies by hand in the CI scripts, which is *awesome*.

It's still a little messy, but it should be much better to work with overall. The next step might be to isolate all of the JS testing and have them all build their required programs themselves, rather than waiting on the `cargo-test-sbf` step to build it for them. That should greatly reduce the CI time.

joncinque: Merging early to see how the dependabot run goes tomorrow morning, please feel free to still give feedback!

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 000000000..daec0f421
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,4 @@
+module.exports = { // E: 'module' is not defined.
+    extends: ['turbo', '@solana/eslint-config-solana', '@solana/eslint-config-solana/jest'],
+    root: true,
+};
diff --git a/.gitignore b/.gitignore
index a6c0a9961..2d6060fdb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ test-ledger
 docker-target
 .idea
 .coderrect
+.turbo
diff --git a/name-service/js/package.json b/name-service/js/package.json
index 56bc864c5..4570d91df 100644
--- a/name-service/js/package.json
+++ b/name-service/js/package.json
@@ -21,12 +21,12 @@
     "dev": "tsc && node --trace-warnings dist/transfer.js",
     "build": "tsc",
     "prepublish": "tsc",
-    "lint": "yarn pretty && eslint --max-warnings 0 'src/*.ts'",
-    "lint:fix": "yarn pretty:fix && eslint 'src/*.ts' --fix",
+    "lint": "npm run pretty && eslint --max-warnings 0 'src/*.ts'",
+    "lint:fix": "npm run pretty:fix && eslint 'src/*.ts' --fix",
     "pretty": "prettier --check '{src/*.ts,test/*/*.ts}'",
     "pretty:fix": "prettier --write '{src/*.ts,test/*/*.ts}'",
-    "doc": "yarn typedoc src/index.ts",
-    "test": "yarn test:unit && yarn test:e2e",
+    "doc": "npm run typedoc src/index.ts",
+    "test": "npm run test:unit && npm run test:e2e",
     "test:unit": "mocha test/unit",
     "test:e2e": "start-server-and-test 'solana-test-validator --bpf-program namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX ../../target/deploy/spl_name_service.so --reset --quiet' http://127.0.0.1:8899/health 'mocha test/e2e'"
   },
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..f43c544cb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+    "private": true,
+    "workspaces": [
+        "libraries/type-length-value/js",
+        "memo/js",
+        "single-pool/js",
+        "stake-pool/js",
+        "token/js",
+        "token-lending/js",
+        "token-metadata/js",
+        "token-swap/js"
+    ],
+    "scripts": {
+        "build": "turbo run build",
+        "clean": "turbo run clean",
+        "lint": "turbo run lint",
+        "lint:fix": "turbo run lint:fix",
+        "test": "turbo run test"
+    },
+    "devDependencies": {
+        "@solana/eslint-config-solana": "^1.0.2",
+        "@solana/prettier-config-solana": "^0.0.3",
+        "eslint-config-turbo": "^1.10.13",
+        "turbo": "^1.10.16"
+    },
+    "engines": {
+        "node": ">=14.0.0"
+    },
+    "packageManager": "pnpm@8.5.1",
+    "prettier": "@solana/prettier-config-solana"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c9ca21bf..832c5743f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,11 +1,22 @@
 lockfileVersion: '6.0'
 
-settings:
-  autoInstallPeers: true
-  excludeLinksFromLockfile: false
-
 importers:
 
+  .:
+    devDependencies:
+      '@solana/eslint-config-solana':
+        specifier: ^1.0.2
+        version: 1.0.2(@typescript-eslint/eslint-plugin@6.10.0)(@typescript-eslint/parser@6.10.0)(eslint-plugin-jest@27.6.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.53.0)(typescript@5.2.2)
+      '@solana/prettier-config-solana':
+        specifier: ^0.0.3
+        version: 0.0.3(prettier@3.0.3)
+      eslint-config-turbo:
+        specifier: ^1.10.13
+        version: 1.10.13(eslint@8.53.0)
+      turbo:
+        specifier: ^1.10.16
+        version: 1.10.16
+
   libraries/type-length-value/js:
     dependencies:
       buffer:
@@ -2016,6 +2027,28 @@ packages:
       fastestsmallesttextencoderdecoder: 1.0.22
     dev: false
 
+  /@solana/eslint-config-solana@1.0.2(@typescript-eslint/eslint-plugin@6.10.0)(@typescript-eslint/parser@6.10.0)(eslint-plugin-jest@27.6.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@10.0.0)(eslint-plugin-sort-keys-fix@1.1.2)(eslint@8.53.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-/mBi2xFsX0fq3ukZ3ugA+Zh2+Ct+bh713ZSdOJAv1G7ARzyOEDxmiUz8TLzHu4joOC/3Ok4Yl7cjupOcIRzMHw==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': ^6.0.0
+      '@typescript-eslint/parser': ^6.0.0
+      eslint: ^8.45.0
+      eslint-plugin-jest: ^27.2.3
+      eslint-plugin-react-hooks: ^4.6.0
+      eslint-plugin-simple-import-sort: ^10.0.0
+      eslint-plugin-sort-keys-fix: ^1.1.2
+      typescript: ^5.1.6
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
+      '@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
+      eslint: 8.53.0
+      eslint-plugin-jest: 27.6.0(@typescript-eslint/eslint-plugin@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
+      eslint-plugin-react-hooks: 4.6.0(eslint@8.53.0)
+      eslint-plugin-simple-import-sort: 10.0.0(eslint@8.53.0)
+      eslint-plugin-sort-keys-fix: 1.1.2
+      typescript: 5.2.2
+    dev: true
+
   /@solana/functional@2.0.0-experimental.21e994f:
     resolution: {integrity: sha512-FMXFiTA+hsc9FCv0r47oF7njq/K9x7zh0H+To7tpeqwN65LtJPu5BMG7xZY3rn5TrudgKw6XPuIr3ARbI8+IWA==}
     dev: false
@@ -2037,6 +2070,14 @@ packages:
       '@solana/codecs-numbers': 2.0.0-experimental.398c396
     dev: false
 
+  /@solana/prettier-config-solana@0.0.3(prettier@3.0.3):
+    resolution: {integrity: sha512-69lOncqTcp7Q/yQIU7R4/nQ6Ejir0TlYiUMcOM/z1PHsiDRF6qL+Jzfy8J9j5euDAR5VCoh0kM5lzaB/sdWgCA==}
+    peerDependencies:
+      prettier: ^3.0.2
+    dependencies:
+      prettier: 3.0.3
+    dev: true
+
   /@solana/rpc-core@2.0.0-experimental.21e994f:
     resolution: {integrity: sha512-T7VcTLRi4dsqmpFYdnvcHZFS8Vcgdi6funMUrXcM7ofQqb8vWGJnlX6AX0eIZiVsmoYk5Ki8wW4D6Ul6bXZyZg==}
     dependencies:
@@ -2321,6 +2362,14 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/scope-manager@5.62.0:
+    resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/visitor-keys': 5.62.0
+    dev: true
+
   /@typescript-eslint/scope-manager@6.10.0:
     resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -2349,11 +2398,37 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/types@5.62.0:
+    resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
   /@typescript-eslint/types@6.10.0:
     resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
+  /@typescript-eslint/typescript-estree@5.62.0(typescript@5.2.2):
+    resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/visitor-keys': 5.62.0
+      debug: 4.3.4(supports-color@8.1.1)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      semver: 7.5.4
+      tsutils: 3.21.0(typescript@5.2.2)
+      typescript: 5.2.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2):
     resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -2375,6 +2450,26 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/utils@5.62.0(eslint@8.53.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
+      '@types/json-schema': 7.0.15
+      '@types/semver': 7.5.5
+      '@typescript-eslint/scope-manager': 5.62.0
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2)
+      eslint: 8.53.0
+      eslint-scope: 5.1.1
+      semver: 7.5.4
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2):
     resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -2394,6 +2489,14 @@ packages:
       - typescript
     dev: true
 
+  /@typescript-eslint/visitor-keys@5.62.0:
+    resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      eslint-visitor-keys: 3.4.3
+    dev: true
+
   /@typescript-eslint/visitor-keys@6.10.0:
     resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -2413,6 +2516,14 @@ packages:
       jsonparse: 1.3.1
       through: 2.3.8
 
+  /acorn-jsx@5.3.2(acorn@7.4.1):
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      acorn: 7.4.1
+    dev: true
+
   /acorn-jsx@5.3.2(acorn@8.11.2):
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
@@ -2426,6 +2537,12 @@ packages:
     engines: {node: '>=0.4.0'}
     dev: true
 
+  /acorn@7.4.1:
+    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: true
+
   /acorn@8.11.2:
     resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}
     engines: {node: '>=0.4.0'}
@@ -3391,6 +3508,11 @@ packages:
       esutils: 2.0.3
     dev: true
 
+  /dotenv@16.0.3:
+    resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
+    engines: {node: '>=12'}
+    dev: true
+
   /duplexer@0.1.2:
     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
     dev: true
@@ -3572,6 +3694,15 @@ packages:
       eslint: 8.53.0
     dev: true
 
+  /eslint-config-turbo@1.10.13(eslint@8.53.0):
+    resolution: {integrity: sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg==}
+    peerDependencies:
+      eslint: '>6.6.0'
+    dependencies:
+      eslint: 8.53.0
+      eslint-plugin-turbo: 1.10.13(eslint@8.53.0)
+    dev: true
+
   /eslint-import-resolver-node@0.3.9:
     resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
     dependencies:
@@ -3679,6 +3810,27 @@ packages:
       - supports-color
     dev: true
 
+  /eslint-plugin-jest@27.6.0(@typescript-eslint/eslint-plugin@6.10.0)(eslint@8.53.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0
+      eslint: ^7.0.0 || ^8.0.0
+      jest: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/eslint-plugin':
+        optional: true
+      jest:
+        optional: true
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
+      '@typescript-eslint/utils': 5.62.0(eslint@8.53.0)(typescript@5.2.2)
+      eslint: 8.53.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /eslint-plugin-prettier@5.0.1(@types/eslint@8.44.7)(eslint-config-prettier@9.0.0)(eslint@8.53.0)(prettier@3.0.3):
     resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -3701,6 +3853,15 @@ packages:
       synckit: 0.8.5
     dev: true
 
+  /eslint-plugin-react-hooks@4.6.0(eslint@8.53.0):
+    resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+    dependencies:
+      eslint: 8.53.0
+    dev: true
+
   /eslint-plugin-require-extensions@0.1.3(eslint@8.53.0):
     resolution: {integrity: sha512-T3c1PZ9PIdI3hjV8LdunfYI8gj017UQjzAnCrxuo3wAjneDbTPHdE3oNWInOjMA+z/aBkUtlW5vC0YepYMZIug==}
     engines: {node: '>=16'}
@@ -3710,6 +3871,41 @@ packages:
       eslint: 8.53.0
     dev: true
 
+  /eslint-plugin-simple-import-sort@10.0.0(eslint@8.53.0):
+    resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
+    peerDependencies:
+      eslint: '>=5.0.0'
+    dependencies:
+      eslint: 8.53.0
+    dev: true
+
+  /eslint-plugin-sort-keys-fix@1.1.2:
+    resolution: {integrity: sha512-DNPHFGCA0/hZIsfODbeLZqaGY/+q3vgtshF85r+YWDNCQ2apd9PNs/zL6ttKm0nD1IFwvxyg3YOTI7FHl4unrw==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      espree: 6.2.1
+      esutils: 2.0.3
+      natural-compare: 1.4.0
+      requireindex: 1.2.0
+    dev: true
+
+  /eslint-plugin-turbo@1.10.13(eslint@8.53.0):
+    resolution: {integrity: sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg==}
+    peerDependencies:
+      eslint: '>6.6.0'
+    dependencies:
+      dotenv: 16.0.3
+      eslint: 8.53.0
+    dev: true
+
+  /eslint-scope@5.1.1:
+    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 4.3.0
+    dev: true
+
   /eslint-scope@7.2.2:
     resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3718,6 +3914,11 @@ packages:
       estraverse: 5.3.0
     dev: true
 
+  /eslint-visitor-keys@1.3.0:
+    resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
+    engines: {node: '>=4'}
+    dev: true
+
   /eslint-visitor-keys@3.4.3:
     resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3770,6 +3971,15 @@ packages:
       - supports-color
     dev: true
 
+  /espree@6.2.1:
+    resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
+    engines: {node: '>=6.0.0'}
+    dependencies:
+      acorn: 7.4.1
+      acorn-jsx: 5.3.2(acorn@7.4.1)
+      eslint-visitor-keys: 1.3.0
+    dev: true
+
   /espree@9.6.1:
     resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3799,6 +4009,11 @@ packages:
       estraverse: 5.3.0
     dev: true
 
+  /estraverse@4.3.0:
+    resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+    engines: {node: '>=4.0'}
+    dev: true
+
   /estraverse@5.3.0:
     resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
     engines: {node: '>=4.0'}
@@ -6010,6 +6225,11 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /requireindex@1.2.0:
+    resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
+    engines: {node: '>=0.10.5'}
+    dev: true
+
   /resolve-cwd@3.0.0:
     resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
     engines: {node: '>=8'}
@@ -6708,10 +6928,24 @@ packages:
       strip-bom: 3.0.0
     dev: true
 
+  /tslib@1.14.1:
+    resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+    dev: true
+
   /tslib@2.6.2:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
     dev: true
 
+  /tsutils@3.21.0(typescript@5.2.2):
+    resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+    engines: {node: '>= 6'}
+    peerDependencies:
+      typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+    dependencies:
+      tslib: 1.14.1
+      typescript: 5.2.2
+    dev: true
+
   /tsx@3.14.0:
     resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==}
     hasBin: true
@@ -6723,6 +6957,66 @@ packages:
       fsevents: 2.3.3
     dev: true
 
+  /turbo-darwin-64@1.10.16:
+    resolution: {integrity: sha512-+Jk91FNcp9e9NCLYlvDDlp2HwEDp14F9N42IoW3dmHI5ZkGSXzalbhVcrx3DOox3QfiNUHxzWg4d7CnVNCuuMg==}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo-darwin-arm64@1.10.16:
+    resolution: {integrity: sha512-jqGpFZipIivkRp/i+jnL8npX0VssE6IAVNKtu573LXtssZdV/S+fRGYA16tI46xJGxSAivrZ/IcgZrV6Jk80bw==}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo-linux-64@1.10.16:
+    resolution: {integrity: sha512-PpqEZHwLoizQ6sTUvmImcRmACyRk9EWLXGlqceogPZsJ1jTRK3sfcF9fC2W56zkSIzuLEP07k5kl+ZxJd8JMcg==}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo-linux-arm64@1.10.16:
+    resolution: {integrity: sha512-TMjFYz8to1QE0fKVXCIvG/4giyfnmqcQIwjdNfJvKjBxn22PpbjeuFuQ5kNXshUTRaTJihFbuuCcb5OYFNx4uw==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo-windows-64@1.10.16:
+    resolution: {integrity: sha512-+jsf68krs0N66FfC4/zZvioUap/Tq3sPFumnMV+EBo8jFdqs4yehd6+MxIwYTjSQLIcpH8KoNMB0gQYhJRLZzw==}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo-windows-arm64@1.10.16:
+    resolution: {integrity: sha512-sKm3hcMM1bl0B3PLG4ifidicOGfoJmOEacM5JtgBkYM48ncMHjkHfFY7HrJHZHUnXM4l05RQTpLFoOl/uIo2HQ==}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /turbo@1.10.16:
+    resolution: {integrity: sha512-2CEaK4FIuSZiP83iFa9GqMTQhroW2QryckVqUydmg4tx78baftTOS0O+oDAhvo9r9Nit4xUEtC1RAHoqs6ZEtg==}
+    hasBin: true
+    optionalDependencies:
+      turbo-darwin-64: 1.10.16
+      turbo-darwin-arm64: 1.10.16
+      turbo-linux-64: 1.10.16
+      turbo-linux-arm64: 1.10.16
+      turbo-windows-64: 1.10.16
+      turbo-windows-arm64: 1.10.16
+    dev: true
+
   /type-check@0.4.0:
     resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
     engines: {node: '>= 0.8.0'}
@@ -7084,3 +7378,7 @@ packages:
     resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
     engines: {node: '>=12.20'}
     dev: true
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json
index d70de2d01..2c15bc73d 100644
--- a/single-pool/js/packages/classic/package.json
+++ b/single-pool/js/packages/classic/package.json
@@ -10,7 +10,8 @@
     }
   },
   "scripts": {
-    "build": "rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./ts-fixup.sh",
+    "clean": "rm -rf dist/*",
+    "build": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./ts-fixup.sh",
     "lint": "eslint --max-warnings 0 .",
     "lint:fix": "eslint . --fix",
     "test": "sed -i '1s/.*/{ \"type\": \"module\",/' package.json && NODE_OPTIONS='--loader=tsx' ava ; ret=$?; sed -i '1s/.*/{/' package.json && exit $ret"
diff --git a/single-pool/js/packages/modern/package.json b/single-pool/js/packages/modern/package.json
index 77c43adcb..094874e00 100644
--- a/single-pool/js/packages/modern/package.json
+++ b/single-pool/js/packages/modern/package.json
@@ -10,7 +10,8 @@
     }
   },
   "scripts": {
-    "build": "rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./ts-fixup.sh",
+    "clean": "rm -fr dist/*",
+    "build": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./ts-fixup.sh",
     "lint": "eslint --max-warnings 0 .",
     "lint:fix": "eslint . --fix"
   },
diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json
index a132de6f5..a3ee5662f 100644
--- a/stake-pool/js/package.json
+++ b/stake-pool/js/package.json
@@ -3,12 +3,10 @@
   "version": "0.7.0",
   "description": "SPL Stake Pool Program JS API",
   "scripts": {
-    "build": "npm run clean && tsc && cross-env NODE_ENV=production rollup -c",
+    "build": "tsc && cross-env NODE_ENV=production rollup -c",
     "lint": "eslint --max-warnings 0 .",
     "lint:fix": "eslint . --fix",
     "test": "jest",
-    "test:watch": "jest --watch",
-    "test:cov": "jest --coverage",
     "clean": "rimraf ./dist"
   },
   "keywords": [],
diff --git a/token-lending/js/package.json b/token-lending/js/package.json
index 7d2f7d857..c3ef4aecd 100644
--- a/token-lending/js/package.json
+++ b/token-lending/js/package.json
@@ -22,9 +22,11 @@
         "lib"
     ],
     "scripts": {
-        "build": "rm -rf lib/* && tsc && rollup --configPlugin @rollup/plugin-typescript -c && rm lib/rollup.config.d.ts",
-        "deploy": "yarn docs && gh-pages -d docs -e token-lending",
-        "docs": "rm -rf docs/* && typedoc",
+        "clean": "rm -rf lib/*",
+        "clean:docs": "rm -rf docs/*",
+        "build": "tsc && rollup --configPlugin @rollup/plugin-typescript -c && rm lib/rollup.config.d.ts",
+        "deploy": "npm docs && gh-pages -d docs -e token-lending",
+        "docs": "typedoc",
         "lint": "eslint . --ext .ts --max-warnings 0 && prettier --check '**/*.{ts,js,json}'",
         "lint:fix": "eslint . --ext .ts --fix && prettier --write '**/*.{ts,js,json}'"
     },
diff --git a/token-swap/js/package.json b/token-swap/js/package.json
index 39e73c1db..5a583d63f 100644
--- a/token-swap/js/package.json
+++ b/token-swap/js/package.json
@@ -31,6 +31,7 @@
     "README.md"
   ],
   "scripts": {
+    "clean": "rm -rf lib/*",
     "build": "tsc --build --verbose tsconfig.all.json",
     "postbuild": "echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
     "test": "mocha test",
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 000000000..8fa5de6b1
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,25 @@
+{
+    "$schema": "https://turbo.build/schema.json",
+    "pipeline": {
+        "build": {
+            "dependsOn": ["^build"],
+            "outputs": ["dist/**", "lib/**"]
+        },
+        "clean": {},
+        "lint:fix": {
+            "inputs": ["src/**", "test/**"],
+            "outputs": []
+        },
+        "lint": {
+            "inputs": ["src/**", "test/**"],
+            "outputs": []
+        },
+        "test": {
+            "inputs": ["src/**", "test/**"],
+            "outputs": []
+        }
+    },
+    "remoteCache": {
+        "signature": true
+    }
+}


Test patch:
diff --git a/ci/js-test-stake-pool.sh b/ci/js-test-stake-pool.sh
index 941b23a58..4650f6597 100755
--- a/ci/js-test-stake-pool.sh
+++ b/ci/js-test-stake-pool.sh
@@ -4,11 +4,8 @@ set -ex
 cd "$(dirname "$0")/.."
 
 pnpm install
-
-(cd memo/js && pnpm build)
-(cd token/js && pnpm build)
+pnpm build
 
 cd stake-pool/js
 pnpm lint
-pnpm build
 pnpm test
diff --git a/ci/js-test-token-lending.sh b/ci/js-test-token-lending.sh
index 5a4861b23..8342a2ab4 100755
--- a/ci/js-test-token-lending.sh
+++ b/ci/js-test-token-lending.sh
@@ -5,10 +5,7 @@ cd "$(dirname "$0")/.."
 
 set -x
 pnpm install
-
-(cd memo/js && pnpm build)
-(cd token/js && pnpm build)
+pnpm build
 
 cd token-lending/js
 pnpm lint
-pnpm build
diff --git a/ci/js-test-token-metadata.sh b/ci/js-test-token-metadata.sh
index 82b826f6d..14c0fc472 100755
--- a/ci/js-test-token-metadata.sh
+++ b/ci/js-test-token-metadata.sh
@@ -5,10 +5,8 @@ cd "$(dirname "$0")/.."
 
 set -x
 pnpm install
-
-(cd libraries/type-length-value/js && pnpm build)
+pnpm build
 
 cd token-metadata/js
 pnpm lint
-pnpm build
 pnpm test
diff --git a/ci/js-test-token-swap.sh b/ci/js-test-token-swap.sh
index 7972da246..d49c7636d 100755
--- a/ci/js-test-token-swap.sh
+++ b/ci/js-test-token-swap.sh
@@ -5,13 +5,10 @@ cd "$(dirname "$0")/.."
 source ./ci/solana-version.sh install
 
 pnpm install
-
-(cd memo/js && pnpm build)
-(cd token/js && pnpm build)
+pnpm build
 
 cd token-swap/js
 pnpm lint
-pnpm build
 pnpm start-with-test-validator
 (cd ../../target/deploy && mv spl_token_swap_production.so spl_token_swap.so)
 SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" pnpm start-with-test-validator
diff --git a/ci/js-test-token.sh b/ci/js-test-token.sh
index e1b082490..3f71c204b 100755
--- a/ci/js-test-token.sh
+++ b/ci/js-test-token.sh
@@ -6,10 +6,8 @@ source ./ci/solana-version.sh install
 
 set -x
 pnpm install
-
-(cd memo/js && pnpm build)
+pnpm build
 
 cd token/js
 pnpm lint
-pnpm build
 pnpm test