diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2e0db42443..db7f9a27af 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,7 +39,7 @@ jobs:
node-version-file: applications/browser-extension/package.json
cache: npm
- run: npm ci
- - run: npm run test --workspace=applications/browser-extension -- --coverage
+ - run: npm run test --workspaces -- --coverage
- uses: actions/upload-artifact@v4
with:
name: extension-test-coverage
@@ -151,7 +151,7 @@ jobs:
node-version-file: applications/browser-extension/package.json
cache: npm
- run: npm ci
- - run: npm run build:typescript --workspace=applications/browser-extension
+ - run: npm run build:typecheck --workspaces
lint:
runs-on: ubuntu-latest
diff --git a/applications/browser-extension/package.json b/applications/browser-extension/package.json
index 36048a6d18..9432b8eb1e 100644
--- a/applications/browser-extension/package.json
+++ b/applications/browser-extension/package.json
@@ -16,9 +16,9 @@
"watch:webpack": "ENV_FILE='.env.development' webpack ${HMR:-watch} --mode development",
"watch:webpack-hmr": "HMR=serve npm run watch:webpack",
"watch:typescript": "tsc --noEmit --watch",
- "build": "concurrently npm:build:webpack npm:build:typescript -r",
+ "build": "concurrently npm:build:webpack npm:build:typecheck -r",
"build:webpack": "NODE_OPTIONS=--max_old_space_size=8192 webpack --mode production",
- "build:typescript": "tsc --noEmit",
+ "build:typecheck": "tsc --noEmit",
"generate:headers": "npm run test -- src/development/headers.test.ts --no-silent",
"storybook": "storybook dev -p 6006 -s public",
"build-storybook": "storybook build",
@@ -27,11 +27,6 @@
"dead-code:base": "echo 'skipping knip until fixed in next changeset. See: https://knip.dev/guides/handling-issues#missing-binaries'",
"dead-code:prod": "npm run dead-code -- --production"
},
- "engine-strict": true,
- "engines": {
- "node": "20.12.0",
- "npm": "10.5.0"
- },
"author": "Todd Schiller",
"license": "AGPL-3.0",
"repository": "https://github.com/pixiebrix/pixiebrix-extension",
@@ -50,6 +45,7 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@mozilla/readability": "^0.5.0",
"@pixiebrix/jq-web": "^0.5.1",
+ "@pixiebrix/utils": "*",
"@reduxjs/toolkit": "^1.9.7",
"@rjsf/bootstrap-4": "^5.22.3",
"@rjsf/core": "^5.22.3",
diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.test.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.test.ts
index 7075c8d267..f18fe019b4 100644
--- a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.test.ts
+++ b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.test.ts
@@ -132,6 +132,7 @@ describe("DataPanel state", () => {
describe("Add/Remove Bricks", () => {
let editor: EditorState;
+ let consoleErrorSpy: jest.SpyInstance;
const source = formStateFactory({
formStateConfig: {
@@ -155,6 +156,12 @@ describe("Add/Remove Bricks", () => {
initialState,
actions.addModComponentFormState(source),
);
+
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
+ });
+
+ afterEach(() => {
+ consoleErrorSpy.mockRestore();
});
test("Add Brick", async () => {
@@ -178,6 +185,42 @@ describe("Add/Remove Bricks", () => {
).toBeArrayOfSize(initialBricks.length + 1);
});
+ test("Add Brick - bad pipeline path error", async () => {
+ // Add a Brick
+ expect(() =>
+ editorSlice.reducer(
+ editor,
+ actions.addNode({
+ block: standardBrick,
+ pipelinePath: "badPath.to.modComponent",
+ pipelineIndex: 0,
+ }),
+ ),
+ ).toThrowError(
+ "Invalid pipeline path for mod component form state: badPath.to.modComponent",
+ );
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
+ "Invalid pipeline path for mod component form state: %s",
+ "badPath.to.modComponent",
+ {
+ block: {
+ config: {},
+ id: "test/teapot",
+ instanceId: "00000001-0000-4000-A000-000000000000",
+ outputKey: "teapotOutput",
+ },
+ element: expect.any(Object),
+ invalidPath: {
+ invalidPath: "badPath",
+ values: expect.any(Object),
+ },
+ pipelineIndex: 0,
+ pipelinePath: "badPath.to.modComponent",
+ },
+ );
+ });
+
test("Remove Brick with Integration Dependency", async () => {
// Get initial bricks and integration dependencies
const initialBricks =
diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts
index 267eaff18b..502524efcb 100644
--- a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts
+++ b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts
@@ -34,7 +34,7 @@ import { uuidv4 } from "@/types/helpers";
import { cloneDeep, compact, get, pull, uniq } from "lodash";
import { DataPanelTabKey } from "@/pageEditor/tabs/editTab/dataPanel/dataPanelTypes";
import { type TreeExpandedState } from "@/components/jsonTree/JsonTree";
-import { getInvalidPath } from "@/utils/debugUtils";
+import { getInvalidPath } from "@pixiebrix/utils/src/debugUtils";
import {
selectActiveBrickConfigurationUIState,
selectActiveBrickPipelineUIState,
diff --git a/libraries/utils/jest.config.js b/libraries/utils/jest.config.js
new file mode 100644
index 0000000000..e294fe25ae
--- /dev/null
+++ b/libraries/utils/jest.config.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 PixieBrix, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+const config = {
+ silent: true,
+ modulePaths: ["/src"],
+ moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
+ transform: {
+ "\\.[jt]sx?$": "@swc/jest",
+ },
+ moduleNameMapper: {
+ "^@/(.*)$": "/src/$1",
+ },
+};
+
+module.exports = config;
diff --git a/libraries/utils/package.json b/libraries/utils/package.json
new file mode 100644
index 0000000000..59eafdb758
--- /dev/null
+++ b/libraries/utils/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@pixiebrix/utils",
+ "version": "1.0.0",
+ "description": "PixieBrix Utility Library",
+ "scripts": {
+ "test": "TZ=UTC jest",
+ "lint": "echo 'missing eslint'",
+ "build": "echo 'buildless package'",
+ "build:typecheck": "tsc --noEmit"
+ },
+ "license": "AGPL-3.0",
+ "repository": "https://github.com/pixiebrix/pixiebrix-extension",
+ "dependencies": {
+ "formik": "^2.4.6"
+ },
+ "devDependencies": {
+ "@swc/core": "^1.7.42",
+ "@swc/jest": "^0.2.37",
+ "eslint": "^8.57.0",
+ "typescript": "^5.6.3"
+ }
+}
diff --git a/applications/browser-extension/src/utils/debugUtils.test.ts b/libraries/utils/src/debugUtils.test.ts
similarity index 95%
rename from applications/browser-extension/src/utils/debugUtils.test.ts
rename to libraries/utils/src/debugUtils.test.ts
index 026d7de2c1..be1854c5bd 100644
--- a/applications/browser-extension/src/utils/debugUtils.test.ts
+++ b/libraries/utils/src/debugUtils.test.ts
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-import { getInvalidPath } from "@/utils/debugUtils";
+import { getInvalidPath } from "@/debugUtils";
describe("getInvalidPath", () => {
it("returns invalid path", () => {
diff --git a/applications/browser-extension/src/utils/debugUtils.ts b/libraries/utils/src/debugUtils.ts
similarity index 98%
rename from applications/browser-extension/src/utils/debugUtils.ts
rename to libraries/utils/src/debugUtils.ts
index 4ce97421fd..313185d726 100644
--- a/applications/browser-extension/src/utils/debugUtils.ts
+++ b/libraries/utils/src/debugUtils.ts
@@ -28,7 +28,7 @@ type InvalidPathInformation = {
* @param path period separated path
*/
export function getInvalidPath(
- value: UnknownObject,
+ value: Record,
path: string,
): InvalidPathInformation {
const parts = path.split(".");
diff --git a/libraries/utils/tsconfig.json b/libraries/utils/tsconfig.json
new file mode 100644
index 0000000000..80838c478f
--- /dev/null
+++ b/libraries/utils/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ // You can see the full details at https://github.com/sindresorhus/tsconfig/blob/main/tsconfig.json
+ // Note: `strict: true` enables many flags that aren’t explicitly listed in that file
+ "extends": "@sindresorhus/tsconfig",
+ "compilerOptions": {
+ "sourceMap": true,
+ "module": "esnext",
+ "target": "es2023",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "baseUrl": ".",
+ "outDir": null,
+ "declaration": false,
+
+ // TODO: Drop these lines to make TS stricter https://github.com/pixiebrix/pixiebrix-extension/issues/775
+ "strictFunctionTypes": false,
+ "noPropertyAccessFromIndexSignature": false,
+ "noImplicitReturns": false,
+ "noUnusedParameters": false,
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/package-lock.json b/package-lock.json
index a880df34ce..3598dcac4b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,6 +35,7 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@mozilla/readability": "^0.5.0",
"@pixiebrix/jq-web": "^0.5.1",
+ "@pixiebrix/utils": "*",
"@reduxjs/toolkit": "^1.9.7",
"@rjsf/bootstrap-4": "^5.22.3",
"@rjsf/core": "^5.22.3",
@@ -303,6 +304,24 @@
"version": "0.0.1",
"extraneous": true
},
+ "libraries/utils": {
+ "name": "@pixiebrix/utils",
+ "version": "1.0.0",
+ "license": "AGPL-3.0",
+ "dependencies": {
+ "formik": "^2.4.6"
+ },
+ "devDependencies": {
+ "@swc/core": "^1.7.42",
+ "@swc/jest": "^0.2.37",
+ "eslint": "^8.57.0",
+ "typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": "20.12.0",
+ "npm": "10.5.0"
+ }
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
@@ -4476,6 +4495,10 @@
"resolved": "https://registry.npmjs.org/@pixiebrix/jq-web/-/jq-web-0.5.1.tgz",
"integrity": "sha512-q99g6J8UVsS7+pNlxyl01eDnUtHuYC9GPgKUXntpfs7akuqhkes9AZjC678LxjTC5BkjFtfOQazFQoyW/9/B/w=="
},
+ "node_modules/@pixiebrix/utils": {
+ "resolved": "libraries/utils",
+ "link": true
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
diff --git a/package.json b/package.json
index db23fd34eb..da2ef05242 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,17 @@
"scripts": {
"test": "npm run test --workspaces",
"lint": "npm run lint --workspaces",
- "build": "npm run build --workspaces"
+ "build": "npm run build --workspaces",
+ "build:typecheck": "npm run build:typecheck --workspaces"
},
"author": "Todd Schiller",
"license": "AGPL-3.0",
"repository": "https://github.com/pixiebrix/pixiebrix-extension",
+ "engine-strict": true,
+ "engines": {
+ "node": "20.12.0",
+ "npm": "10.5.0"
+ },
"devDependencies": {
"prettier": "3.1.0"
}