@kanopi/wp-ai-indexer 1.0.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/.circleci/README.md +41 -0
- package/.circleci/config.yml +43 -0
- package/.circleci/install-dependencies.sh +319 -0
- package/.circleci/security-audit-npm.sh +264 -0
- package/.circleci/setup-node.sh +223 -0
- package/.circleci/test-package.sh +247 -0
- package/.eslintrc.js +52 -0
- package/.prettierrc +10 -0
- package/LICENSE +21 -0
- package/README.md +479 -0
- package/bin/wp-ai-indexer.js +25 -0
- package/package.json +71 -0
- package/tests/helpers/test-config.ts +36 -0
- package/tests/mocks/fixtures/settings.json +13 -0
- package/tests/mocks/fixtures/wordpress-posts.json +42 -0
- package/tests/mocks/openai.mock.ts +52 -0
- package/tests/mocks/pinecone.mock.ts +76 -0
- package/tests/setup.ts +20 -0
- package/vitest.config.ts +29 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# wp-ai-indexer CircleCI Scripts
|
|
2
|
+
|
|
3
|
+
CI/CD scripts for the wp-ai-indexer Node.js package.
|
|
4
|
+
|
|
5
|
+
## Scripts
|
|
6
|
+
|
|
7
|
+
### test-package.sh
|
|
8
|
+
Runs npm tests with caching and parallelism support.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
```bash
|
|
12
|
+
./test-package.sh [--cache-key KEY] [--max-workers NUM] [--test-command CMD]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### security-audit-npm.sh
|
|
16
|
+
Runs npm security audits with configurable severity levels.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
```bash
|
|
20
|
+
./security-audit-npm.sh PACKAGE_PATH [--audit-level LEVEL] [--fail-on-vulnerabilities]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### setup-node.sh
|
|
24
|
+
Installs Node.js if not already present.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
```bash
|
|
28
|
+
./setup-node.sh [--version VERSION] [--skip-if-installed]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### install-dependencies.sh
|
|
32
|
+
Unified dependency installer for npm or Composer projects.
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
```bash
|
|
36
|
+
./install-dependencies.sh PROJECT_PATH [--type npm|composer]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Integration
|
|
40
|
+
|
|
41
|
+
These scripts are designed to work both in CircleCI and locally. See the main project's `.circleci/INTEGRATION.md` for complete integration examples.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
version: 2.1
|
|
2
|
+
|
|
3
|
+
jobs:
|
|
4
|
+
test-package:
|
|
5
|
+
docker:
|
|
6
|
+
- image: cimg/node:22.9
|
|
7
|
+
steps:
|
|
8
|
+
- checkout
|
|
9
|
+
- restore_cache:
|
|
10
|
+
keys:
|
|
11
|
+
- package-deps-{{ checksum "package-lock.json" }}
|
|
12
|
+
- package-deps-{{ .Branch }}-
|
|
13
|
+
- package-deps-main-
|
|
14
|
+
- package-deps-
|
|
15
|
+
- run:
|
|
16
|
+
name: Run Package Tests
|
|
17
|
+
command: .circleci/test-package.sh .
|
|
18
|
+
- save_cache:
|
|
19
|
+
key: package-deps-{{ checksum "package-lock.json" }}
|
|
20
|
+
paths:
|
|
21
|
+
- node_modules
|
|
22
|
+
- ~/.npm
|
|
23
|
+
- ~/.cache
|
|
24
|
+
- store_test_results:
|
|
25
|
+
path: coverage
|
|
26
|
+
- store_artifacts:
|
|
27
|
+
path: coverage
|
|
28
|
+
|
|
29
|
+
security-audit:
|
|
30
|
+
docker:
|
|
31
|
+
- image: cimg/node:22.9
|
|
32
|
+
steps:
|
|
33
|
+
- checkout
|
|
34
|
+
- run:
|
|
35
|
+
name: Security Audit
|
|
36
|
+
command: .circleci/security-audit-npm.sh .
|
|
37
|
+
|
|
38
|
+
workflows:
|
|
39
|
+
version: 2
|
|
40
|
+
test:
|
|
41
|
+
jobs:
|
|
42
|
+
- test-package
|
|
43
|
+
- security-audit
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Unified Dependency Installation Script
|
|
4
|
+
#
|
|
5
|
+
# Description:
|
|
6
|
+
# Installs dependencies for Node.js (npm) or PHP (Composer) projects with caching support.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# install-dependencies.sh [OPTIONS] PROJECT_PATH
|
|
10
|
+
#
|
|
11
|
+
# Arguments:
|
|
12
|
+
# PROJECT_PATH Path to the project directory
|
|
13
|
+
#
|
|
14
|
+
# Options:
|
|
15
|
+
# --type TYPE Dependency type: npm|composer (default: auto-detect)
|
|
16
|
+
# --cache-key KEY Cache key prefix (default: v1-deps)
|
|
17
|
+
# --skip-cache Skip cache restoration and saving
|
|
18
|
+
# --npm-command CMD npm install command: ci|install (default: ci)
|
|
19
|
+
# --composer-options OPTS Additional Composer install options (default: --prefer-dist --no-interaction)
|
|
20
|
+
# --acf-auth Enable ACF authentication for Composer (requires ACF_USERNAME, ACF_PROD_URL)
|
|
21
|
+
# --yoast-auth Enable Yoast authentication for Composer (requires YOAST_TOKEN)
|
|
22
|
+
# --dry-run Show what would be done without executing
|
|
23
|
+
# --help Show this help message
|
|
24
|
+
#
|
|
25
|
+
# Environment Variables:
|
|
26
|
+
# ACF_USERNAME Advanced Custom Fields username (for Composer with --acf-auth)
|
|
27
|
+
# ACF_PROD_URL Advanced Custom Fields production URL (for Composer with --acf-auth)
|
|
28
|
+
# YOAST_TOKEN Yoast token (for Composer with --yoast-auth)
|
|
29
|
+
# CIRCLECI Set automatically in CircleCI environment
|
|
30
|
+
#
|
|
31
|
+
# Exit Codes:
|
|
32
|
+
# 0 - Dependencies installed successfully
|
|
33
|
+
# 1 - Installation failed
|
|
34
|
+
# 2 - Invalid arguments or missing files
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
set -eo pipefail
|
|
38
|
+
|
|
39
|
+
# Colors for output
|
|
40
|
+
RED='\033[0;31m'
|
|
41
|
+
GREEN='\033[0;32m'
|
|
42
|
+
YELLOW='\033[1;33m'
|
|
43
|
+
BLUE='\033[0;34m'
|
|
44
|
+
NC='\033[0m' # No Color
|
|
45
|
+
|
|
46
|
+
# Default values
|
|
47
|
+
PROJECT_PATH=""
|
|
48
|
+
TYPE="auto"
|
|
49
|
+
CACHE_KEY="v1-deps"
|
|
50
|
+
SKIP_CACHE="false"
|
|
51
|
+
NPM_COMMAND="ci"
|
|
52
|
+
COMPOSER_OPTIONS="--prefer-dist --no-interaction"
|
|
53
|
+
ACF_AUTH="false"
|
|
54
|
+
YOAST_AUTH="false"
|
|
55
|
+
DRY_RUN="false"
|
|
56
|
+
|
|
57
|
+
# Parse arguments
|
|
58
|
+
while [[ $# -gt 0 ]]; do
|
|
59
|
+
case $1 in
|
|
60
|
+
--type)
|
|
61
|
+
TYPE="$2"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
--cache-key)
|
|
65
|
+
CACHE_KEY="$2"
|
|
66
|
+
shift 2
|
|
67
|
+
;;
|
|
68
|
+
--skip-cache)
|
|
69
|
+
SKIP_CACHE="true"
|
|
70
|
+
shift
|
|
71
|
+
;;
|
|
72
|
+
--npm-command)
|
|
73
|
+
NPM_COMMAND="$2"
|
|
74
|
+
shift 2
|
|
75
|
+
;;
|
|
76
|
+
--composer-options)
|
|
77
|
+
COMPOSER_OPTIONS="$2"
|
|
78
|
+
shift 2
|
|
79
|
+
;;
|
|
80
|
+
--acf-auth)
|
|
81
|
+
ACF_AUTH="true"
|
|
82
|
+
shift
|
|
83
|
+
;;
|
|
84
|
+
--yoast-auth)
|
|
85
|
+
YOAST_AUTH="true"
|
|
86
|
+
shift
|
|
87
|
+
;;
|
|
88
|
+
--dry-run)
|
|
89
|
+
DRY_RUN="true"
|
|
90
|
+
shift
|
|
91
|
+
;;
|
|
92
|
+
--help)
|
|
93
|
+
grep '^#' "$0" | grep -v '#!/bin/bash' | sed 's/^# //;s/^#//'
|
|
94
|
+
exit 0
|
|
95
|
+
;;
|
|
96
|
+
-*)
|
|
97
|
+
echo -e "${RED}Error: Unknown option $1${NC}"
|
|
98
|
+
echo "Use --help for usage information"
|
|
99
|
+
exit 2
|
|
100
|
+
;;
|
|
101
|
+
*)
|
|
102
|
+
PROJECT_PATH="$1"
|
|
103
|
+
shift
|
|
104
|
+
;;
|
|
105
|
+
esac
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
# Helper functions
|
|
109
|
+
log_header() {
|
|
110
|
+
echo -e "\n${BLUE}========================================${NC}"
|
|
111
|
+
echo -e "${BLUE}$1${NC}"
|
|
112
|
+
echo -e "${BLUE}========================================${NC}"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
log_success() {
|
|
116
|
+
echo -e "${GREEN}✓${NC} $1"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
log_error() {
|
|
120
|
+
echo -e "${RED}✗${NC} $1"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
log_warning() {
|
|
124
|
+
echo -e "${YELLOW}⚠${NC} $1"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
log_info() {
|
|
128
|
+
echo -e "${BLUE}ℹ${NC} $1"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Validate required parameters
|
|
132
|
+
if [[ -z "$PROJECT_PATH" ]]; then
|
|
133
|
+
log_error "Project path is required"
|
|
134
|
+
echo "Usage: $0 [OPTIONS] PROJECT_PATH"
|
|
135
|
+
exit 2
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [[ ! -d "$PROJECT_PATH" ]]; then
|
|
139
|
+
log_error "Project path does not exist: $PROJECT_PATH"
|
|
140
|
+
exit 2
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Auto-detect dependency type if not specified
|
|
144
|
+
if [[ "$TYPE" == "auto" ]]; then
|
|
145
|
+
if [[ -f "$PROJECT_PATH/package.json" ]]; then
|
|
146
|
+
TYPE="npm"
|
|
147
|
+
log_info "Auto-detected: npm (found package.json)"
|
|
148
|
+
elif [[ -f "$PROJECT_PATH/composer.json" ]]; then
|
|
149
|
+
TYPE="composer"
|
|
150
|
+
log_info "Auto-detected: Composer (found composer.json)"
|
|
151
|
+
else
|
|
152
|
+
log_error "Could not auto-detect dependency type (no package.json or composer.json found)"
|
|
153
|
+
exit 2
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Validate type
|
|
158
|
+
if [[ ! "$TYPE" =~ ^(npm|composer)$ ]]; then
|
|
159
|
+
log_error "Invalid dependency type: $TYPE"
|
|
160
|
+
echo "Valid types: npm, composer"
|
|
161
|
+
exit 2
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
log_header "Dependency Installation"
|
|
165
|
+
log_info "Project: $PROJECT_PATH"
|
|
166
|
+
log_info "Type: $TYPE"
|
|
167
|
+
log_info "Dry run: $DRY_RUN"
|
|
168
|
+
|
|
169
|
+
# Function to install npm dependencies
|
|
170
|
+
install_npm_dependencies() {
|
|
171
|
+
log_header "Installing npm Dependencies"
|
|
172
|
+
|
|
173
|
+
# Validate npm is available
|
|
174
|
+
if ! command -v npm &> /dev/null; then
|
|
175
|
+
log_error "npm is not installed"
|
|
176
|
+
exit 1
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Determine lock file for cache key
|
|
180
|
+
LOCK_FILE=""
|
|
181
|
+
if [[ -f "$PROJECT_PATH/package-lock.json" ]]; then
|
|
182
|
+
LOCK_FILE="package-lock.json"
|
|
183
|
+
elif [[ -f "$PROJECT_PATH/npm-shrinkwrap.json" ]]; then
|
|
184
|
+
LOCK_FILE="npm-shrinkwrap.json"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Restore cache (if in CircleCI)
|
|
188
|
+
if [[ "$SKIP_CACHE" == "false" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
189
|
+
log_info "Cache restoration handled by CircleCI"
|
|
190
|
+
if [[ -n "$LOCK_FILE" ]]; then
|
|
191
|
+
log_info "Cache key: ${CACHE_KEY}-{{ checksum \"$PROJECT_PATH/$LOCK_FILE\" }}"
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
196
|
+
cd "$PROJECT_PATH"
|
|
197
|
+
|
|
198
|
+
# Choose install command
|
|
199
|
+
if [[ "$NPM_COMMAND" == "ci" ]]; then
|
|
200
|
+
if [[ -f "package-lock.json" ]] || [[ -f "npm-shrinkwrap.json" ]]; then
|
|
201
|
+
log_info "Running npm ci..."
|
|
202
|
+
npm ci
|
|
203
|
+
else
|
|
204
|
+
log_warning "No lock file found, falling back to npm install..."
|
|
205
|
+
npm install
|
|
206
|
+
fi
|
|
207
|
+
else
|
|
208
|
+
log_info "Running npm install..."
|
|
209
|
+
npm install
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
log_success "npm dependencies installed"
|
|
213
|
+
cd - > /dev/null
|
|
214
|
+
else
|
|
215
|
+
log_info "[DRY RUN] Would run: npm $NPM_COMMAND"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Save cache (if in CircleCI)
|
|
219
|
+
if [[ "$SKIP_CACHE" == "false" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
220
|
+
log_info "Cache saving handled by CircleCI"
|
|
221
|
+
log_info "Cache paths:"
|
|
222
|
+
log_info " - $PROJECT_PATH/node_modules"
|
|
223
|
+
log_info " - ~/.npm"
|
|
224
|
+
log_info " - ~/.cache"
|
|
225
|
+
fi
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Function to install Composer dependencies
|
|
229
|
+
install_composer_dependencies() {
|
|
230
|
+
log_header "Installing Composer Dependencies"
|
|
231
|
+
|
|
232
|
+
# Validate composer is available
|
|
233
|
+
if ! command -v composer &> /dev/null; then
|
|
234
|
+
log_error "Composer is not installed"
|
|
235
|
+
exit 1
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Restore cache (if in CircleCI)
|
|
239
|
+
if [[ "$SKIP_CACHE" == "false" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
240
|
+
log_info "Cache restoration handled by CircleCI"
|
|
241
|
+
if [[ -f "$PROJECT_PATH/composer.lock" ]]; then
|
|
242
|
+
log_info "Cache key: ${CACHE_KEY}-{{ checksum \"$PROJECT_PATH/composer.lock\" }}"
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Configure authentication
|
|
247
|
+
if [[ "$ACF_AUTH" == "true" ]] || [[ "$YOAST_AUTH" == "true" ]]; then
|
|
248
|
+
log_header "Configuring Composer Authentication"
|
|
249
|
+
|
|
250
|
+
if [[ "$ACF_AUTH" == "true" ]]; then
|
|
251
|
+
if [[ -z "${ACF_USERNAME:-}" ]] || [[ -z "${ACF_PROD_URL:-}" ]]; then
|
|
252
|
+
log_error "ACF authentication requested but ACF_USERNAME or ACF_PROD_URL not set"
|
|
253
|
+
exit 2
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
257
|
+
log_info "Configuring ACF authentication..."
|
|
258
|
+
composer config -g http-basic.connect.advancedcustomfields.com "$ACF_USERNAME" "$ACF_PROD_URL"
|
|
259
|
+
log_success "ACF authentication configured"
|
|
260
|
+
else
|
|
261
|
+
log_info "[DRY RUN] Would configure ACF authentication"
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
if [[ "$YOAST_AUTH" == "true" ]]; then
|
|
266
|
+
if [[ -z "${YOAST_TOKEN:-}" ]]; then
|
|
267
|
+
log_error "Yoast authentication requested but YOAST_TOKEN not set"
|
|
268
|
+
exit 2
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
272
|
+
log_info "Configuring Yoast authentication..."
|
|
273
|
+
composer config -g http-basic.my.yoast.com token "$YOAST_TOKEN"
|
|
274
|
+
log_success "Yoast authentication configured"
|
|
275
|
+
else
|
|
276
|
+
log_info "[DRY RUN] Would configure Yoast authentication"
|
|
277
|
+
fi
|
|
278
|
+
fi
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
282
|
+
cd "$PROJECT_PATH"
|
|
283
|
+
|
|
284
|
+
log_info "Running composer install $COMPOSER_OPTIONS..."
|
|
285
|
+
if composer install $COMPOSER_OPTIONS; then
|
|
286
|
+
log_success "Composer dependencies installed"
|
|
287
|
+
else
|
|
288
|
+
log_error "Composer install failed"
|
|
289
|
+
exit 1
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
cd - > /dev/null
|
|
293
|
+
else
|
|
294
|
+
log_info "[DRY RUN] Would run: composer install $COMPOSER_OPTIONS"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
# Save cache (if in CircleCI)
|
|
298
|
+
if [[ "$SKIP_CACHE" == "false" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
299
|
+
log_info "Cache saving handled by CircleCI"
|
|
300
|
+
log_info "Cache paths:"
|
|
301
|
+
log_info " - $PROJECT_PATH/vendor"
|
|
302
|
+
log_info " - ~/.composer/cache"
|
|
303
|
+
log_info " - ~/.cache/composer"
|
|
304
|
+
fi
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Install dependencies based on type
|
|
308
|
+
case "$TYPE" in
|
|
309
|
+
npm)
|
|
310
|
+
install_npm_dependencies
|
|
311
|
+
;;
|
|
312
|
+
composer)
|
|
313
|
+
install_composer_dependencies
|
|
314
|
+
;;
|
|
315
|
+
esac
|
|
316
|
+
|
|
317
|
+
log_header "Installation Complete"
|
|
318
|
+
log_success "Dependencies installed successfully"
|
|
319
|
+
exit 0
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# npm Security Audit Script
|
|
4
|
+
#
|
|
5
|
+
# Description:
|
|
6
|
+
# Runs npm audit on Node.js packages to detect security vulnerabilities.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# security-audit-npm.sh [OPTIONS] PACKAGE_PATH
|
|
10
|
+
#
|
|
11
|
+
# Arguments:
|
|
12
|
+
# PACKAGE_PATH Path to the package (e.g., packages/wp-ai-indexer)
|
|
13
|
+
#
|
|
14
|
+
# Options:
|
|
15
|
+
# --audit-level LEVEL Audit level: info|low|moderate|high|critical (default: high)
|
|
16
|
+
# --cache-key KEY Custom cache key prefix (default: v1-indexer-deps)
|
|
17
|
+
# --skip-cache Skip cache restoration
|
|
18
|
+
# --fail-on-vulnerabilities Fail (exit 1) if vulnerabilities are found (default: false)
|
|
19
|
+
# --output-file FILE Custom output file path (default: {package}/npm-audit-report.json)
|
|
20
|
+
# --store-artifacts Store audit report as artifact (default: true in CI)
|
|
21
|
+
# --dry-run Show what would be done without executing
|
|
22
|
+
# --help Show this help message
|
|
23
|
+
#
|
|
24
|
+
# Exit Codes:
|
|
25
|
+
# 0 - No vulnerabilities found or vulnerabilities ignored
|
|
26
|
+
# 1 - Vulnerabilities found (when --fail-on-vulnerabilities is set)
|
|
27
|
+
# 2 - Invalid arguments or execution error
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
set -eo pipefail
|
|
31
|
+
|
|
32
|
+
# Colors for output
|
|
33
|
+
RED='\033[0;31m'
|
|
34
|
+
GREEN='\033[0;32m'
|
|
35
|
+
YELLOW='\033[1;33m'
|
|
36
|
+
BLUE='\033[0;34m'
|
|
37
|
+
NC='\033[0m' # No Color
|
|
38
|
+
|
|
39
|
+
# Default values
|
|
40
|
+
PACKAGE_PATH=""
|
|
41
|
+
AUDIT_LEVEL="high"
|
|
42
|
+
CACHE_KEY="v1-indexer-deps"
|
|
43
|
+
SKIP_CACHE="false"
|
|
44
|
+
FAIL_ON_VULNERABILITIES="false"
|
|
45
|
+
OUTPUT_FILE=""
|
|
46
|
+
STORE_ARTIFACTS="${CIRCLECI:-false}"
|
|
47
|
+
DRY_RUN="false"
|
|
48
|
+
|
|
49
|
+
# Parse arguments
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case $1 in
|
|
52
|
+
--audit-level)
|
|
53
|
+
AUDIT_LEVEL="$2"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--cache-key)
|
|
57
|
+
CACHE_KEY="$2"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--skip-cache)
|
|
61
|
+
SKIP_CACHE="true"
|
|
62
|
+
shift
|
|
63
|
+
;;
|
|
64
|
+
--fail-on-vulnerabilities)
|
|
65
|
+
FAIL_ON_VULNERABILITIES="true"
|
|
66
|
+
shift
|
|
67
|
+
;;
|
|
68
|
+
--output-file)
|
|
69
|
+
OUTPUT_FILE="$2"
|
|
70
|
+
shift 2
|
|
71
|
+
;;
|
|
72
|
+
--store-artifacts)
|
|
73
|
+
STORE_ARTIFACTS="true"
|
|
74
|
+
shift
|
|
75
|
+
;;
|
|
76
|
+
--dry-run)
|
|
77
|
+
DRY_RUN="true"
|
|
78
|
+
shift
|
|
79
|
+
;;
|
|
80
|
+
--help)
|
|
81
|
+
grep '^#' "$0" | grep -v '#!/bin/bash' | sed 's/^# //;s/^#//'
|
|
82
|
+
exit 0
|
|
83
|
+
;;
|
|
84
|
+
-*)
|
|
85
|
+
echo -e "${RED}Error: Unknown option $1${NC}"
|
|
86
|
+
echo "Use --help for usage information"
|
|
87
|
+
exit 2
|
|
88
|
+
;;
|
|
89
|
+
*)
|
|
90
|
+
PACKAGE_PATH="$1"
|
|
91
|
+
shift
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
# Helper functions
|
|
97
|
+
log_header() {
|
|
98
|
+
echo -e "\n${BLUE}========================================${NC}"
|
|
99
|
+
echo -e "${BLUE}$1${NC}"
|
|
100
|
+
echo -e "${BLUE}========================================${NC}"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log_success() {
|
|
104
|
+
echo -e "${GREEN}✓${NC} $1"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
log_error() {
|
|
108
|
+
echo -e "${RED}✗${NC} $1"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
log_warning() {
|
|
112
|
+
echo -e "${YELLOW}⚠${NC} $1"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
log_info() {
|
|
116
|
+
echo -e "${BLUE}ℹ${NC} $1"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Validate required parameters
|
|
120
|
+
if [[ -z "$PACKAGE_PATH" ]]; then
|
|
121
|
+
log_error "Package path is required"
|
|
122
|
+
echo "Usage: $0 [OPTIONS] PACKAGE_PATH"
|
|
123
|
+
exit 2
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [[ ! -d "$PACKAGE_PATH" ]]; then
|
|
127
|
+
log_error "Package path does not exist: $PACKAGE_PATH"
|
|
128
|
+
exit 2
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if [[ ! -f "$PACKAGE_PATH/package.json" ]]; then
|
|
132
|
+
log_error "No package.json found in $PACKAGE_PATH"
|
|
133
|
+
exit 2
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Validate audit level
|
|
137
|
+
if [[ ! "$AUDIT_LEVEL" =~ ^(info|low|moderate|high|critical)$ ]]; then
|
|
138
|
+
log_error "Invalid audit level: $AUDIT_LEVEL"
|
|
139
|
+
echo "Valid levels: info, low, moderate, high, critical"
|
|
140
|
+
exit 2
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Set default output file if not specified
|
|
144
|
+
if [[ -z "$OUTPUT_FILE" ]]; then
|
|
145
|
+
OUTPUT_FILE="$PACKAGE_PATH/npm-audit-report.json"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
log_header "npm Security Audit"
|
|
149
|
+
log_info "Package: $PACKAGE_PATH"
|
|
150
|
+
log_info "Audit level: $AUDIT_LEVEL"
|
|
151
|
+
log_info "Output file: $OUTPUT_FILE"
|
|
152
|
+
log_info "Dry run: $DRY_RUN"
|
|
153
|
+
|
|
154
|
+
# Restore cache (if in CircleCI and cache enabled)
|
|
155
|
+
if [[ "$SKIP_CACHE" == "false" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
156
|
+
log_header "Restoring Cache"
|
|
157
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
158
|
+
log_info "Cache restoration handled by CircleCI"
|
|
159
|
+
log_info "Cache key: ${CACHE_KEY}-{{ checksum \"$PACKAGE_PATH/package-lock.json\" }}"
|
|
160
|
+
else
|
|
161
|
+
log_info "[DRY RUN] Would restore cache with key: $CACHE_KEY"
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Install dependencies
|
|
166
|
+
log_header "Installing Dependencies"
|
|
167
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
168
|
+
cd "$PACKAGE_PATH"
|
|
169
|
+
|
|
170
|
+
if [[ -f "package-lock.json" ]]; then
|
|
171
|
+
log_info "Running npm ci..."
|
|
172
|
+
npm ci
|
|
173
|
+
log_success "Dependencies installed"
|
|
174
|
+
else
|
|
175
|
+
log_info "No package-lock.json found, running npm install..."
|
|
176
|
+
npm install
|
|
177
|
+
log_success "Dependencies installed"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
cd - > /dev/null
|
|
181
|
+
else
|
|
182
|
+
log_info "[DRY RUN] Would run: cd $PACKAGE_PATH && npm ci"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# Run npm audit
|
|
186
|
+
log_header "Running npm audit"
|
|
187
|
+
AUDIT_RESULT=0
|
|
188
|
+
|
|
189
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
190
|
+
cd "$PACKAGE_PATH"
|
|
191
|
+
|
|
192
|
+
# Run audit with JSON output (continue on failure)
|
|
193
|
+
log_info "Generating JSON audit report..."
|
|
194
|
+
if npm audit --audit-level="$AUDIT_LEVEL" --json > "$(basename "$OUTPUT_FILE")" 2>&1; then
|
|
195
|
+
log_success "No vulnerabilities found at $AUDIT_LEVEL level or above"
|
|
196
|
+
AUDIT_RESULT=0
|
|
197
|
+
else
|
|
198
|
+
VULNERABILITIES_EXIT_CODE=$?
|
|
199
|
+
log_warning "Vulnerabilities detected"
|
|
200
|
+
AUDIT_RESULT=1
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Run audit again for human-readable output
|
|
204
|
+
echo ""
|
|
205
|
+
log_info "Running audit for display output..."
|
|
206
|
+
if npm audit --audit-level="$AUDIT_LEVEL"; then
|
|
207
|
+
log_success "Audit passed"
|
|
208
|
+
else
|
|
209
|
+
# Parse JSON report to show summary
|
|
210
|
+
if [[ -f "$(basename "$OUTPUT_FILE")" ]]; then
|
|
211
|
+
if command -v jq &> /dev/null; then
|
|
212
|
+
echo ""
|
|
213
|
+
log_info "Vulnerability summary:"
|
|
214
|
+
jq -r '.metadata | "Total: \(.vulnerabilities.total)\nInfo: \(.vulnerabilities.info)\nLow: \(.vulnerabilities.low)\nModerate: \(.vulnerabilities.moderate)\nHigh: \(.vulnerabilities.high)\nCritical: \(.vulnerabilities.critical)"' "$(basename "$OUTPUT_FILE")"
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Move output file to final location if different
|
|
220
|
+
if [[ "$(basename "$OUTPUT_FILE")" != "$OUTPUT_FILE" ]]; then
|
|
221
|
+
mkdir -p "$(dirname "$OUTPUT_FILE")"
|
|
222
|
+
mv "$(basename "$OUTPUT_FILE")" "$OUTPUT_FILE"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
cd - > /dev/null
|
|
226
|
+
|
|
227
|
+
if [[ -f "$OUTPUT_FILE" ]]; then
|
|
228
|
+
log_success "Audit report saved to: $OUTPUT_FILE"
|
|
229
|
+
fi
|
|
230
|
+
else
|
|
231
|
+
log_info "[DRY RUN] Would run: npm audit --audit-level=$AUDIT_LEVEL"
|
|
232
|
+
AUDIT_RESULT=0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Store artifacts (if in CircleCI)
|
|
236
|
+
if [[ "$STORE_ARTIFACTS" == "true" ]] && [[ -n "${CIRCLECI:-}" ]]; then
|
|
237
|
+
log_header "Storing Artifacts"
|
|
238
|
+
if [[ "$DRY_RUN" == "false" ]]; then
|
|
239
|
+
if [[ -f "$OUTPUT_FILE" ]]; then
|
|
240
|
+
log_info "Artifact storage handled by CircleCI"
|
|
241
|
+
log_info " - store_artifacts: $OUTPUT_FILE"
|
|
242
|
+
else
|
|
243
|
+
log_warning "Output file not found: $OUTPUT_FILE"
|
|
244
|
+
fi
|
|
245
|
+
else
|
|
246
|
+
log_info "[DRY RUN] Would store artifact: $OUTPUT_FILE"
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
log_header "Security Audit Complete"
|
|
251
|
+
|
|
252
|
+
if [[ $AUDIT_RESULT -eq 0 ]]; then
|
|
253
|
+
log_success "No vulnerabilities found at $AUDIT_LEVEL level or above"
|
|
254
|
+
exit 0
|
|
255
|
+
else
|
|
256
|
+
if [[ "$FAIL_ON_VULNERABILITIES" == "true" ]]; then
|
|
257
|
+
log_error "Vulnerabilities found - failing build"
|
|
258
|
+
exit 1
|
|
259
|
+
else
|
|
260
|
+
log_warning "Vulnerabilities found but not failing build"
|
|
261
|
+
log_info "Use --fail-on-vulnerabilities to fail on vulnerabilities"
|
|
262
|
+
exit 0
|
|
263
|
+
fi
|
|
264
|
+
fi
|