@codyswann/lisa 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/.claude/hooks/enforce-plan-rules.sh +15 -0
- package/all/copy-overwrite/.claude/hooks/track-plan-sessions.sh +155 -0
- package/all/copy-overwrite/.claude/rules/plan.md +29 -0
- package/all/copy-overwrite/.claude/settings.json +30 -0
- package/all/copy-overwrite/CLAUDE.md +2 -9
- package/cdk/copy-overwrite/tsconfig.eslint.json +2 -1
- package/cdk/create-only/cdk.json +23 -0
- package/expo/copy-overwrite/.claude/skills/owasp-zap/SKILL.md +56 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +5 -10
- package/expo/copy-overwrite/.github/workflows/zap-baseline.yml +107 -0
- package/expo/copy-overwrite/.zap/baseline.conf +36 -0
- package/expo/copy-overwrite/jest.expo.ts +59 -13
- package/expo/copy-overwrite/knip.json +1 -1
- package/expo/copy-overwrite/scripts/zap-baseline.sh +92 -0
- package/expo/copy-overwrite/tsconfig.eslint.json +2 -1
- package/expo/copy-overwrite/tsconfig.expo.json +7 -0
- package/expo/copy-overwrite/tsconfig.json +3 -1
- package/expo/create-only/.github/workflows/ci.yml +8 -0
- package/expo/create-only/babel.config.js +27 -0
- package/expo/create-only/jest.config.local.ts +34 -0
- package/expo/create-only/jest.config.react-native-mock.js +88 -0
- package/expo/create-only/jest.setup.pre.js +106 -0
- package/expo/create-only/jest.setup.ts +118 -0
- package/expo/create-only/tsconfig.local.json +1 -7
- package/expo/package-lisa/package.lisa.json +4 -2
- package/nestjs/copy-overwrite/.github/workflows/zap-baseline.yml +123 -0
- package/nestjs/copy-overwrite/.zap/baseline.conf +39 -0
- package/nestjs/copy-overwrite/scripts/zap-baseline.sh +99 -0
- package/nestjs/copy-overwrite/tsconfig.eslint.json +10 -0
- package/nestjs/copy-overwrite/tsconfig.nestjs.json +4 -1
- package/nestjs/create-only/.github/workflows/ci.yml +8 -0
- package/nestjs/package-lisa/package.lisa.json +2 -1
- package/package.json +1 -1
- package/typescript/copy-contents/.husky/pre-push +5 -1
- package/typescript/copy-overwrite/.claude/commands/security/zap-scan.md +12 -0
- package/typescript/copy-overwrite/.claude/settings.json +10 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +100 -5
- package/typescript/copy-overwrite/eslint.base.ts +1 -1
- package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
- package/typescript/copy-overwrite/jest.base.ts +1 -0
- package/typescript/copy-overwrite/tsconfig.eslint.json +3 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# OWASP ZAP Baseline Scan — Expo Web Export
|
|
3
|
+
# Builds the Expo web export, serves it locally, and runs a ZAP baseline scan via Docker.
|
|
4
|
+
# Outputs an HTML report to zap-report.html in the project root.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
TARGET_URL="${ZAP_TARGET_URL:-http://host.docker.internal:3000}"
|
|
10
|
+
ZAP_RULES_FILE="${ZAP_RULES_FILE:-.zap/baseline.conf}"
|
|
11
|
+
REPORT_FILE="zap-report.html"
|
|
12
|
+
|
|
13
|
+
cd "$PROJECT_ROOT"
|
|
14
|
+
|
|
15
|
+
# Verify Docker is available
|
|
16
|
+
if ! command -v docker &> /dev/null; then
|
|
17
|
+
echo "Error: Docker is required but not installed."
|
|
18
|
+
echo "Install Docker from https://docs.docker.com/get-docker/"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if ! docker info &> /dev/null 2>&1; then
|
|
23
|
+
echo "Error: Docker daemon is not running."
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Detect package manager
|
|
28
|
+
if [ -f "bun.lockb" ]; then
|
|
29
|
+
PKG_MGR="bun"
|
|
30
|
+
elif [ -f "yarn.lock" ]; then
|
|
31
|
+
PKG_MGR="yarn"
|
|
32
|
+
elif [ -f "pnpm-lock.yaml" ]; then
|
|
33
|
+
PKG_MGR="pnpm"
|
|
34
|
+
else
|
|
35
|
+
PKG_MGR="npm"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
echo "==> Building Expo web export..."
|
|
39
|
+
npx expo export --platform web
|
|
40
|
+
|
|
41
|
+
echo "==> Starting static server on port 3000..."
|
|
42
|
+
npx serve dist -l 3000 &
|
|
43
|
+
SERVER_PID=$!
|
|
44
|
+
|
|
45
|
+
cleanup() {
|
|
46
|
+
echo "==> Cleaning up..."
|
|
47
|
+
if [ -n "${SERVER_PID:-}" ]; then
|
|
48
|
+
kill "$SERVER_PID" 2>/dev/null || true
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
trap cleanup EXIT
|
|
52
|
+
|
|
53
|
+
sleep 3
|
|
54
|
+
if ! curl -sf http://localhost:3000 > /dev/null 2>&1; then
|
|
55
|
+
echo "Error: Static server failed to start"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "==> Running OWASP ZAP baseline scan..."
|
|
60
|
+
ZAP_ARGS="-t $TARGET_URL"
|
|
61
|
+
|
|
62
|
+
if [ -f "$ZAP_RULES_FILE" ]; then
|
|
63
|
+
echo " Using rules file: $ZAP_RULES_FILE"
|
|
64
|
+
ZAP_ARGS="$ZAP_ARGS -c /zap/wrk/$(basename "$ZAP_RULES_FILE")"
|
|
65
|
+
MOUNT_RULES="-v $(dirname "$(realpath "$ZAP_RULES_FILE")"):/zap/wrk:ro"
|
|
66
|
+
else
|
|
67
|
+
MOUNT_RULES=""
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
docker run --rm \
|
|
71
|
+
--add-host=host.docker.internal:host-gateway \
|
|
72
|
+
-v "$(pwd)":/zap/wrk/:rw \
|
|
73
|
+
$MOUNT_RULES \
|
|
74
|
+
ghcr.io/zaproxy/zaproxy:stable \
|
|
75
|
+
zap-baseline.py $ZAP_ARGS \
|
|
76
|
+
-r "$REPORT_FILE" \
|
|
77
|
+
-J zap-report.json \
|
|
78
|
+
-w zap-report.md \
|
|
79
|
+
-l WARN || ZAP_EXIT=$?
|
|
80
|
+
|
|
81
|
+
echo ""
|
|
82
|
+
if [ -f "$REPORT_FILE" ]; then
|
|
83
|
+
echo "ZAP report saved to: $REPORT_FILE"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if [ "${ZAP_EXIT:-0}" -ne 0 ]; then
|
|
87
|
+
echo "ZAP found medium+ severity findings (exit code: $ZAP_EXIT)"
|
|
88
|
+
echo "Review $REPORT_FILE for details."
|
|
89
|
+
exit "$ZAP_EXIT"
|
|
90
|
+
else
|
|
91
|
+
echo "ZAP baseline scan passed — no medium+ severity findings."
|
|
92
|
+
fi
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
"strict": true,
|
|
5
5
|
"jsx": "react-native",
|
|
6
6
|
"baseUrl": "./",
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"declaration": false,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"paths": {
|
|
11
|
+
"@/graphql/*": ["./generated/*"],
|
|
12
|
+
"@/*": ["./*"]
|
|
13
|
+
},
|
|
7
14
|
"moduleSuffixes": [".ios", ".android", ".native", ".web", ""]
|
|
8
15
|
}
|
|
9
16
|
}
|
|
@@ -25,6 +25,14 @@ jobs:
|
|
|
25
25
|
node_version: '22.21.1'
|
|
26
26
|
package_manager: 'bun'
|
|
27
27
|
|
|
28
|
+
zap:
|
|
29
|
+
name: 🕷️ ZAP Baseline Scan
|
|
30
|
+
needs: [quality]
|
|
31
|
+
uses: ./.github/workflows/zap-baseline.yml
|
|
32
|
+
with:
|
|
33
|
+
node_version: '22.21.1'
|
|
34
|
+
package_manager: 'bun'
|
|
35
|
+
|
|
28
36
|
create_issue_on_failure:
|
|
29
37
|
name: 📌 Create Issue on Failure
|
|
30
38
|
needs: [quality]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Babel Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configures Babel for Expo with NativeWind JSX transform support.
|
|
5
|
+
* This file is create-only — Lisa will create it but never overwrite
|
|
6
|
+
* your customizations.
|
|
7
|
+
*
|
|
8
|
+
* @remarks Required by the `jest.expo.ts` babel-jest transform which
|
|
9
|
+
* uses a metro caller config. Without this file, babel-jest cannot
|
|
10
|
+
* resolve the correct presets for React Native compilation.
|
|
11
|
+
* @see https://docs.expo.dev/versions/latest/config/babel/
|
|
12
|
+
* @module babel.config
|
|
13
|
+
*/
|
|
14
|
+
module.exports = function (api) {
|
|
15
|
+
api.cache(true);
|
|
16
|
+
return {
|
|
17
|
+
presets: [
|
|
18
|
+
[
|
|
19
|
+
"babel-preset-expo",
|
|
20
|
+
{
|
|
21
|
+
jsxImportSource: "nativewind",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
"nativewind/babel",
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration - Project-Local Customizations
|
|
3
|
+
*
|
|
4
|
+
* Add project-specific Jest settings here. This file is create-only,
|
|
5
|
+
* meaning Lisa will create it but never overwrite your customizations.
|
|
6
|
+
*
|
|
7
|
+
* The Expo stack's `jest.expo.ts` provides haste, resolver, transform,
|
|
8
|
+
* and base coverage settings. This file should only contain settings
|
|
9
|
+
* that are project-specific or need to override the base config.
|
|
10
|
+
*
|
|
11
|
+
* @remarks `setupFiles` must define `__DEV__` and other React Native
|
|
12
|
+
* globals before any RN modules load — `jest.setup.pre.js` handles this.
|
|
13
|
+
* @see https://jestjs.io/docs/configuration
|
|
14
|
+
* @module jest.config.local
|
|
15
|
+
*/
|
|
16
|
+
import type { Config } from "jest";
|
|
17
|
+
|
|
18
|
+
const config: Config = {
|
|
19
|
+
setupFiles: ["<rootDir>/jest.setup.pre.js"],
|
|
20
|
+
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
|
|
21
|
+
|
|
22
|
+
// Path aliases matching tsconfig.expo.json paths
|
|
23
|
+
moduleNameMapper: {
|
|
24
|
+
"^@/(.*)$": "<rootDir>/$1",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
coveragePathIgnorePatterns: [
|
|
28
|
+
"/node_modules/",
|
|
29
|
+
"/generated/",
|
|
30
|
+
"\\.mock\\.(ts|tsx|js|jsx)$",
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default config;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native TurboModule Mock Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides mock implementations for React Native's TurboModule native
|
|
5
|
+
* modules used in the test environment. These mocks are loaded by
|
|
6
|
+
* `jest.setup.pre.js` via `__turboModuleProxy`.
|
|
7
|
+
*
|
|
8
|
+
* @remarks Projects should add additional module mocks as needed.
|
|
9
|
+
* Only the baseline modules required by React Native core are included
|
|
10
|
+
* here — project-specific native modules (e.g., camera, maps) should
|
|
11
|
+
* be added to the project's copy of this file.
|
|
12
|
+
* @see https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules
|
|
13
|
+
* @module jest.config.react-native-mock
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Dynamically read React Native version from package.json
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parses React Native version from package.json for PlatformConstants mock
|
|
22
|
+
* @returns The React Native version object with major, minor, and patch numbers
|
|
23
|
+
* @remarks Falls back to 0.81.4 if parsing fails — update the fallback
|
|
24
|
+
* when upgrading React Native
|
|
25
|
+
*/
|
|
26
|
+
const getReactNativeVersion = () => {
|
|
27
|
+
try {
|
|
28
|
+
const packageJson = JSON.parse(
|
|
29
|
+
fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
|
|
30
|
+
);
|
|
31
|
+
const version = packageJson.dependencies["react-native"];
|
|
32
|
+
// Handle version formats: "0.81.4", "^0.81.4", "~0.81.4"
|
|
33
|
+
const versionMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
34
|
+
if (versionMatch) {
|
|
35
|
+
return {
|
|
36
|
+
major: parseInt(versionMatch[1], 10),
|
|
37
|
+
minor: parseInt(versionMatch[2], 10),
|
|
38
|
+
patch: parseInt(versionMatch[3], 10),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
// Fallback to a default version if parsing fails
|
|
43
|
+
console.warn(
|
|
44
|
+
"Failed to parse React Native version from package.json:",
|
|
45
|
+
error
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
// Fallback version
|
|
49
|
+
return { major: 0, minor: 81, patch: 4 };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
PlatformConstants: {
|
|
54
|
+
getConstants: () => ({
|
|
55
|
+
reactNativeVersion: getReactNativeVersion(),
|
|
56
|
+
forceTouchAvailable: false,
|
|
57
|
+
osVersion: "14.4",
|
|
58
|
+
systemName: "iOS",
|
|
59
|
+
interfaceIdiom: "phone",
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
AppState: {
|
|
63
|
+
getConstants: () => ({ initialAppState: "active" }),
|
|
64
|
+
getCurrentAppState: () => Promise.resolve({ app_state: "active" }),
|
|
65
|
+
addListener: () => {},
|
|
66
|
+
addEventListener: () => {},
|
|
67
|
+
removeListeners: () => {},
|
|
68
|
+
removeEventListener: () => {},
|
|
69
|
+
currentState: "active",
|
|
70
|
+
},
|
|
71
|
+
Appearance: {
|
|
72
|
+
getConstants: () => ({ initialColorScheme: "light" }),
|
|
73
|
+
getColorScheme: () => "light",
|
|
74
|
+
setColorScheme: () => {},
|
|
75
|
+
addChangeListener: () => ({ remove: () => {} }),
|
|
76
|
+
removeChangeListener: () => {},
|
|
77
|
+
addListener: () => ({ remove: () => {} }),
|
|
78
|
+
removeListeners: () => {},
|
|
79
|
+
},
|
|
80
|
+
DeviceInfo: {
|
|
81
|
+
getConstants: () => ({
|
|
82
|
+
Dimensions: {
|
|
83
|
+
window: { width: 375, height: 667, scale: 2, fontScale: 1 },
|
|
84
|
+
screen: { width: 375, height: 667, scale: 2, fontScale: 1 },
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Pre-Setup File
|
|
3
|
+
*
|
|
4
|
+
* Runs before Jest loads any other modules to set up global flags needed
|
|
5
|
+
* by React Native. This file must be JavaScript (not TypeScript) and must
|
|
6
|
+
* not import any modules that depend on the global flags being set.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* - Listed in `setupFiles` (runs before test framework loads)
|
|
10
|
+
* - Sets `__DEV__`, `IS_REACT_ACT_ENVIRONMENT`, and other RN globals
|
|
11
|
+
* - Stubs `__turboModuleProxy` so native module requires don't throw
|
|
12
|
+
* - React 19 throws null on cleanup — the unhandledRejection handler
|
|
13
|
+
* suppresses those while still surfacing real errors
|
|
14
|
+
* @see https://github.com/expo/expo/issues/38046 React 19 cleanup issue
|
|
15
|
+
* @see https://github.com/expo/expo/issues/40184 Jest 30 + expo-router
|
|
16
|
+
* @module jest.setup.pre
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Polyfill structuredClone for jsdom environment
|
|
20
|
+
// structuredClone is available in Node.js 17+ but jsdom doesn't expose it
|
|
21
|
+
if (typeof global.structuredClone === "undefined") {
|
|
22
|
+
global.structuredClone = val => JSON.parse(JSON.stringify(val));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle unhandled promise rejections BEFORE any other code runs
|
|
26
|
+
// This must be set up first to catch React 19 "thrown: null" issues
|
|
27
|
+
// Store the handler so it can be removed after tests complete (in jest.setup.ts)
|
|
28
|
+
global.__unhandledRejectionHandler = (reason, _promise) => {
|
|
29
|
+
// Silently ignore null rejections from React 19 cleanup
|
|
30
|
+
if (reason === null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Re-throw other unhandled rejections so tests still fail for real issues
|
|
34
|
+
throw reason;
|
|
35
|
+
};
|
|
36
|
+
process.on("unhandledRejection", global.__unhandledRejectionHandler);
|
|
37
|
+
|
|
38
|
+
// Disable RTLRN's auto cleanup to avoid "thrown: null" errors from React 19
|
|
39
|
+
// We handle cleanup manually in jest.setup.ts
|
|
40
|
+
process.env.RNTL_SKIP_AUTO_CLEANUP = "true";
|
|
41
|
+
|
|
42
|
+
// Set React Native test environment flags BEFORE any modules are loaded
|
|
43
|
+
global.__DEV__ = true;
|
|
44
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
45
|
+
global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true;
|
|
46
|
+
|
|
47
|
+
// Mock React Native bridge for testing
|
|
48
|
+
global.__fbBatchedBridgeConfig = {
|
|
49
|
+
remoteModuleConfig: [],
|
|
50
|
+
localModulesConfig: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Mock TurboModuleRegistry using external mock configuration
|
|
54
|
+
const mockModules = require("./jest.config.react-native-mock");
|
|
55
|
+
|
|
56
|
+
global.__turboModuleProxy = function (moduleName) {
|
|
57
|
+
return mockModules[moduleName] || null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Ensure global timers are available (required by @testing-library/react-native)
|
|
61
|
+
// Jest's environment should have these, but we ensure they're set
|
|
62
|
+
if (typeof global.setTimeout === "undefined") {
|
|
63
|
+
global.setTimeout = globalThis.setTimeout;
|
|
64
|
+
}
|
|
65
|
+
if (typeof global.clearTimeout === "undefined") {
|
|
66
|
+
global.clearTimeout = globalThis.clearTimeout;
|
|
67
|
+
}
|
|
68
|
+
if (typeof global.setInterval === "undefined") {
|
|
69
|
+
global.setInterval = globalThis.setInterval;
|
|
70
|
+
}
|
|
71
|
+
if (typeof global.clearInterval === "undefined") {
|
|
72
|
+
global.clearInterval = globalThis.clearInterval;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Mock window object for web-specific code in platform-agnostic tests
|
|
76
|
+
// This allows tests that check Platform.OS === 'web' branches to work
|
|
77
|
+
// Important: Include timer functions because RTLRN checks typeof window !== 'undefined'
|
|
78
|
+
global.window = {
|
|
79
|
+
// Timer functions (required by RTLRN)
|
|
80
|
+
setTimeout: global.setTimeout,
|
|
81
|
+
clearTimeout: global.clearTimeout,
|
|
82
|
+
setInterval: global.setInterval,
|
|
83
|
+
clearInterval: global.clearInterval,
|
|
84
|
+
setImmediate: global.setImmediate,
|
|
85
|
+
// Web-specific mocks
|
|
86
|
+
confirm: jest.fn(() => true),
|
|
87
|
+
alert: jest.fn(),
|
|
88
|
+
open: jest.fn(),
|
|
89
|
+
location: {
|
|
90
|
+
href: "",
|
|
91
|
+
origin: "http://localhost",
|
|
92
|
+
pathname: "/",
|
|
93
|
+
search: "",
|
|
94
|
+
hash: "",
|
|
95
|
+
},
|
|
96
|
+
addEventListener: jest.fn(),
|
|
97
|
+
removeEventListener: jest.fn(),
|
|
98
|
+
navigator: {
|
|
99
|
+
userAgent: "jest-test",
|
|
100
|
+
},
|
|
101
|
+
matchMedia: jest.fn(() => ({
|
|
102
|
+
matches: false,
|
|
103
|
+
addListener: jest.fn(),
|
|
104
|
+
removeListener: jest.fn(),
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Setup File
|
|
3
|
+
*
|
|
4
|
+
* Configures the testing environment after Jest loads modules.
|
|
5
|
+
* Sets up React Native Testing Library matchers, global mocks,
|
|
6
|
+
* and manual cleanup for React 19 compatibility.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* - Listed in `setupFilesAfterEnv` (runs after test framework loads)
|
|
10
|
+
* - Basic globals like `__DEV__` are set earlier in `jest.setup.pre.js`
|
|
11
|
+
* - Manual cleanup replaces RTLRN auto-cleanup to handle React 19's
|
|
12
|
+
* null-throw behavior during component teardown
|
|
13
|
+
* @see https://github.com/expo/expo/issues/38046 React 19 cleanup issue
|
|
14
|
+
* @see https://github.com/expo/expo/issues/40184 Jest 30 + expo-router
|
|
15
|
+
* @see {@link https://callstack.github.io/react-native-testing-library/ | RTLRN Docs}
|
|
16
|
+
* @module jest.setup
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Import from pure to avoid auto-cleanup (we set RNTL_SKIP_AUTO_CLEANUP in pre-setup)
|
|
20
|
+
// Then extend Jest matchers with React Native Testing Library matchers
|
|
21
|
+
import { cleanup } from "@testing-library/react-native/pure";
|
|
22
|
+
import "@testing-library/react-native/build/matchers/extend-expect";
|
|
23
|
+
|
|
24
|
+
// Mock environment variables module for type-safe env access in tests
|
|
25
|
+
// Tests can override specific values via jest.spyOn or jest.doMock
|
|
26
|
+
// Replace the mock below with your project's actual env module and values
|
|
27
|
+
jest.mock("@/lib/env", () => ({
|
|
28
|
+
env: {
|
|
29
|
+
EXPO_PUBLIC_ENV: "dev",
|
|
30
|
+
// Add your project-specific environment variables here
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Extend global type for the unhandledRejection handler set in jest.setup.pre.js
|
|
35
|
+
declare global {
|
|
36
|
+
let __unhandledRejectionHandler:
|
|
37
|
+
| ((reason: unknown, promise: Promise<unknown>) => void)
|
|
38
|
+
| undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Manual cleanup after each test (replacing RTLRN's auto cleanup)
|
|
42
|
+
// This handles React 19's cleanup which can throw null
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
try {
|
|
45
|
+
await cleanup();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Silently ignore null throws from React 19 cleanup
|
|
48
|
+
if (error !== null) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Mock ResizeObserver for tests that use it
|
|
55
|
+
// eslint-disable-next-line functional/no-classes -- Mock implementation required for testing
|
|
56
|
+
global.ResizeObserver = class ResizeObserver {
|
|
57
|
+
/** Starts observing an element — no-op for tests */
|
|
58
|
+
observe(): void {}
|
|
59
|
+
/** Stops observing an element — no-op for tests */
|
|
60
|
+
unobserve(): void {}
|
|
61
|
+
/** Disconnects observer — no-op for tests */
|
|
62
|
+
disconnect(): void {}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Mock expo-router for tests using navigation hooks
|
|
66
|
+
jest.mock("expo-router", () => ({
|
|
67
|
+
useRouter: jest.fn(() => ({
|
|
68
|
+
push: jest.fn(),
|
|
69
|
+
replace: jest.fn(),
|
|
70
|
+
back: jest.fn(),
|
|
71
|
+
canGoBack: jest.fn(() => true),
|
|
72
|
+
})),
|
|
73
|
+
usePathname: jest.fn(() => "/"),
|
|
74
|
+
useLocalSearchParams: jest.fn(() => ({})),
|
|
75
|
+
useGlobalSearchParams: jest.fn(() => ({})),
|
|
76
|
+
useSegments: jest.fn(() => []),
|
|
77
|
+
useNavigation: jest.fn(() => ({
|
|
78
|
+
navigate: jest.fn(),
|
|
79
|
+
goBack: jest.fn(),
|
|
80
|
+
getParent: jest.fn(() => ({
|
|
81
|
+
setOptions: jest.fn(),
|
|
82
|
+
})),
|
|
83
|
+
})),
|
|
84
|
+
Link: ({ children }: { children: React.ReactNode }) => children,
|
|
85
|
+
Stack: {
|
|
86
|
+
Screen: () => null,
|
|
87
|
+
},
|
|
88
|
+
Tabs: {
|
|
89
|
+
Screen: () => null,
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
// Mock @react-native-firebase/analytics
|
|
94
|
+
jest.mock("@react-native-firebase/analytics", () => {
|
|
95
|
+
const logEvent = jest.fn();
|
|
96
|
+
const getAnalytics = jest.fn(() => ({}));
|
|
97
|
+
return { logEvent, getAnalytics };
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Mock firebase/analytics
|
|
101
|
+
jest.mock("firebase/analytics", () => {
|
|
102
|
+
const logEvent = jest.fn();
|
|
103
|
+
const getAnalytics = jest.fn(() => ({}));
|
|
104
|
+
return { logEvent, getAnalytics };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Clear mocks after each test
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Remove the unhandledRejection handler after all tests to allow Jest to exit cleanly
|
|
113
|
+
// The handler is registered in jest.setup.pre.js and stored on global
|
|
114
|
+
afterAll(() => {
|
|
115
|
+
if (__unhandledRejectionHandler) {
|
|
116
|
+
process.removeListener("unhandledRejection", __unhandledRejectionHandler);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"fetch:graphql:schema:staging": "./scripts/fetch-graphql-schema.sh staging",
|
|
19
19
|
"fetch:graphql:schema:production": "./scripts/fetch-graphql-schema.sh production",
|
|
20
20
|
"export:web": "expo export --platform web --source-maps",
|
|
21
|
-
"pre-build:eas": "cat .gitignore > .easignore && cat .easignore.extra >> .easignore"
|
|
21
|
+
"pre-build:eas": "cat .gitignore > .easignore && cat .easignore.extra >> .easignore",
|
|
22
|
+
"security:zap": "bash scripts/zap-baseline.sh"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"@apollo/client": "^3.10.8",
|
|
@@ -117,7 +118,8 @@
|
|
|
117
118
|
"expo-atlas": "^0.4.0",
|
|
118
119
|
"globals": "^16.0.0",
|
|
119
120
|
"jest-environment-jsdom": "^30.2.0",
|
|
120
|
-
"jest-expo": "^54.0.12"
|
|
121
|
+
"jest-expo": "^54.0.12",
|
|
122
|
+
"serve": "^14.2.0"
|
|
121
123
|
},
|
|
122
124
|
"resolutions": {
|
|
123
125
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# This file is managed by Lisa.
|
|
2
|
+
# Do not edit directly — changes will be overwritten on the next `lisa` run.
|
|
3
|
+
|
|
4
|
+
name: ZAP Baseline Scan (NestJS)
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_call:
|
|
8
|
+
inputs:
|
|
9
|
+
node_version:
|
|
10
|
+
description: 'Node.js version to use'
|
|
11
|
+
required: false
|
|
12
|
+
default: '22.21.1'
|
|
13
|
+
type: string
|
|
14
|
+
package_manager:
|
|
15
|
+
description: 'Package manager to use (npm, yarn, or bun)'
|
|
16
|
+
required: false
|
|
17
|
+
default: 'bun'
|
|
18
|
+
type: string
|
|
19
|
+
zap_target_url:
|
|
20
|
+
description: 'Override URL for ZAP to scan (default: http://localhost:3000/graphql)'
|
|
21
|
+
required: false
|
|
22
|
+
default: 'http://localhost:3000/graphql'
|
|
23
|
+
type: string
|
|
24
|
+
zap_rules_file:
|
|
25
|
+
description: 'Path to ZAP rules configuration file'
|
|
26
|
+
required: false
|
|
27
|
+
default: '.zap/baseline.conf'
|
|
28
|
+
type: string
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
zap_baseline:
|
|
32
|
+
name: ZAP Baseline Scan
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
timeout-minutes: 20
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout repository
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Setup Node.js
|
|
41
|
+
uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: ${{ inputs.node_version }}
|
|
44
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
45
|
+
|
|
46
|
+
- name: Setup Bun
|
|
47
|
+
if: inputs.package_manager == 'bun'
|
|
48
|
+
uses: oven-sh/setup-bun@v2
|
|
49
|
+
with:
|
|
50
|
+
bun-version: '1.3.8'
|
|
51
|
+
|
|
52
|
+
- name: Install dependencies
|
|
53
|
+
run: |
|
|
54
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
55
|
+
npm ci
|
|
56
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
57
|
+
yarn install --frozen-lockfile
|
|
58
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
59
|
+
bun install --frozen-lockfile
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Build project
|
|
63
|
+
run: ${{ inputs.package_manager }} run build
|
|
64
|
+
|
|
65
|
+
- name: Start NestJS server
|
|
66
|
+
run: |
|
|
67
|
+
${{ inputs.package_manager }} run start &
|
|
68
|
+
SERVER_PID=$!
|
|
69
|
+
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
|
|
70
|
+
env:
|
|
71
|
+
NODE_ENV: test
|
|
72
|
+
PORT: 3000
|
|
73
|
+
|
|
74
|
+
- name: Wait for server ready
|
|
75
|
+
run: |
|
|
76
|
+
echo "Waiting for NestJS server to be ready..."
|
|
77
|
+
RETRIES=30
|
|
78
|
+
until curl -sf http://localhost:3000/health > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
|
|
79
|
+
echo "Waiting for server... ($RETRIES retries left)"
|
|
80
|
+
RETRIES=$((RETRIES - 1))
|
|
81
|
+
sleep 2
|
|
82
|
+
done
|
|
83
|
+
if [ $RETRIES -eq 0 ]; then
|
|
84
|
+
echo "Server failed to start within timeout"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
echo "Server is ready"
|
|
88
|
+
|
|
89
|
+
- name: Check for ZAP rules file
|
|
90
|
+
id: check_rules
|
|
91
|
+
run: |
|
|
92
|
+
if [ -f "${{ inputs.zap_rules_file }}" ]; then
|
|
93
|
+
echo "has_rules=true" >> $GITHUB_OUTPUT
|
|
94
|
+
else
|
|
95
|
+
echo "has_rules=false" >> $GITHUB_OUTPUT
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
- name: Run ZAP baseline scan
|
|
99
|
+
uses: zaproxy/action-baseline@v0.14.0
|
|
100
|
+
with:
|
|
101
|
+
target: ${{ inputs.zap_target_url }}
|
|
102
|
+
rules_file_name: ${{ steps.check_rules.outputs.has_rules == 'true' && inputs.zap_rules_file || '' }}
|
|
103
|
+
fail_action: true
|
|
104
|
+
allow_issue_writing: false
|
|
105
|
+
artifact_name: 'zap-report-nestjs'
|
|
106
|
+
|
|
107
|
+
- name: Stop NestJS server
|
|
108
|
+
if: always()
|
|
109
|
+
run: |
|
|
110
|
+
if [ -n "$SERVER_PID" ]; then
|
|
111
|
+
kill "$SERVER_PID" 2>/dev/null || true
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
- name: Upload ZAP report
|
|
115
|
+
if: always()
|
|
116
|
+
uses: actions/upload-artifact@v4
|
|
117
|
+
with:
|
|
118
|
+
name: zap-baseline-report-nestjs-${{ github.run_id }}
|
|
119
|
+
path: |
|
|
120
|
+
zap-report.html
|
|
121
|
+
zap-report.json
|
|
122
|
+
zap-report.md
|
|
123
|
+
retention-days: 14
|