diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ffc7ded --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git/ +bin/ +examples/ +README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ae3c28f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI +on: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.repository == 'codewars/purescript' }} + steps: + - uses: actions/checkout@v2 + - uses: docker/setup-buildx-action@v2 + + - name: Build image + uses: docker/build-push-action@v3 + with: + context: . + push: false + # Make the image available in next step + load: true + tags: ghcr.io/codewars/purescript:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Passing Example + run: bin/run passing + + - name: Report Image Size + run: | + echo "## Image Size" >> $GITHUB_STEP_SUMMARY + docker image inspect --format '{{.Size}}' ghcr.io/codewars/purescript:latest | numfmt --to=si --suffix=B >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/push-image.yml b/.github/workflows/push-image.yml new file mode 100644 index 0000000..2cd61b3 --- /dev/null +++ b/.github/workflows/push-image.yml @@ -0,0 +1,39 @@ +# Build and push a Docker image to GitHub Container Registry when +# a new tag is pushed. +name: Push Image + +on: + push: + tags: + - "*" + +jobs: + build-and-push-image: + if: ${{ github.repository == 'codewars/purescript' }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: codewars + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v3 + with: + context: . + push: true + tags: | + ghcr.io/codewars/purescript:latest + ghcr.io/codewars/purescript:${{ github.ref_name }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3b966bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM buildpack-deps:bionic + +RUN set -ex; \ + useradd --create-home codewarrior; \ + ln -s /home/codewarrior /workspace; + +RUN set -ex; \ + curl -sL https://deb.nodesource.com/setup_8.x | bash -; \ + apt-get install -y nodejs; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/* /tmp/*; + +RUN set -ex; \ + mkdir -p /opt/purescript; \ + curl -fsSL https://github.com/purescript/purescript/releases/download/v0.12.2/linux64.tar.gz | tar xz -C /opt/purescript --strip-components=1; \ + curl -fsSL https://github.com/purescript/psc-package/releases/download/v0.5.1/linux64.tar.gz | tar xz -C /opt/purescript --strip-components=1; + +RUN npm install -g pulp@12.3.1; + +COPY --chown=codewarrior:codewarrior workspace/ /workspace +WORKDIR /workspace + +USER codewarrior + +ENV USER=codewarrior \ + HOME=/home/codewarrior \ + PATH=/opt/purescript:$PATH + +RUN set -ex; \ + cd /workspace; \ + npm install; \ +# install packages + psc-package install; \ +# compile packages + psc-package build; \ +# ensure running `Main.purs` works + pulp run; \ +# ensure testing works + pulp test || true; \ +# clean up + rm -rf ./src/Main.purs ./output/Main/ ./test/Example/ ./output/Example.ExampleSpec/; diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e87d53 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# purescript + +Container image for PureScript + +## Usage + +```bash +W=/workspace +# Create container +C=$(docker container create --rm -w $W ghcr.io/codewars/purescript:latest pulp test) + +# Copy files from the examples directory +docker container cp ./examples/passing/. $C:$W + +# Start +docker container start --attach $C +``` + +## Building + +```bash +docker build -t ghcr.io/codewars/purescript:latest . +``` diff --git a/bin/run b/bin/run new file mode 100755 index 0000000..5aa0b9a --- /dev/null +++ b/bin/run @@ -0,0 +1,17 @@ +#!/bin/bash +set -eu + +if [ -z "${IMAGE:+x}" ]; then + IMAGE=ghcr.io/codewars/purescript:latest +fi + +W=/workspace + +# Create container +C=$(docker container create --rm -w $W $IMAGE pulp test) + +# Copy files from the examples directory +docker container cp examples/${1:-passing}/. $C:$W + +# Run tests +docker container start --attach $C diff --git a/examples/passing/src/Example.purs b/examples/passing/src/Example.purs new file mode 100644 index 0000000..a4bcca6 --- /dev/null +++ b/examples/passing/src/Example.purs @@ -0,0 +1,5 @@ +module Example where +import Prelude + +add' :: Int -> Int -> Int +add' a b = a + b diff --git a/examples/passing/test/ExampleSpec.purs b/examples/passing/test/ExampleSpec.purs new file mode 100644 index 0000000..79640c2 --- /dev/null +++ b/examples/passing/test/ExampleSpec.purs @@ -0,0 +1,15 @@ +module ExampleSpec where + +import Prelude +import Example (add') + +import Test.Spec (Spec, describe, it) +import Test.Spec.Assertions (shouldEqual) + +spec :: Spec Unit +spec = + describe "Example" do + describe "add" do + it "returns sum" do + let sum = add' 1 1 + sum `shouldEqual` 2 diff --git a/workspace/package-lock.json b/workspace/package-lock.json new file mode 100644 index 0000000..068a749 --- /dev/null +++ b/workspace/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "cw-purescript", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "big-integer": { + "version": "1.6.41", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.41.tgz", + "integrity": "sha512-d5AT9lMTYJ/ZE/4gzxb+5ttPcRWljVsvv7lF1w9KzkPhVUhBtHrjDo1J8swfZKepfLsliDhYa31zRYwcD0Yg9w==" + } + } +} diff --git a/workspace/package.json b/workspace/package.json new file mode 100644 index 0000000..321ed1d --- /dev/null +++ b/workspace/package.json @@ -0,0 +1,8 @@ +{ + "name": "cw-purescript", + "version": "0.0.1", + "description": "", + "dependencies": { + "big-integer": "^1.6.41" + } +} diff --git a/workspace/psc-package.json b/workspace/psc-package.json new file mode 100644 index 0000000..817d4b1 --- /dev/null +++ b/workspace/psc-package.json @@ -0,0 +1,17 @@ +{ + "name": "cw-purescript", + "set": "psc-0.12.2-20190119", + "source": "https://github.com/purescript/package-sets.git", + "depends": [ + "prelude", + "console", + "debug", + "effect", + "bigints", + "rationals", + "profunctor-lenses", + "spec", + "spec-discovery", + "spec-quickcheck" + ] +} diff --git a/workspace/src/Main.purs b/workspace/src/Main.purs new file mode 100644 index 0000000..971037a --- /dev/null +++ b/workspace/src/Main.purs @@ -0,0 +1,9 @@ +module Main where + +import Prelude +import Effect (Effect) +import Effect.Console (log) + +main :: Effect Unit +main = do + log "Hello World!" diff --git a/workspace/src/Test/Spec/Reporter/Codewars.purs b/workspace/src/Test/Spec/Reporter/Codewars.purs new file mode 100644 index 0000000..3b2a5bc --- /dev/null +++ b/workspace/src/Test/Spec/Reporter/Codewars.purs @@ -0,0 +1,49 @@ +module Test.Spec.Reporter.Codewars (codewarsReporter) where + +import Prelude + +import Data.Maybe (Maybe(..)) +import Data.String + ( Pattern(Pattern) + , Replacement(Replacement) + , replaceAll + ) +import Effect.Console (log) + +import Test.Spec.Reporter.Base (defaultReporter) +import Test.Spec.Runner (Reporter) +import Test.Spec.Runner.Event as Event + + +codewarsReporter :: Reporter +codewarsReporter = defaultReporter {} update + where + update s = case _ of + Event.Start _ -> pure s + Event.End _ -> pure s + Event.Suite name -> s <$ log ("\n" <> name) + Event.SuiteEnd -> s <$ log "\n" + + Event.Pending name -> s <$ do + log $ "\n" <> name + log $ "\nPending Test" + log $ "\n" + + Event.Pass name _ ms -> s <$ do + log $ "\n" <> name + log $ "\nTest Passed" + log $ "\n" <> (show ms) + + Event.Fail name msg mStack -> s <$ do + log $ "\n" <> name + log $ "\nTest Failed<:LF:>" <> (escapeLF msg) + case mStack of + Nothing -> pure unit + Just k -> log $ "\n" <> (escapeLF k) + log $ "\n" + + _ -> pure s + + +escapeLF :: String -> String +escapeLF = replaceAll (Pattern "\n") (Replacement "<:LF:>") diff --git a/workspace/test/Example/ExampleSpec.purs b/workspace/test/Example/ExampleSpec.purs new file mode 100644 index 0000000..01c3e59 --- /dev/null +++ b/workspace/test/Example/ExampleSpec.purs @@ -0,0 +1,36 @@ +module Example.ExampleSpec where + +import Prelude +import Effect.Aff (delay) +import Data.Time.Duration (Milliseconds(..)) + +import Test.QuickCheck ((===), (/==)) +import Test.Spec (Spec, describe, it) +import Test.Spec.Assertions (shouldEqual) +import Test.Spec.QuickCheck (quickCheck) + +-- example from README plus basic sanity checks +spec :: Spec Unit +spec = + describe "purescript-spec" do + describe "Attributes" do + it "awesome" do + let isAwesome = true + isAwesome `shouldEqual` true + + describe "Features" do + it "runs in NodeJS" $ pure unit + it "runs in the browser" $ pure unit + it "supports async specs" do + res <- delay (Milliseconds 100.0) *> pure "Alligator" + res `shouldEqual` "Alligator" + + describe "QuickCheck" do + it "works" $ + quickCheck \n -> (n * 2 / 2) === n + it "works again" $ + quickCheck \n -> ((n + 1) * 2) /== n + + describe "Failure example" do + it "should fail" do + 1 `shouldEqual` 2 diff --git a/workspace/test/Main.purs b/workspace/test/Main.purs new file mode 100644 index 0000000..d0c6e48 --- /dev/null +++ b/workspace/test/Main.purs @@ -0,0 +1,15 @@ +module Test.Main where + +import Prelude +import Data.Maybe (Maybe(..)) +import Effect (Effect) + +import Test.Spec.Discovery (discover) +import Test.Spec.Runner (defaultConfig, run') + +import Test.Spec.Reporter.Codewars (codewarsReporter) + +main :: Effect Unit +main = discover ".+Spec" >>= run' config [codewarsReporter] + where + config = defaultConfig { timeout = Just 12000 }