Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically push Docker image to Docker Hub #5

Merged
merged 17 commits into from
Dec 24, 2020
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Format code
SleeplessByte committed Dec 24, 2020
commit 293b90d528f6a0f550699304bc4ef4990c539cce
6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ name: Deploy to Amazon ECR
env:
aws_region: eu-west-2
ecr_repository: ${{ github.event.repository.name }}
dockerfile: "Dockerfile"
dockerfile: 'Dockerfile'

on:
push:
@@ -47,13 +47,13 @@ jobs:
env:
ECR_REGISTRY: ${{ steps.login_to_ecr.outputs.registry }}
ECR_REPOSITORY: ${{ env.ecr_repository }}
IMAGE_TAG: "${{ github.sha }}"
IMAGE_TAG: '${{ github.sha }}'
DOCKERFILE: ${{ env.dockerfile }}
run: |
# Build a docker container and push it to ECR
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f $DOCKERFILE .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

# Retag this as the production tag to deploy it
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:production
docker push $ECR_REGISTRY/$ECR_REPOSITORY:production
26 changes: 13 additions & 13 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting

## Our Responsibilities

17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -71,7 +71,7 @@ You can pass the following type of URLs:
- Your solutions: `/my/solutions/<id>`
- Private solutions: `/solutions/<id>`

### Using docker
## Using docker

To create the image, execute the following command from the repository root:

@@ -90,3 +90,18 @@ Example:
```bash
docker run -v ~/solution-238382y7sds7fsadfasj23j:/solution exercism/javascript-representer two-fer /solution
```

## Formatting code

The easiest way to satisfy the code-format linter is to add a comment to your PR:

```text
/format
```

Alternatively, run the following command:

```shell
# Ensure that the version matches the version inside .github/format-code.yml
EXERCISM_PRETTIER_VERSION=2.2.1 bin/format.sh
```
21 changes: 8 additions & 13 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
module.exports = {
verbose: true,
projects: [
'<rootDir>'
],
roots: [
"<rootDir>/src/",
"<rootDir>/test/"
],
projects: ['<rootDir>'],
roots: ['<rootDir>/src/', '<rootDir>/test/'],
moduleNameMapper: {
'^~src/(.*)$': '<rootDir>/src/$1',
'^~test/(.*)$': '<rootDir>/test/$1'
'^~test/(.*)$': '<rootDir>/test/$1',
},
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/test/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)"
'**/__tests__/**/*.[jt]s?(x)',
'**/test/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
testPathIgnorePatterns: [
'/(?:production_)?node_modules/',
'.d.ts$',
'<rootDir>/test/fixtures',
'<rootDir>/test/helpers',
'__mocks__'
'__mocks__',
],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
};
}
30 changes: 21 additions & 9 deletions src/AstParser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { parse as parseToTree, TSESTree, TSESTreeOptions } from "@typescript-eslint/typescript-estree";
import { NoSourceError } from '~src/errors/NoSourceError';
import { ParserError } from "~src/errors/ParserError";
import { getProcessLogger } from "~src/utils/logger";
import {
parse as parseToTree,
TSESTree,
TSESTreeOptions,
} from '@typescript-eslint/typescript-estree'
import { NoSourceError } from '~src/errors/NoSourceError'
import { ParserError } from '~src/errors/ParserError'
import { getProcessLogger } from '~src/utils/logger'

type Program = TSESTree.Program

export class ParsedSource {
constructor(public readonly program: Program, public readonly source: string) {}
constructor(
public readonly program: Program,
public readonly source: string
) {}
}

