Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
42dd9a1
fix : Update get Deployment logs query to GetDeploymentLogsV2 to matc…
AryanBansal-launch Aug 5, 2026
c421930
fix(deps): resolve 8 npm advisories via npm overrides
dhruv-parekh-cs Aug 24, 2026
5c3fd6c
CL-4965 | Consolidate secrets-scan onto the shared Talisman + truffle…
venkatesh-cstk Aug 26, 2026
bcf443a
Merge pull request #224 from contentstack/cl-4965
venkatesh-cstk Aug 26, 2026
87d7de8
fix(deps): resolve 8 Snyk vulnerabilities via scoped overrides
dhruv-parekh-cs Aug 27, 2026
41d6f56
fix(deps): bump adm-zip to 0.6.0 and override qs to 6.16.0
dhruv-parekh-cs Aug 31, 2026
9321dab
fix(deps): allowlist file-upload.ts in talisman (+Claude)
dhruv-parekh-cs Aug 31, 2026
09cd079
test(file-upload): cover the archive() zip-failure path (+Claude)
dhruv-parekh-cs Aug 31, 2026
5a27251
test(file-upload): scope the scanner exception to the auth-flag test …
dhruv-parekh-cs Aug 31, 2026
5957580
test(file-upload): extend scanner exceptions to remaining pre-existin…
dhruv-parekh-cs Aug 31, 2026
edf6986
test(file-upload): match the map callback fragment talisman reports (…
dhruv-parekh-cs Aug 31, 2026
3bb3ff2
refactor(file-upload): use try/catch for the zip failure path (+Claude)
dhruv-parekh-cs Aug 31, 2026
8496f57
fix(deps): resolve high-severity browserslist DoS/prototype-write adv…
dhruv-parekh-cs Sep 2, 2026
916fe9e
fix(deps): raise fast-uri override to ^3.1.7
dhruv-parekh-cs Sep 3, 2026
85242d0
fix(deps): repair broken lockfile resolution and bump nyc/shx to clea…
dhruv-parekh-cs Sep 10, 2026
6755575
fix(deps): resolve critical proxy-addr, high adm-zip, and fast-uri Sn…
dhruv-parekh-cs Sep 21, 2026
6ff5dc3
test(integration): add end-to-end test suite for launch-cli commands
SakshiKoli-CS Sep 22, 2026
3ef2991
Merge pull request #235 from contentstack/cl-7154
SakshiKoli-CS Sep 22, 2026
eba4ffb
feat(rollback): show a plan-not-available message when instant rollba…
AryanBansal-launch Sep 30, 2026
65a22fa
fix(deps): resolve brace-expansion, http-cache-semantics and serializ…
dhruv-parekh-cs Oct 5, 2026
708e1c9
CL-7796 | +Claude | resolve Snyk findings and upgrade cli-utilities a…
SakshiKoli-CS Oct 15, 2026
704a225
CL-7796 | +Claude | read .cs-launch.json with readFileSync and drop a…
SakshiKoli-CS Oct 15, 2026
a667c90
CL-7796 | +Claude | add talisman exceptions for pre-existing lines in…
SakshiKoli-CS Oct 15, 2026
775f1e0
CL-7796 | +Claude | use a talisman checksum for src/base-command.ts
SakshiKoli-CS Oct 15, 2026
18797b1
fix(deps): resolve handlebars, qs, traverse and source-map-js Snyk/np…
dhruv-parekh-cs Oct 15, 2026
a6aaa27
CL-7796 | +Claude | merge development, keeping #243's overrides
SakshiKoli-CS Oct 15, 2026
6c273a5
Merge pull request #242 from contentstack/cl-7796
SakshiKoli-CS Oct 26, 2026
4be20fb
fix(deps): resolve body-parser, fast-uri and serialize-javascript npm…
dhruv-parekh-cs Oct 26, 2026
a0d7fe6
Merge pull request #250 from contentstack/CL-snyk-fixes/26-Oct-2026
dhruv-parekh-cs Oct 26, 2026
e5e8132
chore(release): bump version to 1.11.4
Harshi-Shah-CS Oct 29, 2026
11fb180
Merge pull request #260 from contentstack/CL-0000
Harshi-Shah-CS Oct 29, 2026
c6f4afb
Merge pull request #261 from contentstack/development
Harshi-Shah-CS Oct 29, 2026
85f0336
chore: merge main into staging for the v1.11.4 release
Harshi-Shah-CS Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/secrets-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Secrets Scan

on:
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read

jobs:
secrets-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Run Talisman
env:
TALISMAN_VERSION: v1.37.0
# Pinned from https://github.com/thoughtworks/talisman/releases/download/v1.37.0/checksums -
# committed here rather than fetched at runtime, since fetching the checksums file from the
# same release as the binary it's meant to verify wouldn't catch a compromised release.
TALISMAN_SHA256: 8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4
run: |
set -euo pipefail
curl -fsSL -o talisman_linux_amd64 "https://github.com/thoughtworks/talisman/releases/download/${TALISMAN_VERSION}/talisman_linux_amd64"
echo "${TALISMAN_SHA256} talisman_linux_amd64" | sha256sum -c -
chmod +x talisman_linux_amd64
echo "refs/heads/${{ github.head_ref }} ${{ github.event.pull_request.head.sha }} refs/heads/${{ github.base_ref }} ${{ github.event.pull_request.base.sha }}" | ./talisman_linux_amd64 --githook pre-push

- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0
with:
path: ./
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
extra_args: --results=verified,unverified,unknown
10 changes: 4 additions & 6 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
npm run prepack && npm run lint-staged

echo "\n\n Running Talisman secret scan..."
talisman --githook pre-commit

echo "\n\n Running Snyk vulnerability scan..."
npm run prepack && npm run lint-staged && \
echo "\n\n Running secrets scan (Talisman + trufflehog)..." && \
sh .husky/secrets-scan.sh pre-commit && \
echo "\n\n Running Snyk vulnerability scan..." && \
snyk test --all-projects --fail-on=all
2 changes: 1 addition & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1 +1 @@
npm run test
sh .husky/secrets-scan.sh pre-push "$@" && npm run test
146 changes: 146 additions & 0 deletions .husky/secrets-scan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/bin/sh
set -eu

cd "$(git rev-parse --show-toplevel)"

for tool in git curl tar mktemp awk xargs uname tr dirname; do
command -v "$tool" >/dev/null 2>&1 || { echo "secrets-scan: required tool '$tool' not found in PATH" >&2; exit 1; }
done
if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
echo "secrets-scan: required tool 'sha256sum' or 'shasum' not found in PATH" >&2
exit 1
fi

HOOK_NAME="$1"

TALISMAN_VERSION="v1.37.0"
TRUFFLEHOG_VERSION="3.96.0"
CACHE_DIR="$HOME/.cache/secrets-scan-tools"
mkdir -p "$CACHE_DIR"

OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH_RAW=$(uname -m)
case "$ARCH_RAW" in
x86_64) ARCH=amd64 ;;
arm64|aarch64) ARCH=arm64 ;;
*) ARCH="$ARCH_RAW" ;;
esac

