Skip to content

Commit 18b95c9

Browse files
committed
Install JavaScript dependencies in cloned source
1 parent 6f1f150 commit 18b95c9

File tree

3 files changed

+291
-0
lines changed

3 files changed

+291
-0
lines changed

.github/workflows/public-analyze-code-graph.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ jobs:
203203
working-directory: temp/${{ inputs.analysis-name }}
204204
run: ./../../scripts/cloneGitRepository.sh --url "${{ inputs.source-repository }}" --branch "${{ inputs.source-repository-branch }}" --history-only "${{ inputs.source-repository-history-only }}" --target "source/${{ inputs.analysis-name }}"
205205

206+
- name: (Code Analysis Setup) Install JavaScript dependencies in cloned source repository if needed
207+
if: inputs.source-repository != ''
208+
working-directory: temp/${{ inputs.analysis-name }}
209+
run: ./../../scripts/installJavaScriptDependencies.sh
210+
206211
- name: (Code Analysis Setup) Download artifacts for analysis
207212
if: inputs.artifacts-upload-name != ''
208213
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env bash
2+
3+
# This script triggers the installation of dependencies for JavaScript projects in the source folder.
4+
# It supports npm, pnpm and yarn and will skip other projects.
5+
6+
# This script is used inside .github/workflows/public-analyze-code-graph.yml (December 2025)
7+
# If you want to delete all node_modules directories in the source folder, run:
8+
# find "./${SOURCE_DIRECTORY}" -type d -name node_modules -prune -exec rm -rf {} +
9+
10+
# Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands)
11+
set -o errexit -o pipefail
12+
13+
# Overrideable Constants (defaults also defined in sub scripts)
14+
LOG_GROUP_START=${LOG_GROUP_START:-"::group::"}
15+
LOG_GROUP_END=${LOG_GROUP_END:-"::endgroup::"}
16+
SOURCE_DIRECTORY=${SOURCE_DIRECTORY:-"source"}
17+
18+
# Local constants
19+
SCRIPT_NAME=$(basename "${0}")
20+
21+
fail() {
22+
local ERROR_COLOR='\033[0;31m' # red
23+
local DEFAULT_COLOR='\033[0m'
24+
local errorMessage="${1}"
25+
echo -e "${ERROR_COLOR}${SCRIPT_NAME}: Error: ${errorMessage}${DEFAULT_COLOR}" >&2
26+
exit 1
27+
}
28+
29+
dry_run=false
30+
31+
# Input Arguments: Parse the command-line arguments
32+
while [[ $# -gt 0 ]]; do
33+
arg="$1"
34+
case $arg in
35+
--dry-run)
36+
dry_run=true
37+
shift
38+
;;
39+
*)
40+
fail "Unknown argument: ${arg}"
41+
esac
42+
done
43+
44+
if [ ! -d "./${SOURCE_DIRECTORY}" ]; then
45+
fail "This script needs to run inside the analysis directory with an already existing source directory in it. Change into that directory or use ./init.sh to set up an analysis."
46+
fi
47+
48+
dry_run_info=""
49+
if [ "${dry_run}" = true ] ; then
50+
echo "${SCRIPT_NAME}: Info: Dry run mode enabled."
51+
dry_run_info=" (dry run)"
52+
fi
53+
54+
# Returns 0 (success) if "private": true is set in package.json, otherwise 1 (false).
55+
# Pass the directory of the package.json file as first argument (default=.=current directory).
56+
is_private_package_json() {
57+
directory=${1:-.}
58+
node -e "process.exit(require(require('path').resolve('${directory}/package.json')).private ? 0 : 1)"
59+
}
60+
61+
# Install node package manager (npm) dependencies by finding all directories with package-lock.json files and running "npm ci" in them.
62+
find "./${SOURCE_DIRECTORY}" -type d -name "node_modules" -prune -o -type f -name "package-lock.json" -print | sort | while read -r lock_file; do
63+
lock_file_directory=$(dirname -- "${lock_file}")
64+
if is_private_package_json "${lock_file_directory}" ; then
65+
echo "${SCRIPT_NAME}: Info: Skipping npm install in private package ${lock_file_directory}."
66+
continue
67+
fi
68+
(
69+
cd "${lock_file_directory}"
70+
echo "${LOG_GROUP_START}$(date +'%Y-%m-%dT%H:%M:%S') Installing JavaScript dependencies with npm in ${lock_file_directory}${dry_run_info}";
71+
if [ "${dry_run}" = true ] ; then
72+
echo "${SCRIPT_NAME}: Info: Dry run mode - skipping npm ci in ${lock_file_directory}."
73+
else
74+
if ! command -v "npm" &> /dev/null ; then
75+
fail "Command npm (Node Package Manager) not found. It's needed to install JavaScript dependencies."
76+
fi
77+
npm ci --no-scripts || true
78+
fi
79+
echo "${LOG_GROUP_END}";
80+
)
81+
done
82+
83+
# Install pnpm dependencies by finding all directories with pnpm-lock.yaml files and running "pnpm install --frozen-lockfile" in them.
84+
find "./${SOURCE_DIRECTORY}" -type d -name "node_modules" -prune -o -type f -name "pnpm-lock.yaml" -print | sort | while read -r lock_file; do
85+
lock_file_directory=$(dirname -- "${lock_file}")
86+
# Run pnpm also in private packages since they might trigger the installation of dependencies in monorepos.
87+
(
88+
cd "${lock_file_directory}"
89+
echo "${LOG_GROUP_START}$(date +'%Y-%m-%dT%H:%M:%S') Installing JavaScript dependencies with pnpm in ${lock_file_directory}${dry_run_info}";
90+
if [ "${dry_run}" = true ] ; then
91+
echo "${SCRIPT_NAME}: Info: Dry run mode - skipping pnpm install in ${lock_file_directory}."
92+
else
93+
if ! command -v "pnpm" &> /dev/null ; then
94+
fail "Command pnpm (Performant Node.js Package Manager) not found. It's needed to install JavaScript dependencies."
95+
fi
96+
pnpm install --frozen-lockfile --ignore-scripts || true
97+
fi
98+
echo "${LOG_GROUP_END}";
99+
)
100+
done
101+
102+
# Install yarn dependencies by finding all directories with yarn.lock files and running "yarn install --frozen-lockfile" in them.
103+
find "./${SOURCE_DIRECTORY}" -type d -name "node_modules" -prune -o -type f -name "yarn.lock" -print | sort | while read -r lock_file; do
104+
lock_file_directory=$(dirname -- "${lock_file}")
105+
if is_private_package_json "${lock_file_directory}" ; then
106+
echo "${SCRIPT_NAME}: Info: Skipping yarn install in private package ${lock_file_directory}."
107+
continue
108+
fi
109+
(
110+
cd "${lock_file_directory}"
111+
echo "${LOG_GROUP_START}$(date +'%Y-%m-%dT%H:%M:%S') Installing JavaScript dependencies with yarn in ${lock_file_directory}${dry_run_info}";
112+
if [ "${dry_run}" = true ] ; then
113+
echo "${SCRIPT_NAME}: Info: Dry run mode - skipping yarn install in ${lock_file_directory}."
114+
else
115+
if ! command -v "yarn" &> /dev/null ; then
116+
fail "Command yarn (Yet Another Resource Negotiator) not found. It's needed to install JavaScript dependencies."
117+
fi
118+
yarn install --frozen-lockfile --ignore-scripts || true
119+
fi
120+
echo "${LOG_GROUP_END}";
121+
)
122+
done
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env bash
2+
3+
# Tests "installJavaScriptDependencies.sh".
4+
5+
# Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands)
6+
set -o errexit -o pipefail
7+
8+
# Local constants
9+
SCRIPT_NAME=$(basename "${0}")
10+
COLOR_ERROR='\033[0;31m' # red
11+
COLOR_DE_EMPHASIZED='\033[0;90m' # dark gray
12+
COLOR_SUCCESSFUL="\033[0;32m" # green
13+
COLOR_DEFAULT='\033[0m'
14+
15+
## Get this "scripts" directory if not already set
16+
# Even if $BASH_SOURCE is made for Bourne-like shells it is also supported by others and therefore here the preferred solution.
17+
# CDPATH reduces the scope of the cd command to potentially prevent unintended directory changes.
18+
# This way non-standard tools like readlink aren't needed.
19+
SCRIPTS_DIR=${SCRIPTS_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )} # Repository directory containing the shell scripts
20+
21+
tearDown() {
22+
# echo "${SCRIPT_NAME}: Tear down tests...."
23+
rm -rf "${temporaryTestDirectory}"
24+
}
25+
26+
successful() {
27+
echo ""
28+
echo -e "${COLOR_DE_EMPHASIZED}${SCRIPT_NAME}:${COLOR_DEFAULT} ${COLOR_SUCCESSFUL}✅ Tests finished successfully.${COLOR_DEFAULT}"
29+
tearDown
30+
}
31+
32+
info() {
33+
local infoMessage="${1}"
34+
echo -e "${COLOR_DE_EMPHASIZED}${SCRIPT_NAME}:${COLOR_DEFAULT} ${infoMessage}"
35+
}
36+
37+
fail() {
38+
local errorMessage="${1}"
39+
echo -e "${COLOR_DE_EMPHASIZED}${SCRIPT_NAME}: ${COLOR_ERROR}${errorMessage}${COLOR_DEFAULT}"
40+
tearDown
41+
return 1
42+
}
43+
44+
printTestLogFileContent() {
45+
local logFileContent
46+
logFileContent=$( cat "${temporaryTestDirectory}/${SCRIPT_NAME}-${test_case_number}.log" )
47+
# Remove color codes from the output for better readability in test logs
48+
logFileContent=$(echo -e "${logFileContent}" | sed -r "s/\x1B\[[0-9;]*[mK]//g")
49+
echo -e "${COLOR_DE_EMPHASIZED}${logFileContent}${COLOR_DEFAULT}"
50+
}
51+
52+
installJavaScriptDependenciesExpectingSuccessUnderTest() {
53+
local COLOR_DE_EMPHASIZED='\033[0;90m' # dark gray
54+
(
55+
cd "${temporaryTestDirectory}";
56+
source "${SCRIPTS_DIR}/installJavaScriptDependencies.sh" "$@" >"${temporaryTestDirectory}/${SCRIPT_NAME}-${test_case_number}.log" 2>&1
57+
)
58+
exitCode=$?
59+
if [ ${exitCode} -ne 0 ]; then
60+
fail "❌ Test failed: Script exited with non-zero exit code ${exitCode}."
61+
fi
62+
printTestLogFileContent
63+
}
64+
65+
installJavaScriptDependenciesExpectingFailureUnderTest() {
66+
set +o errexit
67+
(
68+
cd "${temporaryTestDirectory}";
69+
source "${SCRIPTS_DIR}/installJavaScriptDependencies.sh" "$@" >"${temporaryTestDirectory}/${SCRIPT_NAME}-${test_case_number}.log" 2>&1
70+
exitCode=$?
71+
if [ ${exitCode} -eq 0 ]; then
72+
fail "❌ Test failed: Script exited with zero exit code but was expected to fail."
73+
fi
74+
)
75+
set -o errexit
76+
printTestLogFileContent
77+
}
78+
79+
info "Starting tests...."
80+
81+
# Create testing resources
82+
temporaryTestDirectory=$(mktemp -d 2>/dev/null || mktemp -d -t "temporaryTestDirectory_${SCRIPT_NAME}")
83+
mkdir -p "${temporaryTestDirectory}/source"
84+
85+
# ------- Unit Test Case
86+
test_case_number=1
87+
echo ""
88+
info "${test_case_number}.) Should do nothing if the source folder is empty (dry-run)."
89+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
90+
echo "${result}"
91+
if [[ "${result}" == *"Installing"* ]]; then
92+
fail "❌ Test failed: Expected no installation attempts, but some were found:\n${result}"
93+
fi
94+
95+
# ------- Unit Test Case
96+
test_case_number=2
97+
echo ""
98+
info "${test_case_number}.) Should do nothing when the source folder contains no JavaScript projects (dry-run)."
99+
mkdir -p "${temporaryTestDirectory}/source/non-javascript-project"
100+
echo "This is a text file." > "${temporaryTestDirectory}/source/non-javascript-project/README.md"
101+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
102+
if [[ "${result}" == *"Installing"* ]]; then
103+
fail "❌ Test failed: Expected no installation attempts, but some were found:\n${result}"
104+
fi
105+
106+
# ------- Unit Test Case
107+
test_case_number=3
108+
echo ""
109+
info "${test_case_number}.) Should do nothing when the source folder contains a private npm project (dry-run)."
110+
mkdir -p "${temporaryTestDirectory}/source/private-npm-project"
111+
echo "{ \"name\": \"private-npm-project\" }" > "${temporaryTestDirectory}/source/private-npm-project/package-lock.json"
112+
echo "{ \"name\": \"private-npm-project\", \"private\": true }" > "${temporaryTestDirectory}/source/private-npm-project/package.json"
113+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
114+
if [[ "${result}" == *"Installing"* ]]; then
115+
fail "❌ Test failed: Expected no installation attempts, but some were found:\n${result}"
116+
fi
117+
118+
# ------- Unit Test Case
119+
test_case_number=4
120+
echo ""
121+
info "${test_case_number}.) Should do nothing when the source folder contains a private yarn project (dry-run)."
122+
mkdir -p "${temporaryTestDirectory}/source/private-yarn-project"
123+
echo "{ \"name\": \"private-yarn-project\" }" > "${temporaryTestDirectory}/source/private-yarn-project/package-lock.json"
124+
echo "{ \"name\": \"private-yarn-project\", \"private\": true }" > "${temporaryTestDirectory}/source/private-yarn-project/package.json"
125+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
126+
if [[ "${result}" == *"Installing"* ]]; then
127+
fail "❌ Test failed: Expected no installation attempts, but some were found:\n${result}"
128+
fi
129+
130+
# ------- Unit Test Case
131+
test_case_number=5
132+
echo ""
133+
info "${test_case_number}.) Should install npm dependencies for a npm project (dry-run)."
134+
mkdir -p "${temporaryTestDirectory}/source/npm-project"
135+
echo "{ \"name\": \"npm-project\" }" > "${temporaryTestDirectory}/source/npm-project/package-lock.json"
136+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
137+
if [[ ! "${result}" == *"Installing JavaScript dependencies with npm in"* ]]; then
138+
fail "❌ Test failed: Expected npm installation attempt, but none was found:\n${result}"
139+
fi
140+
141+
# ------- Unit Test Case
142+
test_case_number=6
143+
echo ""
144+
info "${test_case_number}.) Should install pnpm dependencies for a pnpm project (dry-run)."
145+
mkdir -p "${temporaryTestDirectory}/source/pnpm-project"
146+
echo "name: pnpm-project" > "${temporaryTestDirectory}/source/pnpm-project/pnpm-lock.yaml"
147+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
148+
if [[ ! "${result}" == *"Installing JavaScript dependencies with pnpm in"* ]]; then
149+
fail "❌ Test failed: Expected pnpm installation attempt, but none was found:\n${result}"
150+
fi
151+
152+
# ------- Unit Test Case
153+
test_case_number=7
154+
echo ""
155+
info "${test_case_number}.) Should install yarn dependencies for a yarn project (dry-run)."
156+
mkdir -p "${temporaryTestDirectory}/source/yarn-project"
157+
echo "name: yarn-project" > "${temporaryTestDirectory}/source/yarn-project/yarn.lock"
158+
result=$(installJavaScriptDependenciesExpectingSuccessUnderTest "--dry-run")
159+
if [[ ! "${result}" == *"Installing JavaScript dependencies with yarn in"* ]]; then
160+
fail "❌ Test failed: Expected yarn installation attempt, but none was found:\n${result}"
161+
fi
162+
163+
successful
164+
return 0

0 commit comments

Comments
 (0)