export class AstParser {
constructor(private readonly options?: TSESTreeOptions, private readonly n = 1) {
}
constructor(
private readonly options?: TSESTreeOptions,
private readonly n = 1
) {}

/**
* Parse a files into an AST tree
@@ -32,8 +41,11 @@ export class AstParser {
}

try {
return sources.map((source): ParsedSource => new ParsedSource(parseToTree(source, this.options), source))
} catch(error) {
return sources.map(
(source): ParsedSource =>
new ParsedSource(parseToTree(source, this.options), source)
)
} catch (error) {
throw new ParserError(error)
}
}
2 changes: 1 addition & 1 deletion src/errors/NoExportError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StructureError } from "./StructureError";
import { StructureError } from './StructureError'

export class NoExportError extends StructureError {
constructor(public readonly namedExport: string) {
3 changes: 1 addition & 2 deletions src/errors/NoMethodError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { StructureError } from "./StructureError";

import { StructureError } from './StructureError'

export class NoMethodError extends StructureError {
constructor(public readonly method: string) {
4 changes: 2 additions & 2 deletions src/errors/NoSourceError.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SOURCE_MISSING_ERROR } from "./codes";
import { SOURCE_MISSING_ERROR } from './codes'

export class NoSourceError extends Error {
public readonly code: typeof SOURCE_MISSING_ERROR;
public readonly code: typeof SOURCE_MISSING_ERROR

constructor() {
super('No source file(s) found')
10 changes: 6 additions & 4 deletions src/errors/ParserError.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { SOURCE_PARSE_ERROR } from "./codes";
import { SOURCE_PARSE_ERROR } from './codes'

export class ParserError extends Error {
public readonly code: typeof SOURCE_PARSE_ERROR;
public readonly code: typeof SOURCE_PARSE_ERROR

constructor(public readonly original: Error) {
super(`
super(
`
Could not parse the source; most likely due to a syntax error.

Original error:
${original.message}
`.trim())
`.trim()
)

Error.captureStackTrace(this, this.constructor)
this.code = SOURCE_PARSE_ERROR
2 changes: 1 addition & 1 deletion src/errors/StructureError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { STRUCTURE_ERROR_UNCAUGHT } from "./codes";
import { STRUCTURE_ERROR_UNCAUGHT } from './codes'

export class StructureError extends Error {
public readonly code: typeof STRUCTURE_ERROR_UNCAUGHT
18 changes: 13 additions & 5 deletions src/errors/handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { GENERIC_FAILURE } from "./codes";
import { GENERIC_FAILURE } from './codes'

export function registerExceptionHandler(): void {
process.on('uncaughtException', reportException)
}

function reportException<T extends Error & { message: string; code?: number }>(err: T): void
function reportException<T extends Error & { message: string; code?: number }>(err: T | string): void {

function reportException<T extends Error & { message: string; code?: number }>(
err: T
): void
function reportException<T extends Error & { message: string; code?: number }>(
err: T | string
): void {
if (typeof err === 'string') {
return reportException({ message: err, code: GENERIC_FAILURE, stack: undefined, name: 'UnknownError' })
return reportException({
message: err,
code: GENERIC_FAILURE,
stack: undefined,
name: 'UnknownError',
})
}

const errorMessage = `
36 changes: 24 additions & 12 deletions src/input/DirectoryInput.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readDir } from "~src/utils/fs";
import { FileInput } from "./FileInput";
import { readDir } from '~src/utils/fs'
import { FileInput } from './FileInput'

import nodePath from 'path'

@@ -8,18 +8,23 @@ const TEST_FILES = /\.spec|test\./
const CONFIGURATION_FILES = /(?:babel\.config\.js|jest\.config\.js|\.eslintrc\.js)$/

export class DirectoryInput implements Input {
constructor(private readonly path: string, private readonly exerciseSlug: string) {}
constructor(
private readonly path: string,
private readonly exerciseSlug: string
) {}

public async read(n = 1): Promise<string[]> {
const files = await readDir(this.path)

const candidates = findCandidates(files, n, `${this.exerciseSlug}.js`)
const fileSources = await Promise.all(
candidates.map((candidate): Promise<string> => {
return new FileInput(nodePath.join(this.path, candidate))
.read()
.then(([source]): string => source)
})
candidates.map(
(candidate): Promise<string> => {
return new FileInput(nodePath.join(this.path, candidate))
.read()
.then(([source]): string => source)
}
)
)

return fileSources
@@ -34,7 +39,11 @@ export class DirectoryInput implements Input {
* @param n the number of files it should return
* @param preferredNames the names of the files it prefers
*/
function findCandidates(files: string[], n: number, ...preferredNames: string[]): string[] {
function findCandidates(
files: string[],
n: number,
...preferredNames: string[]
): string[] {
const candidates = files
.filter((file): boolean => EXTENSIONS.test(file))
.filter((file): boolean => !TEST_FILES.test(file))
@@ -44,9 +53,12 @@ function findCandidates(files: string[], n: number, ...preferredNames: string[])
? candidates.filter((file): boolean => preferredNames.includes(file))
: []

const allMatches = preferredMatches.length >= n
? preferredMatches
: preferredMatches.concat(candidates.filter((file): boolean => !preferredMatches.includes(file)))
const allMatches =
preferredMatches.length >= n
? preferredMatches
: preferredMatches.concat(
candidates.filter((file): boolean => !preferredMatches.includes(file))
)

return allMatches.slice(0, n)
}
4 changes: 2 additions & 2 deletions src/input/FileInput.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { readFile } from "~src/utils/fs";
import { readFile } from '~src/utils/fs'

export class FileInput implements Input {
constructor(private readonly path: string) {}

public async read(_n = 1): Promise<string[]> {
const buffer = await readFile(this.path)
return [buffer.toString("utf8")]
return [buffer.toString('utf8')]
}
}
82 changes: 53 additions & 29 deletions test/__mocks__/fs.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,67 @@

import path from 'path'
import { PathLike } from 'fs';

const fs = jest.genMockFromModule('fs') as Omit<typeof import('fs'), 'readFile' | 'readdir' | 'exists' | 'writeFile'> & {
__setMockFiles: typeof __setMockFiles;
__getWrittenFiles: typeof __getWrittenFiles;

readdir(path: PathLike, callback: (err: NodeJS.ErrnoException | null, files: string[]) => void): void;
readFile(path: PathLike | number, callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void): void;
writeFile(path: PathLike | number, data: unknown, callback: (err: NodeJS.ErrnoException | null) => void): void;
exists(path: PathLike, callback: (exists: boolean) => void): void;
};
import { PathLike } from 'fs'

const fs = jest.genMockFromModule('fs') as Omit<
typeof import('fs'),
'readFile' | 'readdir' | 'exists' | 'writeFile'
> & {
__setMockFiles: typeof __setMockFiles
__getWrittenFiles: typeof __getWrittenFiles

readdir(
path: PathLike,
callback: (err: NodeJS.ErrnoException | null, files: string[]) => void
): void
readFile(
path: PathLike | number,
callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void
): void
writeFile(
path: PathLike | number,
data: unknown,
callback: (err: NodeJS.ErrnoException | null) => void
): void
exists(path: PathLike, callback: (exists: boolean) => void): void
}

// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles: { [dir: string]: { [file: string]: string } } = Object.create(null)
let writtenFiles: { [dir: string]: { [file: string]: string } } = Object.create(null)
let mockFiles: { [dir: string]: { [file: string]: string } } = Object.create(
null
)
let writtenFiles: { [dir: string]: { [file: string]: string } } = Object.create(
null
)

class NotMocked extends Error {
public readonly code: string;
public readonly errno: 34;
public readonly code: string
public readonly errno: 34

constructor(key: string) {
super(
`${key} does not exist (because it has not been mocked)\n` +
`Mocked are: \n${JSON.stringify(mockFiles, null, 2)}`
`Mocked are: \n${JSON.stringify(mockFiles, null, 2)}`
)

this.code = 'ENOENT'
this.errno = 34

Error.captureStackTrace(this, this.constructor);
Error.captureStackTrace(this, this.constructor)
}
}

class CanOnlyWriteUnmockedFiles extends Error {
public readonly code: string;
public readonly errno: 47;
public readonly code: string
public readonly errno: 47

constructor(key: string) {
super(
`${key} already exists as readonly (because it has been mocked)`
)
super(`${key} already exists as readonly (because it has been mocked)`)

this.code = 'EEXIST'
this.errno = 47

Error.captureStackTrace(this, this.constructor);
Error.captureStackTrace(this, this.constructor)
}
}

@@ -61,7 +75,7 @@ function __setMockFiles(newMockFiles: { [path: string]: string }): void {
const file = path.basename(osPath)

if (!mockFiles[dir]) {
mockFiles[dir] = {};
mockFiles[dir] = {}
}

mockFiles[dir][file] = newMockFiles[fullPath]
@@ -72,7 +86,10 @@ function __getWrittenFiles(): typeof writtenFiles {
return JSON.parse(JSON.stringify(writtenFiles))
}

function readdir(dirPath: PathLike, callback: (err: NodeJS.ErrnoException | null, files: string[]) => void): void {
function readdir(
dirPath: PathLike,
callback: (err: NodeJS.ErrnoException | null, files: string[]) => void
): void {
const key = path.normalize(dirPath.toString())

if (key in mockFiles) {
@@ -81,7 +98,10 @@ function readdir(dirPath: PathLike, callback: (err: NodeJS.ErrnoException | null
return callback(new NotMocked(key), [])
}

function readFile(filePath: PathLike | number, callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void): void {
function readFile(
filePath: PathLike | number,
callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void
): void {
const key = path.normalize(filePath.toString())
const dir = path.dirname(key)
const file = path.basename(key)
@@ -92,7 +112,11 @@ function readFile(filePath: PathLike | number, callback: (err: NodeJS.ErrnoExcep
return callback(new NotMocked(key), Buffer.from([]))
}

function writeFile(filePath: PathLike | number, data: unknown, callback: (err: NodeJS.ErrnoException | null) => void): void {
function writeFile(
filePath: PathLike | number,
data: unknown,
callback: (err: NodeJS.ErrnoException | null) => void
): void {
const key = path.normalize(filePath.toString())
const dir = path.dirname(key)
const file = path.basename(key)
@@ -102,7 +126,7 @@ function writeFile(filePath: PathLike | number, data: unknown, callback: (err: N
}

if (!writtenFiles[dir]) {
writtenFiles[dir] = {};
writtenFiles[dir] = {}
}

writtenFiles[dir][file] = String(data)
2 changes: 1 addition & 1 deletion test/helpers/smoke.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InlineInput } from "~test/helpers/input/InlineInput";
import { InlineInput } from '~test/helpers/input/InlineInput'

type RepresenterFactory = () => Representer
type represent = (solutionContent: string) => Promise<Output>
4 changes: 2 additions & 2 deletions test/ref.d.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,6 @@
/// <reference path="../src/declarations.d.ts" />

interface MockedFs {
__setMockFiles(files: { [path: string]: string }): void;
__getWrittenFiles(): { [dir: string]: { [file: string]: string }};
__setMockFiles(files: { [path: string]: string }): void
__getWrittenFiles(): { [dir: string]: { [file: string]: string } }
}
28 changes: 16 additions & 12 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["esnext"], /* Specify library files to be included in the compilation. */
"target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"lib": [
"esnext"
] /* Specify library files to be included in the compilation. */,
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDirs": ["./"], /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"outDir": "./dist" /* Redirect output structure to the directory. */,
"rootDirs": [
"./"
] /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
"isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
"isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
@@ -32,19 +36,19 @@

/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noUnusedParameters": true /* Report errors on unused parameters. */,
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
// "paths": { "~src/*": ["./src/*"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */

/* Source Map Options */