# Pinned checksums (from https://github.com/thoughtworks/talisman/releases/download/v1.37.0/checksums
# and https://github.com/trufflesecurity/trufflehog/releases/download/v3.96.0/trufflehog_3.96.0_checksums.txt),
# committed here rather than fetched at runtime - the whole point is to not trust a checksums file
# downloaded from the same release as the binary it's meant to verify.
case "${OS}_${ARCH}" in
darwin_amd64)
TALISMAN_SHA256="9a67e015d6c99650c45d653ce4a597f885bf661164c2671f08a3827013b277fb"
TRUFFLEHOG_SHA256="a30d8f1095e031a81a668e1582f2ed479c3b50476cef86317e0fb74210c33617"
;;
darwin_arm64)
TALISMAN_SHA256="4806746b2ba0190941f0606853990b4da6eb7df2d64c261212a6aa66eb6b15c2"
TRUFFLEHOG_SHA256="87478306b95ca2420cfb844b7582383ac60b922e262350a0088e797f328d2e62"
;;
linux_amd64)
TALISMAN_SHA256="8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4"
TRUFFLEHOG_SHA256="7105f1cd6577f058a9e39d0578f1a99c8a1e481e4d3512cd8a09acfe22a0fdc0"
;;
linux_arm64)
TALISMAN_SHA256="ca706f428a45d7a4ed3cc5c3cf7b3e5b0dfce6f2f584000a117d0d602a0342b7"
TRUFFLEHOG_SHA256="50acd4c7a3b8ebfe5083d8350956057030c44be3515dedd55b45263495c490b2"
;;
*)
echo "secrets-scan: no pinned checksum for platform ${OS}_${ARCH} (talisman ${TALISMAN_VERSION} / trufflehog ${TRUFFLEHOG_VERSION})" >&2
exit 1
;;
esac

