Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ jobs:
npm install -g @devcontainers/cli
devcontainer build --workspace-folder . --config devcontainer.json --image-name cs50/codespace:${{ github.sha }} --image-name cs50/codespace:latest

- name: Smoke-test (latest)
if: ${{ github.ref == 'refs/heads/main' }}
run: |
bash tests/smoke.sh cs50/codespace:latest

- name: Push (latest) to GitHub Packages
if: ${{ github.ref == 'refs/heads/main' }}
run: |
Expand Down Expand Up @@ -72,6 +77,11 @@ jobs:
npm install -g @devcontainers/cli
devcontainer build --workspace-folder . --config devcontainer.json --image-name cs50/codespace:${{ github.sha }} --image-name cs50/codespace:${{ github.ref_name }}

- name: Smoke-test (${{ github.ref_name }})
if: ${{ github.ref != 'refs/heads/main' }}
run: |
bash tests/smoke.sh cs50/codespace:${{ github.ref_name }}

- name: Squash (${{ github.ref_name }})
if: ${{ github.ref != 'refs/heads/main' }}
run: |
Expand Down
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ RUN npm install --global @vscode/vsce yarn && \
mv design50-1.0.0.vsix /opt/cs50/extensions && \
cd /tmp && \
rm --force --recursive design50.vsix && \
git clone https://github.com/cs50/help50.vsix.git && \
cd help50.vsix && \
npm install && \
vsce package && \
mv help50-0.0.1.vsix /opt/cs50/extensions && \
cd /tmp && \
rm --force --recursive help50.vsix && \
npm uninstall --global vsce yarn


Expand Down
7 changes: 6 additions & 1 deletion devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"/opt/cs50/extensions/cs50-0.0.1.vsix",
"/opt/cs50/extensions/design50-1.0.0.vsix",
"/opt/cs50/extensions/ddb50-2.0.0.vsix",
"/opt/cs50/extensions/help50-0.0.1.vsix",
"/opt/cs50/extensions/phpliteadmin-0.0.1.vsix",
"/opt/cs50/extensions/style50-0.0.1.vsix",
"cs50.extension-uninstaller",
Expand Down Expand Up @@ -189,7 +190,11 @@
"terminal.integrated.defaultProfile.linux": "bash",
"terminal.integrated.gpuAcceleration": "off",
"terminal.integrated.profiles.linux": {
"JavaScript Debug Terminal": null
"JavaScript Debug Terminal": null,
"Sysadmins": {
"path": "sudo",
"args": ["bash"]
}
},
"terminal.integrated.persistentSessionReviveProcess": "never",
"terminal.integrated.sendKeybindingsToShell": true,
Expand Down
48 changes: 47 additions & 1 deletion etc/profile.d/codespace.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# If not root
if [ "$(whoami)" != "root" ]; then
if [ `id -u` -ne 0 ]; then

# Library (from cs50/cli; absent in images built before help50 landed there)
if [ -f /opt/cs50/lib/cli ]; then
. /opt/cs50/lib/cli
fi

# Check if running locally and set $RepositoryName if not already set
if [[ "$CODESPACES" != "true" && -z "$RepositoryName" ]]; then
export RepositoryName=$(ls -1t --color=never /workspaces | tail -1 | sed 's:/*$::')
export LOCAL_WORKSPACE_FOLDER="/workspaces/$RepositoryName"
fi

# Where help50's helpers look for misplaced files (cs50/cli defaults this to $HOME)
export WORKDIR="/workspaces/$RepositoryName"

