diff --git a/.github/runtime-gate.md b/.github/runtime-gate.md
new file mode 100644
index 0000000..eb94ec9
--- /dev/null
+++ b/.github/runtime-gate.md
@@ -0,0 +1,14 @@
+# test: notes for the runtime explorer
+
+**What it is.** A Create React App 2 starter kept for reproducing bugs, now
+built with Vite. It renders a single page: a header with the spinning React
+logo and "Welcome to React", and a line of intro text. Bootstrap 3 (Sass),
+Font Awesome 4 and jQuery are bundled, and a service worker is registered
+in production builds.
+
+**Flows worth trying.**
+1. Load `/` and check that the header, logo and intro text render.
+2. Resize to phone width and check nothing overflows.
+3. Reload, so the service worker serves the page, and check it still renders.
+
+**No logins, forms or data.** There is nothing else to explore.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..e9b9105
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,27 @@
+# The repo's own test suite (Vitest + Testing Library), on the latest Current
+# Node from .nvmrc, via the shared node-ci workflow. There is no lint or
+# typecheck script, and the production build and the browser checks are in
+# runtime.yml, so only `npm test` runs here.
+name: ci
+
+on:
+ pull_request:
+ push:
+ branches: [master]
+ workflow_dispatch:
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ uses: rishitank/.github/.github/workflows/node-ci.yml@v1
+ with:
+ lint: ""
+ typecheck: ""
+ test: npm test
+ build: ""
diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml
new file mode 100644
index 0000000..2b44999
--- /dev/null
+++ b/.github/workflows/runtime.yml
@@ -0,0 +1,63 @@
+# Runtime gate: boots the app and uses it in a real browser before a change
+# (a Renovate update included) can merge. Fails on runtime errors: uncaught
+# exceptions, console errors, React hydration errors, failed same-origin
+# requests, 4xx/5xx, blank pages, crash screens. Advisory on top: screenshot
+# diff against the last master run, and an AI explorer (Gemini free
+# tier, GEMINI_API_KEY secret) that uses the app like a person for major or
+# framework updates, the `ai-explore` label, the weekly schedule and manual
+# runs. See docs/runtime-gate.md in rishitank/.github.
+name: runtime
+
+on:
+ pull_request:
+ push:
+ branches: [master]
+ schedule:
+ - cron: "43 5 * * 4"
+ workflow_dispatch:
+
+concurrency:
+ group: runtime-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ runtime:
+ name: runtime
+ uses: rishitank/.github/.github/workflows/runtime-gate.yml@f5b19aff6eac60563a34e7a2ddc057e75766235c
+ permissions:
+ contents: read
+ actions: read
+ pull-requests: write
+ with:
+ # Vite build on the latest Current Node (.nvmrc is `node`), installed
+ # with `npm ci` from package-lock.json.
+ mode: static
+ build: npm run build
+ static-dir: dist
+ # The generated service worker answers every navigation with
+ # index.html, so serve unknown paths the same way.
+ spa: true
+ port: 3000
+ # No sign-in: the app is the static CRA starter page with no accounts or
+ # login page, so login-path stays unset and there is one crawl.
+ secrets:
+ gemini-api-key: ${{ secrets.GEMINI_API_KEY }}
+
+ # Weekly/manual runs only: saves the paths the explorer completed to
+ # .github/runtime-gate-flows.json through a reviewed PR; the runtime job then
+ # replays them on every PR.
+ flows:
+ needs: runtime
+ if: ${{ !cancelled() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
+ uses: rishitank/.github/.github/workflows/runtime-gate-flows.yml@f5b19aff6eac60563a34e7a2ddc057e75766235c
+ permissions:
+ contents: write
+ pull-requests: write
+ actions: write
+ with:
+ ci-workflows: |
+ runtime.yml
+ ci.yml
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
new file mode 100644
index 0000000..6a603b0
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,26 @@
+# Vulnerable dependencies (osv-scanner), leaked secrets (gitleaks, full
+# history) and the ecosystem's own audit, via the shared workflow.
+name: security
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ schedule:
+ - cron: "40 4 * * 1"
+ workflow_dispatch:
+
+concurrency:
+ group: security-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ security:
+ uses: rishitank/.github/.github/workflows/security.yml@v1
+ with:
+ ecosystem: node
+ # osv-scanner scans package-lock.json; the audit's setup-node reads
+ # .nvmrc (`node`, the latest Current release), the shared default.
diff --git a/.github/workflows/workflows-lint.yml b/.github/workflows/workflows-lint.yml
new file mode 100644
index 0000000..f927d2f
--- /dev/null
+++ b/.github/workflows/workflows-lint.yml
@@ -0,0 +1,18 @@
+# actionlint over this repo's workflows. Catches bad `uses:` refs, undefined
+# contexts and shell bugs in `run:` blocks before they cost a red build.
+name: workflows-lint
+
+on:
+ pull_request:
+ paths: [".github/workflows/**"]
+ push:
+ branches: [master]
+ paths: [".github/workflows/**"]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ actionlint:
+ uses: rishitank/.github/.github/workflows/workflows-lint.yml@v1
diff --git a/.gitignore b/.gitignore
index d30f40e..2028e0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
# production
/build
+/dist
# misc
.DS_Store
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..64f5a0a
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+node
diff --git a/config/env.js b/config/env.js
deleted file mode 100644
index 8b39b6c..0000000
--- a/config/env.js
+++ /dev/null
@@ -1,90 +0,0 @@
-'use strict';
-
-const fs = require('fs');
-const path = require('path');
-const paths = require('./paths');
-
-// Make sure that including paths.js after env.js will read .env variables.
-delete require.cache[require.resolve('./paths')];
-
-const NODE_ENV = process.env.NODE_ENV;
-if (!NODE_ENV) {
- throw new Error(
- 'The NODE_ENV environment variable is required but was not specified.'
- );
-}
-
-// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
-var dotenvFiles = [
- `${paths.dotenv}.${NODE_ENV}.local`,
- `${paths.dotenv}.${NODE_ENV}`,
- // Don't include `.env.local` for `test` environment
- // since normally you expect tests to produce the same
- // results for everyone
- NODE_ENV !== 'test' && `${paths.dotenv}.local`,
- paths.dotenv,
-].filter(Boolean);
-
-// Load environment variables from .env* files. Suppress warnings using silent
-// if this file is missing. dotenv will never modify any environment variables
-// that have already been set.
-// https://github.com/motdotla/dotenv
-dotenvFiles.forEach(dotenvFile => {
- if (fs.existsSync(dotenvFile)) {
- require('dotenv').config({
- path: dotenvFile,
- });
- }
-});
-
-// We support resolving modules according to `NODE_PATH`.
-// This lets you use absolute paths in imports inside large monorepos:
-// https://github.com/facebookincubator/create-react-app/issues/253.
-// It works similar to `NODE_PATH` in Node itself:
-// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
-// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
-// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
-// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
-// We also resolve them to make sure all tools using them work consistently.
-const appDirectory = fs.realpathSync(process.cwd());
-process.env.NODE_PATH = (process.env.NODE_PATH || '')
- .split(path.delimiter)
- .filter(folder => folder && !path.isAbsolute(folder))
- .map(folder => path.resolve(appDirectory, folder))
- .join(path.delimiter);
-
-// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
-// injected into the application via DefinePlugin in Webpack configuration.
-const REACT_APP = /^REACT_APP_/i;
-
-function getClientEnvironment(publicUrl) {
- const raw = Object.keys(process.env)
- .filter(key => REACT_APP.test(key))
- .reduce(
- (env, key) => {
- env[key] = process.env[key];
- return env;
- },
- {
- // Useful for determining whether we’re running in production mode.
- // Most importantly, it switches React into the correct mode.
- NODE_ENV: process.env.NODE_ENV || 'development',
- // Useful for resolving the correct path to static assets in `public`.
- // For example,
.
- // This should only be used as an escape hatch. Normally you would put
- // images into the `src` and `import` them in code to get their paths.
- PUBLIC_URL: publicUrl,
- }
- );
- // Stringify all values so we can feed into Webpack DefinePlugin
- const stringified = {
- 'process.env': Object.keys(raw).reduce((env, key) => {
- env[key] = JSON.stringify(raw[key]);
- return env;
- }, {}),
- };
-
- return { raw, stringified };
-}
-
-module.exports = getClientEnvironment;
diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js
deleted file mode 100644
index f1534f6..0000000
--- a/config/jest/cssTransform.js
+++ /dev/null
@@ -1,14 +0,0 @@
-'use strict';
-
-// This is a custom Jest transformer turning style imports into empty objects.
-// http://facebook.github.io/jest/docs/tutorial-webpack.html
-
-module.exports = {
- process() {
- return 'module.exports = {};';
- },
- getCacheKey() {
- // The output is always the same.
- return 'cssTransform';
- },
-};
diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js
deleted file mode 100644
index ffce0da..0000000
--- a/config/jest/fileTransform.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-const path = require('path');
-
-// This is a custom Jest transformer turning file imports into filenames.
-// http://facebook.github.io/jest/docs/tutorial-webpack.html
-
-module.exports = {
- process(src, filename) {
- return `module.exports = ${JSON.stringify(path.basename(filename))};`;
- },
-};
diff --git a/config/paths.js b/config/paths.js
deleted file mode 100644
index 6d16efc..0000000
--- a/config/paths.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use strict';
-
-const path = require('path');
-const fs = require('fs');
-const url = require('url');
-
-// Make sure any symlinks in the project folder are resolved:
-// https://github.com/facebookincubator/create-react-app/issues/637
-const appDirectory = fs.realpathSync(process.cwd());
-const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
-
-const envPublicUrl = process.env.PUBLIC_URL;
-
-function ensureSlash(path, needsSlash) {
- const hasSlash = path.endsWith('/');
- if (hasSlash && !needsSlash) {
- return path.substr(path, path.length - 1);
- } else if (!hasSlash && needsSlash) {
- return `${path}/`;
- } else {
- return path;
- }
-}
-
-const getPublicUrl = appPackageJson =>
- envPublicUrl || require(appPackageJson).homepage;
-
-// We use `PUBLIC_URL` environment variable or "homepage" field to infer
-// "public path" at which the app is served.
-// Webpack needs to know it to put the right
+