if command -v sha256sum >/dev/null 2>&1; then
checksum() { sha256sum "$1" | awk '{print $1}'; }
else
checksum() { shasum -a 256 "$1" | awk '{print $1}'; }
fi

TALISMAN_BIN="$CACHE_DIR/talisman_${TALISMAN_VERSION}_${OS}_${ARCH}"
if [ ! -x "$TALISMAN_BIN" ]; then
echo "secrets-scan: downloading Talisman ${TALISMAN_VERSION}..."
curl -fsSL -o "${TALISMAN_BIN}.tmp" "https://github.com/thoughtworks/talisman/releases/download/${TALISMAN_VERSION}/talisman_${OS}_${ARCH}"
ACTUAL_HASH=$(checksum "${TALISMAN_BIN}.tmp")
if [ "$ACTUAL_HASH" != "$TALISMAN_SHA256" ]; then
echo "secrets-scan: Talisman checksum verification FAILED" >&2
rm -f "${TALISMAN_BIN}.tmp"
exit 1
fi
mv "${TALISMAN_BIN}.tmp" "$TALISMAN_BIN"
chmod +x "$TALISMAN_BIN"
fi

TRUFFLEHOG_DIR="$CACHE_DIR/trufflehog_${TRUFFLEHOG_VERSION}_${OS}_${ARCH}"
TRUFFLEHOG_BIN="$TRUFFLEHOG_DIR/trufflehog"
if [ ! -x "$TRUFFLEHOG_BIN" ]; then
echo "secrets-scan: downloading trufflehog ${TRUFFLEHOG_VERSION}..."
mkdir -p "$TRUFFLEHOG_DIR"
TARBALL="$CACHE_DIR/trufflehog_${TRUFFLEHOG_VERSION}_${OS}_${ARCH}.tar.gz"
curl -fsSL -o "$TARBALL" "https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_${OS}_${ARCH}.tar.gz"
ACTUAL_HASH=$(checksum "$TARBALL")
if [ "$ACTUAL_HASH" != "$TRUFFLEHOG_SHA256" ]; then
echo "secrets-scan: trufflehog checksum verification FAILED" >&2
rm -f "$TARBALL"
exit 1
fi
tar -xzf "$TARBALL" -C "$TRUFFLEHOG_DIR"
chmod +x "$TRUFFLEHOG_BIN"
fi

case "$HOOK_NAME" in
pre-commit)
"$TALISMAN_BIN" --githook pre-commit

STAGED_LIST=$(mktemp)
STAGE_DIR=$(mktemp -d)
trap 'rm -f "$STAGED_LIST"; rm -rf "$STAGE_DIR"' EXIT
git diff --cached --name-only --diff-filter=ACM -z > "$STAGED_LIST"
if [ -s "$STAGED_LIST" ]; then
export STAGE_DIR
xargs -0 sh -c 'for f; do mkdir -p "$STAGE_DIR/$(dirname "$f")"; git show ":$f" > "$STAGE_DIR/$f"; done' _ < "$STAGED_LIST"
"$TRUFFLEHOG_BIN" filesystem --fail --no-update --results=verified,unverified,unknown "$STAGE_DIR"
fi
;;
pre-push)
REMOTE_NAME="${2:-origin}"
STDIN_DATA=$(cat)
if [ -z "$STDIN_DATA" ]; then
exit 0
fi

DEFAULT_REF=$(git symbolic-ref -q --short "refs/remotes/${REMOTE_NAME}/HEAD" 2>/dev/null || echo "")
DEFAULT_BASE=""
[ -n "$DEFAULT_REF" ] && DEFAULT_BASE=$(git rev-parse --verify "$DEFAULT_REF" 2>/dev/null || echo "")
[ -z "$DEFAULT_BASE" ] && DEFAULT_BASE=$(git rev-parse --verify "${REMOTE_NAME}/main" 2>/dev/null || echo "")
[ -z "$DEFAULT_BASE" ] && DEFAULT_BASE=$(git rev-parse --verify "${REMOTE_NAME}/master" 2>/dev/null || echo "")

