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" }