# Rewrites URLs of the form http://HOST:PORT as https://$CODESPACE_NAME.app.github.dev:PORT
_hostname() {

Expand Down Expand Up @@ -79,4 +87,42 @@ if [ "$(whoami)" != "root" ]; then
http-server() {
command http-server "$@" | _hostname | _version | uniq
}

# help50 hooks, called by _help50 in /etc/profile.d/help50.sh after each command.
# These relay to the help50 VS Code extension via command50, which shows a "help50"
# button in the terminal's title bar; clicking it hands the message to the CS50 Duck.
# command50 runs detached with output discarded so the prompt isn't delayed and a
# missing extension server degrades silently.
_help50_button() {
( command50 help50.showButton "$1" "$2" > /dev/null 2>&1 & )
_HELP50_BUTTON=1
}

# A helper had advice: show it here (as in cs50/cli) and let the duck repeat it
_helpful() {
for name in n no y yes; do
alias $name=_rhetorical # Intercept answers to the rhetorical question
done
_alert "$(_ansi "$1")"
_help50_button say "$1"
}

# No helper matched: offer the duck the failed command's output to explain.
# Skip when there's no output (e.g., grep with no match, or a program exiting 1),
# since there'd be nothing to explain and most such exits aren't errors.
_helpless() {
if [[ -z "${1//[[:space:]]/}" ]]; then
return
fi
_alert "$(_ansi "🦆 Click \`help50\` above for help with that error.")"
_help50_button ask "$1"
}

# Command succeeded: hide the button, if showing
_helped() {
if [[ -n "$_HELP50_BUTTON" ]]; then
( command50 help50.hideButton > /dev/null 2>&1 & )
unset _HELP50_BUTTON
fi
}
fi
50 changes: 50 additions & 0 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash
# Smoke-tests a built cs50/codespace image. Usage: tests/smoke.sh [IMAGE]
# Each check has a timeout so that a regression that hangs the shell fails loudly.

set -o errexit -o errtrace -o nounset -o pipefail
step="startup"
trap 'echo "FAILED: $step" >&2' ERR

IMAGE="${1:-cs50/codespace}"
run() { timeout 60 docker run --rm --env CODESPACES=true --env RepositoryName=smoke "$@"; }
check() { step="$1"; echo "- $step"; }
skip() { echo "- SKIPPED: $1"; }

echo "Checking $IMAGE"

check "login shells start without errors"
test -z "$(run "$IMAGE" bash --login -c 'true' 2>&1)"

check "non-interactive login shell exits (help50 must not start without a terminal)"
run "$IMAGE" bash --login -c 'echo ok' | grep -qx ok
echo true | run --interactive "$IMAGE" bash --login

# The rest applies once the cs50/cli base image carries help50 (cs50/cli#210).
# Branch builds use cs50/cli:amd64 (main), canary uses cs50/cli:canary.
if run "$IMAGE" bash --login -c 'test -f /etc/profile.d/help50.sh -a -f /opt/cs50/lib/cli'; then

check "help50 is inherited from cs50/cli and its hooks are overridden by codespace.sh"
run "$IMAGE" bash --login -c '
test "$(type -P help50)" = /opt/cs50/bin/help50 &&
test "$WORKDIR" = /workspaces/smoke &&
for f in _helped _helpful _helpless _help50_button _alert _ansi; do declare -F "$f" > /dev/null || { echo "missing $f" >&2; exit 1; }; done &&
declare -f _help50_button | grep -q command50'

check "hooks behave: advice is printed, empty output is ignored (no extension server needed)"
run "$IMAGE" bash --login -c '
_helpful "Did you mean \`ls\`?" 2>&1 | grep -q "Did you mean" &&
test -z "$(_helpless "" 2>&1)" &&
test -z "$(_helpless " " 2>&1)" &&
_helpless "cat: x: No such file" 2>&1 | grep -q help50'

check "root shells (Sysadmins profile) do not start help50"
run --user root "$IMAGE" bash --login -c 'test -z "${HELP50:-}" && ! declare -F _helpful > /dev/null'
else
skip "help50 checks: base image has no help50 (cs50/cli without #210)"
fi

check "help50 extension is packaged for installation"
run "$IMAGE" bash --login -c 'test -f /opt/cs50/extensions/help50-0.0.1.vsix && command -v command50 > /dev/null'

echo "OK"