RESOLVED_STDIN=""
while read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
[ -z "$LOCAL_SHA" ] && continue
[ "$LOCAL_SHA" = "0000000000000000000000000000000000000000" ] && continue
if [ -z "$REMOTE_SHA" ] || [ "$REMOTE_SHA" = "0000000000000000000000000000000000000000" ]; then
REMOTE_SHA="$DEFAULT_BASE"
fi
if [ -z "$REMOTE_SHA" ]; then
echo "secrets-scan: could not determine a base commit to scan ${LOCAL_REF} against (no ${REMOTE_NAME}/HEAD, ${REMOTE_NAME}/main, or ${REMOTE_NAME}/master found locally) - refusing to silently skip the scan. Run 'git fetch ${REMOTE_NAME}' and try again." >&2
exit 1
fi
RESOLVED_STDIN="${RESOLVED_STDIN}${LOCAL_REF} ${LOCAL_SHA} ${REMOTE_REF} ${REMOTE_SHA}
"
"$TRUFFLEHOG_BIN" git "file://$(pwd)" --since-commit "$REMOTE_SHA" --branch "$LOCAL_SHA" --fail --no-update --results=verified,unverified,unknown
done <<EOF
$STDIN_DATA
EOF

if [ -n "$RESOLVED_STDIN" ]; then
printf '%s' "$RESOLVED_STDIN" | "$TALISMAN_BIN" --githook pre-push
fi
;;
*)
echo "secrets-scan: unknown hook '$HOOK_NAME'" >&2
exit 1
;;
esac
34 changes: 31 additions & 3 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,44 @@ fileignoreconfig:
- filename: .github/workflows/secrets-scan.yml
ignore_detectors:
- filecontent
- filename: .husky/secrets-scan.sh
checksum: 5a44edca8d209ca4b7f6eefc4ce2caab838679f8916ef6136ad68584c0705cb6
- filename: src/commands/launch/index.test.ts
checksum: 9db6c02ad35a0367343cd753b916dd64db4a9efd24838201d2e1113ed19c9b62
- filename: src/commands/launch/rollback.test.ts
checksum: a1010882456f315a918afe2777f90472985e9966bd308c5311ac0de318b14e8c
- filename: test/unit/commands/rollback.test.ts
checksum: d1f931f2d9a397131409399ad6463653e28b5a2224e870b641d9ba57c4418f18
- filename: package-lock.json
checksum: 36459c67c22b67c7255935e31ec8eda5c4483a702afbcbd0c1ff6f3d969cfbe4
ignore_detectors:
- filecontent
- filename: src/adapters/github.test.ts
checksum: b3d3d3a3c14adde103152d2ac4e1c4b7121a5f7533a87c3cc600f6d25fb59d1b
- filename: src/adapters/file-upload.test.ts
checksum: 962fd8b45578914926d20857ddc3d1bc2fa19e8b192c6dfdc03d214ae1898b59
version: "1.0"
allowed_patterns:
- "--(disable|enable)-cs-auth is passed"
- filename: src/adapters/file-upload.ts
allowed_patterns:
- "const \\{ formFieldKey, formFieldValue \\} of fields"
- "formData\\.append\\(formFieldKey, formFieldValue\\)"
- "headers: \\{ key: string; value: string \\}\\[\\]"
- "headers\\?\\.reduce\\(\\(acc, \\{ key, value \\}\\)"
- "\\(\\{ key, value \\}\\) => \\(\\{ key, value \\}\\)\\)"
- "const \\{ token, apiKey \\} = configHandler\\.get"
- filename: test/integration/specs/environments.test.ts
checksum: f02e76dc6f4dfac2bc21d53357d70fc2b93c6473b9e71ab08300670f2c5649c1
- filename: test/integration/jest.setup-env.ts
checksum: e82558593f4614fc62127f01ea8beb95231c95826b65071eb3971f9c45332344
- filename: test/integration/harness/launch-api.ts
checksum: a4549b87c896744422e71ab5395a1d6d2dfa372678d2488aecc4247ee9738f87
- filename: test/integration/harness/session.ts
checksum: 74dc637304710b1df882bea6a1c52b8779f1bd416dbeeab93a9026c68edb3f9e
- filename: test/integration/specs/launch-github.test.ts
checksum: 4ea1ae16627383530d7590374ba2b37fde87b5c111cc6c38c5125456aa9b51c8
- filename: test/integration/harness/fixtures.ts
checksum: b3133a65f3f4b3f3a32caf0cc0bf65b0c71187f96e044a3331ddcce3adea88c1
- filename: src/base-command.ts
checksum: db8bc45f6d8aa3daebae621920c8618b6e23d1a20984dee0749d91bfa5c1530c
- filename: test/unit/base-command.test.ts
checksum: 4612c2a41c9d5a7af887f77842feee0a6f6a28ba95a094282b5e9aa20dba6c8c
version: "1.0"
30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,33 @@
- Use only jest for writing test cases and refer existing unit test under the /src folder.
- Do not create code comments for any changes.

