diff --git a/.env b/.env index 60a2d2a26..f60921e48 100644 --- a/.env +++ b/.env @@ -1,2 +1,6 @@ -REACT_APP_VERSION=$npm_package_version -REACT_APP_NAME=$npm_package_name +VITE_VERSION=$npm_package_version +VITE_NAME=$npm_package_name +VITE_FULL_URL=/ +VITE_SANITY_PROJECT=ajwvhvgo +VITE_SANITY_DATASET=apps +VITE_SANITY_PREVIEW_DATASET=apps-preview \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..cacd0111b --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,63 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + "eslint:recommended", + // We should switch to recommended-type-checked but there are many issues to review + "plugin:@typescript-eslint/recommended", + //"plugin:@typescript-eslint/recommended-type-checked", + "plugin:react-hooks/recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + ], + ignorePatterns: [ + "dist", + ".eslintrc.cjs", + "deployment.cjs", + "bin/**/*.js", + "bootstrap-template.js", + "playwright.config.ts", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + project: ["./tsconfig.json", "./tsconfig.node.json"], + tsconfigRootDir: __dirname, + }, + plugins: ["react-refresh"], + settings: { + react: { + version: "18", + }, + }, + rules: { + // More trouble than it's worth + "react/no-unescaped-entities": "off", + // False positives from library imports from Chakra UI + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: false, + }, + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + // Let's remove e from here + caughtErrorsIgnorePattern: "^_|e", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + // Temporary, new rules on Vite migration that are widely flouted + "@typescript-eslint/no-explicit-any": "off", + "prefer-const": "off", + "react/display-name": "off", + }, +}; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c74d395cb..5f5797c28 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ concurrency: jobs: build: + timeout-minutes: 15 runs-on: ubuntu-latest permissions: contents: read @@ -22,13 +23,13 @@ jobs: PRODUCTION_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2 STAGING_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2 REVIEW_CLOUDFRONT_DISTRIBUTION_ID: E3267W09ZJHQG9 - REACT_APP_FOUNDATION_BUILD: ${{ github.repository_owner == 'microbit-foundation' }} + VITE_FOUNDATION_BUILD: ${{ github.repository_owner == 'microbit-foundation' }} steps: # Note: This workflow disables deployment steps and micro:bit branding installation on forks. - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20.x cache: "npm" @@ -36,31 +37,33 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.2.0-dev.23 @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1 + - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.2.0-dev.43 @microbit-foundation/website-deploy-aws@0.6.0 @microbit-foundation/website-deploy-aws-config@0.9.0 @microbit-foundation/circleci-npm-package-versioner@1 if: github.repository_owner == 'microbit-foundation' env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: node ./bin/print-ci-env-stage.js >> $GITHUB_ENV - - run: node ./bin/print-ci-env-public-url.js >> $GITHUB_ENV + - run: node ./bin/print-ci-env-stage.cjs >> $GITHUB_ENV + - run: node ./bin/print-ci-env-public-url.cjs >> $GITHUB_ENV - run: npm run ci:update-version if: github.repository_owner == 'microbit-foundation' - run: npm run ci env: - REACT_APP_GA_COOKIE_PREFIX: ${{ secrets.REACT_APP_GA_COOKIE_PREFIX }} - REACT_APP_GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }} - REACT_APP_SENTRY_DSN: ${{ secrets.REACT_APP_SENTRY_DSN }} - - run: mkdir -p /tmp/app${PUBLIC_URL} && cp -r build/* /tmp/app${PUBLIC_URL} && npx serve --no-clipboard -l 3000 /tmp/app & + VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + - run: mkdir -p /tmp/app${BASE_URL} && cp -r build/* /tmp/app${BASE_URL} && npx serve --no-clipboard -l 3000 /tmp/app & if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' - run: curl --insecure -4 --retry 7 --retry-connrefused http://localhost:3000 1>/dev/null if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' - - run: npm run test:e2e:headless + - name: Run Playwright tests if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' + uses: docker://mcr.microsoft.com/playwright:v1.42.1-jammy + with: + args: npx playwright test - name: Store reports if: (env.STAGE == 'REVIEW' || env.STAGE == 'STAGING') && failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: reports path: reports/ + retention-days: 3 - run: npm run deploy if: github.repository_owner == 'microbit-foundation' && (env.STAGE == 'REVIEW' || success()) env: diff --git a/.gitignore b/.gitignore index be4846622..232c2e487 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ __pycache__ *.pyc /reports/* /crowdin/* +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/README.md b/README.md index f73bbd801..5994c9d56 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,18 @@ Getting up and running: ### `npm start` -Runs the app in the development mode.\ +Runs the app in the development mode. + Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -The page will reload if you make edits.\ -You will also see any lint errors in the console. +The page will reload if you make edits. + +This does not show TypeScript or lint errors. +Use the eslint plugin for your editor and consider also running `npm run typecheck:watch` to see full type checking errors. ### `npm test` -Launches the test runner in the interactive watch mode.\ +Launches the [test runner](https://vitest.dev/) in interactive mode (unless the `CI` environment variable is defined). See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. If you have a connected micro:bit device, then setting the environment variable `TEST_MODE_DEVICE=1` will enable additional tests that will connect to your micro:bit. The tests will overwrite programs and data on the micro:bit. @@ -51,14 +54,10 @@ These are excluded from the normal test run. The tests expect the app to already be running on http://localhost:3000, for example via `npm start`. -We use [Puppeteer](https://pptr.dev/) and the helpers provided by [Testing Library](https://testing-library.com/docs/pptr-testing-library/intro/). +We use [Playwright](https://playwright.dev/). The CI tests run these end-to-end tests against a production build. -### `npm run test:all --testPathPattern autocomplete` - -An example of how to use jest options to filter to a specific subset of the tests (e2e or unit). - ### `npm run build` Builds the app for production to the `build` folder.\ @@ -68,11 +67,11 @@ It correctly bundles React in production mode and optimizes the build for the be Most users should use the supported Foundation deployment at https://python.microbit.org/ -The editor is deployed by [CircleCI](https://circleci.com/gh/microbit-foundation/python-editor-v3). +The editor is deployed by [GitHub actions](https://github.com/microbit-foundation/python-editor-v3/actions). The `main` branch is deployed to https://python.microbit.org/v/beta on each push. -Other branches (e.g. for PRs) are deployed to https://review-python-editor-v3.microbit.org/{branch}. Special characters in the branch name are replaced by hyphens. +Other branches (e.g. for PRs) are deployed to https://review-python-editor-v3.microbit.org/{branch}. Special characters in the branch name are replaced by hyphens. Deployments will not run in forks. ## License diff --git a/bin/crowdin-convert.js b/bin/crowdin-convert.cjs similarity index 100% rename from bin/crowdin-convert.js rename to bin/crowdin-convert.cjs diff --git a/bin/fix-licensing-headers.js b/bin/fix-licensing-headers.cjs similarity index 100% rename from bin/fix-licensing-headers.js rename to bin/fix-licensing-headers.cjs diff --git a/bin/print-ci-env-public-url.cjs b/bin/print-ci-env-public-url.cjs new file mode 100644 index 000000000..0b7e6c80a --- /dev/null +++ b/bin/print-ci-env-public-url.cjs @@ -0,0 +1,17 @@ +#!/usr/bin/env node +let baseUrl; +if (process.env.GITHUB_REPOSITORY_OWNER === "microbit-foundation") { + // STAGE must be defined before this is imported + const { bucketPrefix, bucketName } = require("../deployment.cjs"); + baseUrl = `/${bucketPrefix}/`; + + const fullUrl = `https://${bucketName}${baseUrl}`; + // This is used for og:url and similar. Not quite right for review domain but we don't really care. + console.log(`VITE_FULL_URL=${fullUrl}`); +} else { + baseUrl = "/"; +} + +// Two env vars as BASE_URL seems to be blank when running jest even if we set it. +console.log(`BASE_URL=${baseUrl}`); +console.log(`E2E_BASE_URL=${baseUrl}`); diff --git a/bin/print-ci-env-public-url.js b/bin/print-ci-env-public-url.js deleted file mode 100644 index 1b0f6aa0b..000000000 --- a/bin/print-ci-env-public-url.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node -let url; -if (process.env.GITHUB_REPOSITORY_OWNER === "microbit-foundation") { - // STAGE must be defined before this is imported - const { bucketPrefix } = require("../deployment"); - url = `/${bucketPrefix}/`; -} else { - url = "/"; -} -// Two env vars as PUBLIC_URL seems to be blank when running jest even if we set it. -console.log(`PUBLIC_URL=${url}`); -console.log(`E2E_PUBLIC_URL=${url}`); diff --git a/bin/print-ci-env-stage.js b/bin/print-ci-env-stage.cjs similarity index 86% rename from bin/print-ci-env-stage.js rename to bin/print-ci-env-stage.cjs index 8fe0be3a4..8afa27508 100644 --- a/bin/print-ci-env-stage.js +++ b/bin/print-ci-env-stage.cjs @@ -10,4 +10,4 @@ if (ref === "refs/heads/main") { } console.log(`STAGE=${stage}`); -console.log(`REACT_APP_STAGE=${stage}`); +console.log(`VITE_STAGE=${stage}`); diff --git a/bin/tidy-lang.js b/bin/tidy-lang.cjs similarity index 100% rename from bin/tidy-lang.js rename to bin/tidy-lang.cjs diff --git a/bin/update-translations.sh b/bin/update-translations.sh index b38aee490..c2bbfc4ac 100755 --- a/bin/update-translations.sh +++ b/bin/update-translations.sh @@ -15,7 +15,7 @@ if [ $# -eq 0 ]; then exit 1 fi -languages="ca de fr es-ES ja ko nl zh-CN zh-TW" +languages="ca de fr es-ES ja ko nl zh-CN zh-TW lol" mkdir -p crowdin/translated for language in $languages; do diff --git a/deployment.js b/deployment.cjs similarity index 100% rename from deployment.js rename to deployment.cjs diff --git a/docs/tech-overview.md b/docs/tech-overview.md index fd159cbe7..3c58e556c 100644 --- a/docs/tech-overview.md +++ b/docs/tech-overview.md @@ -6,11 +6,13 @@ The document assumes some familiarity with the app as a user. [Try it out](http: ## User interface -The editor is written in [TypeScript](https://www.typescriptlang.org/) using [React](https://reactjs.org/). The best documentation for React at the time of writing is their [beta documentation](https://beta.reactjs.org/). +The editor is written in [TypeScript](https://www.typescriptlang.org/) using [React](https://reactjs.org/). -We use the [Chakra UI component library](https://chakra-ui.com/docs/getting-started) which provides a base set of accessible components. We're currently using Chakra UI 1.x. +We use the [Chakra UI component library](https://chakra-ui.com/docs/getting-started) which provides a base set of accessible components. We're currently using Chakra UI 2.x. -The project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). We're using [Craco](https://github.com/dilanx/craco) to override some parts of the Create React App configuration. +The project is bundled using [Vite](https://vitejs.dev/). The test runner is [Vitest](https://vitest.dev/) and we're using [eslint](https://eslint.org/). + +We use Prettier to format code. Please set this up to format before committing changes, ideally on save (there's [a VS Code plugin](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)). The UI is split into different areas by the [workbench component](../src/workbench/Workbench.tsx). This component manages layout including expand/collapse of the sidebar and simulator and is a good starting point to navigate the codebase. The main areas are: @@ -18,7 +20,7 @@ The UI is split into different areas by the [workbench component](../src/workben 2. The main editing area with the text editor for the current file, header with the project title and the project action bar with buttons for key interactions. When a micro:bit device is connected over WebUSB, the serial area appears between the text editor and the project actions. 3. The simulator on the right, with its own serial area and controls. -The branding that you see on the [Foundation deployment](https://python.microbit.org/v/3) is not Open Source and is managed in a private GitHub project. This is swapped in via a webpack alias for `theme-package` configured by Craco. +The branding that you see on the [Foundation deployment](https://python.microbit.org/v/3) is not Open Source and is managed in a private GitHub project. This is swapped in via an alias for `theme-package` in the Vite config. ## Connecting to the micro:bit via WebUSB, flashing and hex files @@ -30,7 +32,11 @@ To produce the correct data format to flash a micro:bit, we use the Foundation's ## The sidebar and educational content -The Reference and Ideas sidebar tabs show educational content that is managed in the Micro:bit Educational Foundation's content management system (CMS). The content is currently sourced live from the CMS. For non-localhost deploys this will require CORS configuration on our end. Please open an issue to discuss this. +The Reference and Ideas sidebar tabs show educational content that is managed in the Micro:bit Educational Foundation's content management system (Sanity CMS). This content is not Open Source. The content is sourced live from the CMS APIs. It will work for local development but non-Foundation deployments will see CORS errors. + +You can substitute your own content by setting `VITE_SANITY_PROJECT` and `VITE_SANITY_DATASET` environment variables, overriding the defaults in `.env`. The schemas we use for Sanity CMS are [packaged as a plugin in this GitHub project](https://github.com/microbit-foundation/sanity-plugin-python-editor-v3/). + +We also plan to explore adding Markdown documentation support as an alternative to Sanity CMS. You can follow the discussion on [#1160](https://github.com/microbit-foundation/python-editor-v3/issues/1160). The API tab shows detailed documentation of the MicroPython API for users who need more detail than the curated content in the Reference tab provides. The API tab content is generated at runtime from the bundled type stubs for MicroPython. We do this using an enhancement to the Foundation's fork of Pyright. For more details see [Python code intelligence](#python-code-intelligence). diff --git a/public/index.html b/index.html similarity index 52% rename from public/index.html rename to index.html index 00ae47ae9..9037b7050 100644 --- a/public/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + micro:bit Python Editor @@ -11,26 +11,43 @@ property="og:description" content="Built by the Micro:bit Educational Foundation and the global Python Community." /> - - - <% if (process.env.REACT_APP_FOUNDATION_BUILD === 'true') { %> - - + + <% if (typeof VITE_FOUNDATION_BUILD !== 'undefined' && VITE_FOUNDATION_BUILD + === 'true') { %> + + + <% } %> +