## Integration tests

- Unit tests mock the CLI's own classes, so they keep passing when behaviour changes.
Integration tests (`test/integration/`) exist to catch what unit tests cannot: they run a
real oclif command end to end and assert on what the CLI *does*.
- Run them with `npm run test:integration` (they are also part of `npm test`).
- Only three things are replaced: the terminal prompt (`inquirer`), the `open` package, and
the Contentstack management SDK, which resolves its base URL as `https://<host>:443` and so
cannot be pointed at a local server. Apollo, the GraphQL documents, the CLI config store,
flag parsing, the filesystem and `git` all run for real.
- The Launch API is a local HTTP server (`test/integration/harness/launch-api.ts`) that the CLI
reaches through a temporary region config. It records every GraphQL operation, so tests assert
on the operations and variables the CLI actually sent rather than on internal method calls.
- Assert on the observable contract - operation sequence, request variables, prompt sequence,
`.cs-launch.json` contents, stdout, exit code. Never stub a method on a command or adapter;
that is what makes these tests survive a refactor and fail on a behaviour change.
- Register a response for every operation a flow needs and finish with
`session.api.assertNoUnhandledOperations()` and `prompts.assertScriptFullyConsumed()`, so a
renamed operation or an added prompt fails loudly instead of passing quietly.
- V2 (epic CL-4867) removes the bare `csdx launch` command in favour of `projects:create`,
`environments:create` and `deployments:create`. `launch-github.test.ts` and
`launch-file-upload.test.ts` therefore describe behaviour that has to be *ported* to those
commands, not a command that survives — read a failure there as "the new command does something
different", and repoint the spec rather than deleting the assertion.
- Tests named after a `CL-` ticket pin a bug that was already fixed once. Do not relax them.
- `forceExit: true` in the integration jest config papers over a known-benign open handle:
oclif's `Config.load()` eagerly loads every command manifest, so `launch:functions` pulls in
rollup's native bindings and their GC handle. The cost is that a *future* spec which genuinely
hangs will force-exit instead of failing loudly — run `--detectOpenHandles` periodically to
check nothing real is hiding behind it.
15 changes: 15 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ export default tseslint.config(
// test files use @ts-nocheck and scaffolding vars that aren't all referenced
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': 'off',
// harness mirrors untyped CLI payloads, and jest.mock factories must use require()
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-require-imports': 'off',
},
},
{
files: ['*.config.js'],
languageOptions: {
sourceType: 'commonjs',
globals: {
module: 'writable',
require: 'readonly',
process: 'readonly',
__dirname: 'readonly',
},
},
},
);
26 changes: 26 additions & 0 deletions jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const config = {
testEnvironment: 'node',
rootDir: '.',
testMatch: ['<rootDir>/test/integration/specs/**/*.test.ts'],
transform: {
'^.+\\.ts$': ['ts-jest', { tsconfig: '<rootDir>/test/integration/tsconfig.json' }],
'^.+\\.js$': [
'ts-jest',
{ tsconfig: { allowJs: true, module: 'commonjs', target: 'es2020', esModuleInterop: true } },
],
},
transformIgnorePatterns: ['/node_modules/(?!uuid/)'],
moduleNameMapper: { '^chalk$': require.resolve('chalk') },
globalSetup: '<rootDir>/test/integration/jest.global-setup.ts',
globalTeardown: '<rootDir>/test/integration/jest.global-teardown.ts',
setupFiles: ['<rootDir>/test/integration/jest.setup-env.ts'],
setupFilesAfterEnv: ['<rootDir>/test/integration/jest.setup.ts'],
maxWorkers: 1,
testTimeout: 30000,
forceExit: true,
coverageDirectory: 'coverage/integration',
coverageProvider: 'v8',
collectCoverageFrom: ['src/**/*.ts', '!src/test/**', '!src/**/*.test.ts'],
};

module.exports = config;
Loading
Loading