├── server ├── src │ ├── app-root.ts │ ├── filters │ │ ├── readme.md │ │ ├── index.ts │ │ ├── kb-not-found-exception.filter.spec.ts │ │ ├── kb-validation-exception.filter.spec.ts │ │ ├── kb-not-found-exception.filter.ts │ │ └── kb-validation-exception.filter.ts │ ├── events │ │ ├── readme.md │ │ ├── index.ts │ │ ├── events.module.ts │ │ ├── events.gateway.spec.ts │ │ └── events.gateway.ts │ ├── main.ts │ ├── tasks │ │ ├── index.ts │ │ ├── readme.md │ │ ├── tasks.service.ts │ │ └── tasks.service.spec.ts │ ├── app │ │ ├── readme.md │ │ ├── index.ts │ │ ├── app.module.ts │ │ ├── app.controller.ts │ │ ├── app.service.ts │ │ └── app.controller.spec.ts │ ├── decorators │ │ ├── readme.md │ │ ├── index.ts │ │ ├── kb-api-validation-error-response.decorator.ts │ │ ├── get-all.decorator.ts │ │ ├── kb-post.decorator.ts │ │ ├── get-one.decorator.ts │ │ ├── kb-delete.decorator.ts │ │ ├── kb-put.decorator.ts │ │ └── kb-patch.decorator.ts │ ├── api │ │ ├── readme.md │ │ ├── index.ts │ │ ├── api.module.ts │ │ ├── product │ │ │ ├── product.module.ts │ │ │ ├── product.service.ts │ │ │ └── product.controller.ts │ │ ├── api.controller.spec.ts │ │ └── api.controller.ts │ ├── abstracts │ │ ├── index.ts │ │ ├── readme.md │ │ ├── base.model.abstract.ts │ │ └── base.service.abstract.ts │ ├── models │ │ ├── index.ts │ │ ├── readme.md │ │ ├── api.model.ts │ │ ├── public-error.model.ts │ │ └── product.model.ts │ ├── bootstrap-application.ts │ └── swagger.ts ├── .eslintignore ├── nest-cli.json ├── tsconfig.build.json ├── test-tools │ └── jest.setup.ts ├── test │ ├── __snapshots__ │ │ └── app.e2e-spec.ts.snap │ ├── utils.ts │ ├── app.e2e-spec.ts │ ├── jest-e2e.json │ ├── sockets.e2e-spec.ts │ └── socket.service.ts ├── tsconfig.json ├── .eslintrc.js ├── README.md └── package.json ├── client ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.scss │ │ ├── app.component.html │ │ ├── app-routing.module.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.component.spec.ts │ │ └── angular-material │ │ │ └── angular-material.module.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── main.ts │ ├── index.html │ ├── test.ts │ ├── styles.scss │ └── polyfills.ts ├── proxy.conf.json ├── .editorconfig ├── tsconfig.app.json ├── e2e │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ ├── tsconfig.json │ └── protractor.conf.js ├── tsconfig.spec.json ├── tsconfig.json ├── .gitignore ├── .browserslistrc ├── README.md ├── .eslintrc.js ├── karma.conf.js ├── package.json ├── tslint.json └── angular.json ├── .commit-template ├── .github ├── FUNDING.yml ├── workflows │ ├── build.yml │ ├── release.yml │ ├── e2e-tests.yml │ ├── api-tests.yml │ ├── server-unit-tests.yml │ └── client-unit-tests.yml └── CODEOWNERS ├── tools ├── readme.md ├── scripts │ ├── replace-template-string.js │ ├── get-all-contributors.js │ └── prune-untrackted-branches.js ├── initialize.js └── data │ └── names-dictionary.js ├── commitlint.config.js ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── .all-contributorsrc ├── .devcontainer ├── docker-compose.yml ├── Dockerfile └── devcontainer.json ├── .gitignore ├── TEMPLATE_INSTRUCTIONS.md ├── README.md └── package.json /server/src/app-root.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.commit-template: -------------------------------------------------------------------------------- 1 | type(scope): subject 2 | 3 | description -------------------------------------------------------------------------------- /server/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .eslintrc.js -------------------------------------------------------------------------------- /server/src/filters/readme.md: -------------------------------------------------------------------------------- 1 | ## FILTERS 2 | 3 | Nest.js filters -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: 2 | - https://paypal.me/thatkookooguy?locale.x=en_US -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/kb-server-client-template/master/client/src/favicon.ico -------------------------------------------------------------------------------- /server/src/events/readme.md: -------------------------------------------------------------------------------- 1 | ## EVENTS 2 | 3 | This module is in charge of everything related to async events on top of websockets -------------------------------------------------------------------------------- /server/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from './bootstrap-application'; 2 | 3 | bootstrap() 4 | .then((app) => app.listen(10102)); 5 | -------------------------------------------------------------------------------- /server/src/tasks/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './tasks.service'; 6 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/src/app/readme.md: -------------------------------------------------------------------------------- 1 | ## APP 2 | 3 | The main app definition. Everything is defined here (all the submodules are imported in this module as well). -------------------------------------------------------------------------------- /server/src/decorators/readme.md: -------------------------------------------------------------------------------- 1 | ## DECORATORS 2 | 3 | Mostly using `applyDecorators` to combine decorators that are commonly being used together across the project -------------------------------------------------------------------------------- /server/src/api/readme.md: -------------------------------------------------------------------------------- 1 | ## API 2 | 3 | Contains the main API of the application under `/api`. As a rule of thumbs, each part of the api should be a module of itself. -------------------------------------------------------------------------------- /server/src/events/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './events.gateway'; 6 | export * from './events.module'; 7 | -------------------------------------------------------------------------------- /client/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "http://localhost:10102", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "changeOrigin": true 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/abstracts/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './base.model.abstract'; 6 | export * from './base.service.abstract'; 7 | -------------------------------------------------------------------------------- /server/src/app/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './app.controller'; 6 | export * from './app.module'; 7 | export * from './app.service'; 8 | -------------------------------------------------------------------------------- /server/src/filters/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './kb-not-found-exception.filter'; 6 | export * from './kb-validation-exception.filter'; 7 | -------------------------------------------------------------------------------- /server/src/models/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './api.model'; 6 | export * from './product.model'; 7 | export * from './public-error.model'; 8 | -------------------------------------------------------------------------------- /server/src/events/events.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EventsGateway } from './events.gateway'; 3 | 4 | @Module({ 5 | providers: [EventsGateway] 6 | }) 7 | export class EventsModule {} 8 | -------------------------------------------------------------------------------- /server/src/models/readme.md: -------------------------------------------------------------------------------- 1 | ## MODELS 2 | 3 | Every `item` in our system should be a model. We should have two types of items: persistent items, which will be saved to the database, and non-persistent items, which resign in-memory -------------------------------------------------------------------------------- /server/src/tasks/readme.md: -------------------------------------------------------------------------------- 1 | ## TASKS 2 | 3 | This module is in charge of everything related to scheduled tasks. Think of cron jobs that the application should perform. This can include database backup, or updating data once a day and things like that -------------------------------------------------------------------------------- /server/test-tools/jest.setup.ts: -------------------------------------------------------------------------------- 1 | const nativeConsoleError = global.console.warn; 2 | 3 | global.console.warn = (...args) => { 4 | if (args.join('').includes('Using Unsupported mongoose version')) { 5 | return; 6 | } 7 | return nativeConsoleError(...args); 8 | } 9 | -------------------------------------------------------------------------------- /server/src/filters/kb-not-found-exception.filter.spec.ts: -------------------------------------------------------------------------------- 1 | import { KbNotFoundExceptionFilter } from '@kb-filters'; 2 | 3 | describe('NotFoundExceptionFilterFilter', () => { 4 | it('should be defined', () => { 5 | expect(new KbNotFoundExceptionFilter('')).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /server/src/filters/kb-validation-exception.filter.spec.ts: -------------------------------------------------------------------------------- 1 | import { KbValidationExceptionFilter } from '@kb-filters'; 2 | 3 | describe('KbValidationExceptionFilter', () => { 4 | it('should be defined', () => { 5 | expect(new KbValidationExceptionFilter()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tools/readme.md: -------------------------------------------------------------------------------- 1 | ## APPLICATION TOOLS 2 | 3 | This folder contains tools that can be used while developing. 4 | 5 | The most obvious one is the script to initialize this template. 6 | But you also have things like `prune-untrackted-branches` to delete branches 7 | that already appeared in the cloud and got deleted. -------------------------------------------------------------------------------- /server/src/api/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './api.controller'; 6 | export * from './api.module'; 7 | export * from './product/product.controller'; 8 | export * from './product/product.module'; 9 | export * from './product/product.service'; 10 | -------------------------------------------------------------------------------- /client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | kibibit Client Side 3 | 4 | 5 |
6 |

Start Writing your app!

7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /server/src/abstracts/readme.md: -------------------------------------------------------------------------------- 1 | ## ABSTRACTS 2 | 3 | Contains all the abstracts of this project. Usually these are common parts of the application that repeat a lot (like services for database items or the database items themselves). 4 | 5 | Currently, this contain an abstract base service for DB items, and a base DB model to define new models based on. -------------------------------------------------------------------------------- /client/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root #site-name')) 10 | .getText() as Promise; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './get-all.decorator'; 6 | export * from './get-one.decorator'; 7 | export * from './kb-api-validation-error-response.decorator'; 8 | export * from './kb-delete.decorator'; 9 | export * from './kb-patch.decorator'; 10 | export * from './kb-post.decorator'; 11 | export * from './kb-put.decorator'; 12 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ '@commitlint/config-angular' ], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', [ 7 | 'build', 8 | 'chore', 9 | 'ci', 10 | 'docs', 11 | 'feat', 12 | 'fix', 13 | 'perf', 14 | 'refactor', 15 | 'revert', 16 | 'style', 17 | 'test' 18 | ] 19 | ] 20 | } 21 | }; -------------------------------------------------------------------------------- /server/src/tasks/tasks.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { Cron } from '@nestjs/schedule'; 3 | 4 | @Injectable() 5 | export class TasksService { 6 | private readonly logger = new Logger(TasksService.name); 7 | 8 | /** Run every minute */ 9 | @Cron('* * * * *') 10 | handleCron() { 11 | this.logger.debug('Scheduled task example: Called once every minute'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /server/src/api/api.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { ApiController } from './api.controller'; 5 | import { ProductModule } from './product/product.module'; 6 | 7 | @Module({ 8 | controllers: [ApiController], 9 | imports: [ 10 | MongooseModule.forRoot('mongodb://localhost:27017'), 11 | ProductModule 12 | ] 13 | }) 14 | export class ApiModule {} 15 | -------------------------------------------------------------------------------- /server/src/decorators/kb-api-validation-error-response.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { ApiResponse } from '@nestjs/swagger'; 3 | 4 | import { PublicError } from '@kb-models'; 5 | 6 | export const KbApiValidateErrorResponse = () => { 7 | return applyDecorators( 8 | ApiResponse({ 9 | description: 'Some validation error as accured on the given model', 10 | status: 405, 11 | type: PublicError 12 | }) 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "rbbit.typescript-hero", 4 | "coenraads.bracket-pair-colorizer", 5 | "wix.vscode-import-cost", 6 | "orta.vscode-jest", 7 | "dbaeumer.vscode-eslint", 8 | "actboy168.tasks", 9 | "johnpapa.vscode-peacock", 10 | "angular.ng-template", 11 | "abhijoybasak.nestjs-files", 12 | "eamodio.gitlens", 13 | "codeandstuff.package-json-upgrade", 14 | "mongodb.mongodb-vscode", 15 | "ms-azuretools.vscode-docker", 16 | "jock.svg" 17 | ] 18 | } -------------------------------------------------------------------------------- /client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { webConsolelogo } from '@kibibit/consologo'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'kibibit client'; 11 | 12 | constructor() { 13 | webConsolelogo('kibibit client template', [ 14 | 'kibibit server-client template', 15 | 'change this up in app.component.ts' 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | - next 9 | 10 | jobs: 11 | build: 12 | name: Build Production 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout commit 16 | uses: actions/checkout@v2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 12 21 | - name: Build 22 | run: | 23 | npm install 24 | npm run build -------------------------------------------------------------------------------- /server/test/__snapshots__/app.e2e-spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AppController (e2e) /api (GET) API Information 1`] = ` 4 | Object { 5 | "author": "thatkookooguy ", 6 | "bugs": "https://github.com/Kibibit/kb-server-client-template/issues", 7 | "description": "", 8 | "license": "MIT", 9 | "name": "@kibibit/kb-server-client-template", 10 | "repository": "https://github.com/Kibibit/kb-server-client-template.git", 11 | "version": "0.0.0-development", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /server/src/tasks/tasks.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { TasksService } from '@kb-tasks'; 4 | 5 | describe('TasksService', () => { 6 | let service: TasksService; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [TasksService] 11 | }).compile(); 12 | 13 | service = module.get(TasksService); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(service).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/events/events.gateway.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { EventsGateway } from '@kb-events'; 4 | 5 | describe('EventsGateway', () => { 6 | let gateway: EventsGateway; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [EventsGateway] 11 | }).compile(); 12 | 13 | gateway = module.get(EventsGateway); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(gateway).toBeDefined(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/src/models/api.model.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class ApiInfo { 4 | @ApiProperty() 5 | name: string; 6 | 7 | @ApiProperty() 8 | description: string; 9 | 10 | @ApiProperty() 11 | version: string; 12 | 13 | @ApiProperty() 14 | license: string; 15 | 16 | @ApiProperty() 17 | repository: string; 18 | 19 | @ApiProperty() 20 | author: string; 21 | 22 | @ApiProperty() 23 | bugs: string; 24 | 25 | constructor(partial: Partial = {}) { 26 | Object.assign(this, partial); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/api/product/product.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | 4 | import { Product } from '../../models/product.model'; 5 | import { ProductController } from './product.controller'; 6 | import { ProductService } from './product.service'; 7 | 8 | @Module({ 9 | imports: [ 10 | MongooseModule.forFeature([ 11 | { name: Product.modelName, schema: Product.schema } 12 | ]) 13 | ], 14 | providers: [ProductService], 15 | controllers: [ProductController] 16 | }) 17 | export class ProductModule {} 18 | -------------------------------------------------------------------------------- /server/src/decorators/get-all.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Get, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | export function GetAll(model: any, path?: string | string[]) { 11 | return applyDecorators( 12 | Get(path), 13 | ApiOperation({ summary: `Get all ${ model.name }` }), 14 | ApiOkResponse({ description: `Return a list of all ${ model.name }s` }), 15 | UseInterceptors(ClassSerializerInterceptor) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /server/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ScheduleModule } from '@nestjs/schedule'; 3 | 4 | import { ApiModule } from '@kb-api'; 5 | import { EventsGateway, EventsModule } from '@kb-events'; 6 | import { TasksService } from '@kb-tasks'; 7 | 8 | import { AppController } from './app.controller'; 9 | import { AppService } from './app.service'; 10 | 11 | @Module({ 12 | imports: [ 13 | ApiModule, 14 | ScheduleModule.forRoot(), 15 | EventsModule 16 | ], 17 | controllers: [AppController], 18 | providers: [AppService, TasksService, EventsGateway] 19 | }) 20 | export class AppModule {} 21 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "disableTypeScriptVersionCheck": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/src/models/public-error.model.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus } from '@nestjs/common'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class PublicError { 5 | @ApiProperty() 6 | statusCode: HttpStatus; 7 | 8 | // toISOString formatted Date 9 | @ApiProperty() 10 | timestamp: string; 11 | 12 | @ApiProperty() 13 | path: string; 14 | 15 | @ApiProperty() 16 | name: string; 17 | 18 | @ApiProperty({ 19 | oneOf: [{ type: 'string' }, { type: '[string]' }] 20 | }) 21 | error: string | string[]; 22 | 23 | constructor(partial: Partial = {}) { 24 | Object.assign(this, partial); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/api/api.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { ApiController } from '@kb-api'; 4 | 5 | describe('ApiController', () => { 6 | let controller: ApiController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ApiController] 11 | }).compile(); 12 | 13 | controller = module.get(ApiController); 14 | }); 15 | 16 | it('should be defined', () => { 17 | expect(controller).toBeDefined(); 18 | }); 19 | 20 | it.skip('should return package.json object', () => { 21 | expect(controller.getAPI()).toBe('API'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Res } from '@nestjs/common'; 2 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; 3 | import { Response } from 'express'; 4 | import { join } from 'path'; 5 | 6 | import { AppService } from './app.service'; 7 | 8 | @Controller() 9 | export class AppController { 10 | constructor(private readonly appService: AppService) {} 11 | 12 | @Get() 13 | @ApiOperation({ summary: 'Get Web Client (HTML)' }) 14 | @ApiOkResponse({ 15 | description: 'Returns the Web Client\'s HTML File' 16 | }) 17 | sendWebClient(@Res() res: Response): void { 18 | res.sendFile(join(this.appService.appRoot, '/dist/client/index.html')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { browser, logging } from 'protractor'; 2 | 3 | import { AppPage } from './app.po'; 4 | 5 | describe('workspace-project App', () => { 6 | let page: AppPage; 7 | 8 | beforeEach(() => { 9 | page = new AppPage(); 10 | }); 11 | 12 | it('should display welcome message', () => { 13 | page.navigateTo(); 14 | expect(page.getTitleText()).toEqual('kibibit Client Side'); 15 | }); 16 | 17 | afterEach(async () => { 18 | // Assert that there are no errors emitted from the browser 19 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 20 | expect(logs).not.toContain(jasmine.objectContaining({ 21 | level: logging.Level.SEVERE 22 | } as logging.Entry)); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /server/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import findRoot from 'find-root'; 3 | import { pathExistsSync, readJSONSync } from 'fs-extra'; 4 | import { join } from 'path'; 5 | 6 | @Injectable() 7 | export class AppService { 8 | appRoot = findRoot(__dirname, (dir) => { 9 | const packagePath = join(dir, 'package.json'); 10 | const isPackageJsonExists = pathExistsSync(packagePath); 11 | 12 | if (isPackageJsonExists) { 13 | const packageContent = readJSONSync(packagePath, { encoding: 'utf8' }); 14 | if (['server', 'client'].indexOf(packageContent.name) < 0) { 15 | return true; 16 | } 17 | } 18 | 19 | return false; 20 | }) 21 | 22 | getHello(): string { 23 | return 'Hello World!'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - dummy 6 | # - master 7 | # - next 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - name: Checkout commit 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 12 21 | - name: Install dependencies 22 | run: | 23 | npm ci 24 | npm install 25 | npm run build 26 | - name: Release 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | run: npm run semantic-release -------------------------------------------------------------------------------- /client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | 5 | import { 6 | AngularMaterialModule 7 | } from './angular-material/angular-material.module'; 8 | import { AppRoutingModule } from './app-routing.module'; 9 | import { AppComponent } from './app.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | AppRoutingModule, 18 | BrowserAnimationsModule, 19 | AngularMaterialModule 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent], 23 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 24 | }) 25 | export class AppModule { } 26 | -------------------------------------------------------------------------------- /server/src/decorators/kb-post.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Post, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { ApiCreatedResponse, ApiOperation } from '@nestjs/swagger'; 8 | 9 | import { KbApiValidateErrorResponse } from '@kb-decorators'; 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | export function KbPost(type: any, path?: string | string[]) { 13 | return applyDecorators( 14 | Post(path), 15 | ApiOperation({ summary: `Create a new ${ type.name }` }), 16 | ApiCreatedResponse({ 17 | description: `The ${ type.name } has been successfully created.`, 18 | type 19 | }), 20 | KbApiValidateErrorResponse(), 21 | UseInterceptors(ClassSerializerInterceptor) 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | * @thatkookooguy @ortichon @dunaevsky @zimgil 9 | 10 | # Server Owners 11 | /server/ @thatkookooguy 12 | 13 | # Client Owners 14 | /client/ @thatkookooguy 15 | 16 | # Achievements Owners 17 | # These are the people that understand the pattern to create 18 | # and work with achievements 19 | /achievements/ @thatkookooguy @ortichon @dunaevsky @zimgil 20 | 21 | # Build\Github Actions Owners 22 | /tools/ @thatkookooguy 23 | /.github/ @thatkookooguy 24 | /.devcontainer/ @thatkookooguy 25 | /.vscode/ @thatkookooguy 26 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /server/src/models/product.model.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; 2 | import { prop as PersistInDb } from '@typegoose/typegoose'; 3 | import { Exclude, Expose } from 'class-transformer'; 4 | import { IsNotEmpty } from 'class-validator'; 5 | 6 | import { BaseModel } from '../abstracts/base.model.abstract'; 7 | 8 | @Exclude() 9 | export class Product extends BaseModel { 10 | 11 | @Expose() 12 | @IsNotEmpty() 13 | @ApiProperty() 14 | @PersistInDb() 15 | name: string; 16 | 17 | @Expose() 18 | @ApiPropertyOptional() 19 | @PersistInDb() 20 | description: string; 21 | 22 | @Expose() 23 | @IsNotEmpty() 24 | @ApiProperty() 25 | @PersistInDb() 26 | price: number; 27 | 28 | constructor(partial: Partial = {}) { 29 | super(); 30 | Object.assign(this, partial); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 11 | 16 | 20 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /server/src/api/product/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { ReturnModelType } from '@typegoose/typegoose'; 4 | 5 | import { BaseService } from '../../abstracts/base.service.abstract'; 6 | import { Product } from '../../models/product.model'; 7 | 8 | @Injectable() 9 | export class ProductService extends BaseService { 10 | constructor( 11 | @InjectModel(Product.modelName) 12 | private readonly productModel: ReturnModelType, 13 | ) { 14 | super(productModel, Product); 15 | } 16 | 17 | async findByName(name: string): Promise { 18 | const dbProduct = await this.findOne({ name }).exec(); 19 | 20 | if (!dbProduct) { 21 | return; 22 | } 23 | 24 | return new Product(dbProduct.toObject()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/decorators/get-one.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Get, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { 8 | ApiBadRequestResponse, 9 | ApiNotFoundResponse, 10 | ApiOkResponse, 11 | ApiOperation 12 | } from '@nestjs/swagger'; 13 | 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | export function GetOne(type: any, path?: string | string[]) { 16 | return applyDecorators( 17 | Get(path), 18 | ApiOperation({ summary: `Get an existing ${ type.name }` }), 19 | ApiOkResponse({ description: `Return a single ${ type.name }`, type }), 20 | ApiNotFoundResponse({ description: `${ type.name } not found` }), 21 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }), 22 | UseInterceptors(ClassSerializerInterceptor) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads 2 | // recursively all the .spec and framework files 3 | import 'zone.js/dist/zone-testing'; 4 | 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | 12 | declare const require: { 13 | context(path: string, deep?: boolean, filter?: RegExp): { 14 | keys(): string[]; 15 | (id: string): T; 16 | }; 17 | }; 18 | 19 | // First, initialize the Angular testing environment. 20 | getTestBed().initTestEnvironment( 21 | BrowserDynamicTestingModule, 22 | platformBrowserDynamicTesting() 23 | ); 24 | // Then we find all the tests. 25 | const context = require.context('./', true, /\.spec\.ts$/); 26 | // And load the modules. 27 | context.keys().map(context); 28 | -------------------------------------------------------------------------------- /server/src/decorators/kb-delete.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Delete, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { 8 | ApiBadRequestResponse, 9 | ApiNotFoundResponse, 10 | ApiOkResponse, 11 | ApiOperation 12 | } from '@nestjs/swagger'; 13 | 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | export function KbDelete(type: any, path?: string | string[]) { 16 | return applyDecorators( 17 | Delete(path), 18 | ApiOperation({ 19 | summary: `Delete an existing ${ type.name }` 20 | }), 21 | ApiOkResponse({ type: type, description: `${ type.name } deleted` }), 22 | ApiNotFoundResponse({ 23 | description: `${ type.name } not found` 24 | }), 25 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }), 26 | UseInterceptors(ClassSerializerInterceptor) 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | "outDir": "../dist/server", 13 | "baseUrl": "./", 14 | "paths": { 15 | "@kb-server": [ "src/*" ], 16 | "@kb-models": [ "src/models/index" ], 17 | "@kb-abstracts": [ "src/abstracts/index" ], 18 | "@kb-decorators": [ "src/decorators/index" ], 19 | "@kb-filters": [ "src/filters/index" ], 20 | "@kb-events": [ "src/events/index" ], 21 | "@kb-tasks": [ "src/tasks/index" ], 22 | "@kb-app": [ "src/app/index" ], 23 | "@kb-api": [ "src/api/index" ] 24 | 25 | }, 26 | "incremental": true, 27 | "skipLibCheck": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/src/app/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { mockResponse } from 'jest-mock-req-res'; 3 | 4 | import { AppController, AppService } from '@kb-app'; 5 | 6 | describe('AppController', () => { 7 | let appController: AppController; 8 | 9 | beforeEach(async () => { 10 | const app: TestingModule = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService] 13 | }).compile(); 14 | 15 | appController = app.get(AppController); 16 | }); 17 | 18 | describe('root', () => { 19 | it('should return an HTML page', async () => { 20 | const mocRes = mockResponse(); 21 | appController.sendWebClient(mocRes); 22 | expect(mocRes.sendFile.mock.calls.length).toBe(1); 23 | const param = mocRes.sendFile.mock.calls[0][0] as string; 24 | expect(param.endsWith('dist/client/index.html')).toBeTruthy(); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /server/src/filters/kb-not-found-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | NotFoundException 6 | } from '@nestjs/common'; 7 | import { join } from 'path'; 8 | 9 | @Catch(NotFoundException) 10 | export class KbNotFoundExceptionFilter implements ExceptionFilter { 11 | constructor(private readonly appRoot: string) {} 12 | 13 | catch(exception: NotFoundException, host: ArgumentsHost) { 14 | const ctx = host.switchToHttp(); 15 | const response = ctx.getResponse(); 16 | const request = ctx.getRequest(); 17 | const path: string = request.path; 18 | 19 | if (path.startsWith('/api/')) { 20 | response.status(exception.getStatus()).json({ 21 | statusCode: exception.getStatus(), 22 | name: exception.name, 23 | error: exception.message 24 | }); 25 | 26 | return; 27 | } 28 | 29 | response.sendFile( 30 | join(this.appRoot, './dist/client/index.html') 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "kb-server-client-template", 3 | "projectOwner": "Kibibit", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "badgeTemplate": "-orange.svg?style=flat-square\" alt=\"All Contributors\">", 10 | "imageSize": 100, 11 | "commit": false, 12 | "commitConvention": "angular", 13 | "contributors": [ 14 | { 15 | "login": "Thatkookooguy", 16 | "name": "Neil Kalman", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/10427304?v=4", 18 | "profile": "http://thatkookooguy.kibibit.io/", 19 | "contributions": [ 20 | "code", 21 | "doc", 22 | "design", 23 | "maintenance", 24 | "infra", 25 | "test" 26 | ] 27 | } 28 | ], 29 | "contributorsPerLine": 7, 30 | "skipCi": true 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | paths: 6 | - 'client/**' 7 | push: 8 | branches: 9 | - master 10 | - next 11 | jobs: 12 | unittest: 13 | name: E2E Tests 14 | runs-on: ubuntu-18.04 15 | steps: 16 | - name: Checkout Commit 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 12 24 | - name: Start MongoDB 25 | uses: supercharge/mongodb-github-action@1.3.0 26 | with: 27 | mongodb-version: 4.2 28 | - name: Install Dependencies 29 | run: | 30 | npm ci 31 | npm install 32 | - name: Build 33 | run: npm run build --if-present 34 | - name: Run E2E Tests 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | run: npm run test:e2e -------------------------------------------------------------------------------- /server/src/decorators/kb-put.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Put, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { 8 | ApiBadRequestResponse, 9 | ApiNotFoundResponse, 10 | ApiOkResponse, 11 | ApiOperation 12 | } from '@nestjs/swagger'; 13 | 14 | import { KbApiValidateErrorResponse } from '@kb-decorators'; 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | export function KbPut(type: any, path?: string | string[]) { 18 | return applyDecorators( 19 | Put(path), 20 | ApiOperation({ 21 | summary: `Update an existing ${ type.name }`, 22 | description: `Expects a full ${ type.name }` 23 | }), 24 | ApiOkResponse({ type: type, description: `${ type.name } updated` }), 25 | ApiNotFoundResponse({ 26 | description: `${ type.name } not found` 27 | }), 28 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }), 29 | KbApiValidateErrorResponse(), 30 | UseInterceptors(ClassSerializerInterceptor) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /server/src/decorators/kb-patch.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyDecorators, 3 | ClassSerializerInterceptor, 4 | Patch, 5 | UseInterceptors 6 | } from '@nestjs/common'; 7 | import { 8 | ApiBadRequestResponse, 9 | ApiNotFoundResponse, 10 | ApiOkResponse, 11 | ApiOperation 12 | } from '@nestjs/swagger'; 13 | 14 | import { KbApiValidateErrorResponse } from '@kb-decorators'; 15 | 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | export function KbPatch(type: any, path?: string | string[]) { 18 | return applyDecorators( 19 | Patch(path), 20 | ApiOperation({ 21 | summary: `Update an existing ${ type.name }`, 22 | description: `Expects a partial ${ type.name }` 23 | }), 24 | ApiOkResponse({ type: type, description: `${ type.name } updated` }), 25 | ApiNotFoundResponse({ 26 | description: `${ type.name } not found` 27 | }), 28 | ApiBadRequestResponse({ description: 'Invalid identifier supplied' }), 29 | KbApiValidateErrorResponse(), 30 | UseInterceptors(ClassSerializerInterceptor) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /server/src/filters/kb-validation-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | BadRequestException, 4 | Catch, 5 | ExceptionFilter, 6 | HttpStatus 7 | } from '@nestjs/common'; 8 | 9 | import { PublicError } from '@kb-models'; 10 | 11 | @Catch(BadRequestException) 12 | export class KbValidationExceptionFilter implements ExceptionFilter { 13 | catch(exception: BadRequestException, host: ArgumentsHost) { 14 | console.error(exception); 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | const request = ctx.getRequest(); 18 | 19 | response 20 | .status(HttpStatus.METHOD_NOT_ALLOWED) 21 | // you can manipulate the response here 22 | .json(new PublicError({ 23 | statusCode: HttpStatus.METHOD_NOT_ALLOWED, 24 | timestamp: new Date().toISOString(), 25 | path: request.url, 26 | name: 'BadRequestException', 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | error: (exception.getResponse() as any).message as string[] 29 | })); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /server/src/bootstrap-application.ts: -------------------------------------------------------------------------------- 1 | import { terminalConsoleLogo } from '@kibibit/consologo'; 2 | import { ValidationPipe } from '@nestjs/common'; 3 | import { NestFactory } from '@nestjs/core'; 4 | import { NestExpressApplication } from '@nestjs/platform-express'; 5 | import { WsAdapter } from '@nestjs/platform-ws'; 6 | import { join } from 'path'; 7 | 8 | import { AppModule, AppService } from '@kb-app'; 9 | import { KbNotFoundExceptionFilter } from '@kb-filters'; 10 | 11 | import { Swagger } from './swagger'; 12 | 13 | const appRoot = new AppService().appRoot; 14 | 15 | export async function bootstrap(): Promise { 16 | terminalConsoleLogo('kibibit server template', [ 17 | 'change this in server/src/bootstrap-application.ts' 18 | ]); 19 | const app = await NestFactory.create(AppModule); 20 | app.useWebSocketAdapter(new WsAdapter(app)) 21 | app.useGlobalFilters(new KbNotFoundExceptionFilter(appRoot)); 22 | app.useGlobalPipes(new ValidationPipe()); 23 | app.useStaticAssets(join(appRoot, './dist/client')); 24 | 25 | await Swagger.addSwagger(app); 26 | 27 | return app; 28 | } 29 | -------------------------------------------------------------------------------- /server/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common/interfaces'; 2 | import { TestingModule } from '@nestjs/testing/testing-module'; 3 | import * as bodyParser from 'body-parser'; 4 | import express from 'express'; 5 | 6 | import { SocketService } from './socket.service'; 7 | 8 | export class Utils { 9 | public static socket: SocketService; 10 | private static server: express.Express; 11 | private static app: INestApplication; 12 | private static module: TestingModule; 13 | 14 | public static async startServer(testingModule: TestingModule) { 15 | this.module = testingModule; 16 | this.server = express(); 17 | this.server.use(bodyParser.json()); 18 | this.app = await testingModule.createNestApplication(); 19 | await this.app.init(); 20 | } 21 | 22 | public static async createSocket(defer = false) { 23 | await this.app.listen(10109); 24 | this.socket = new SocketService(defer); 25 | 26 | return this.socket; 27 | } 28 | public static async closeApp() { 29 | this.socket.close(); 30 | await this.app.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome', 17 | chromeOptions: { 18 | args: [ '--headless', '--disable-gpu', '--window-size=800,600', '--log-level=3', '--no-sandbox' ] 19 | } 20 | }, 21 | directConnect: true, 22 | baseUrl: 'http://localhost:10101/', 23 | framework: 'jasmine', 24 | jasmineNodeOpts: { 25 | showColors: true, 26 | defaultTimeoutInterval: 30000, 27 | print: function() {} 28 | }, 29 | onPrepare() { 30 | require('ts-node').register({ 31 | project: require('path').join(__dirname, './tsconfig.json') 32 | }); 33 | jasmine.getEnv().addReporter(new SpecReporter({ 34 | spec: { 35 | displayStacktrace: StacktraceOption.PRETTY 36 | } 37 | })); 38 | } 39 | }; -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import request from 'supertest'; 4 | 5 | import { AppModule } from '@kb-app'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | let server; 10 | 11 | beforeEach(async () => { 12 | const moduleFixture: TestingModule = await Test.createTestingModule({ 13 | imports: [AppModule] 14 | }).compile(); 15 | 16 | app = moduleFixture.createNestApplication(); 17 | await app.init(); 18 | server = app.getHttpServer(); 19 | }); 20 | 21 | afterEach(async() => { 22 | await app.close(); 23 | }); 24 | 25 | test('/api (GET) API Information', async () => { 26 | const response = await request(server).get('/api'); 27 | expect(response.status).toBe(200); 28 | expect(response.body).toMatchSnapshot(); 29 | }); 30 | 31 | test('/ (GET) HTML of client application', async () => { 32 | const response = await request(server).get('/'); 33 | // console.log(response.body); 34 | expect(response.status).toBe(200); 35 | expect(response.type).toMatch(/html/); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | ], 12 | ignorePatterns: [ '.eslintrc.js' ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | rules: { 19 | 'eol-last': [ 2, 'windows' ], 20 | 'comma-dangle': [ 'error', 'never' ], 21 | 'max-len': [ 'error', { 'code': 80, "ignoreComments": true } ], 22 | 'quotes': ["error", "single"], 23 | '@typescript-eslint/no-empty-interface': 'error', 24 | '@typescript-eslint/member-delimiter-style': 'error', 25 | '@typescript-eslint/explicit-function-return-type': 'off', 26 | '@typescript-eslint/explicit-module-boundary-types': 'off', 27 | '@typescript-eslint/naming-convention': [ 28 | "error", 29 | { 30 | "selector": "interface", 31 | "format": ["PascalCase"], 32 | "custom": { 33 | "regex": "^I[A-Z]", 34 | "match": true 35 | } 36 | } 37 | ] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/api-tests.yml: -------------------------------------------------------------------------------- 1 | name: API Tests 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | paths: 6 | - 'server/**' 7 | push: 8 | branches: 9 | - master 10 | - next 11 | jobs: 12 | unittest: 13 | name: API Tests 14 | runs-on: ubuntu-18.04 15 | steps: 16 | - name: Checkout Commit 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 12 24 | - name: Start MongoDB 25 | uses: supercharge/mongodb-github-action@1.3.0 26 | with: 27 | mongodb-version: 4.2 28 | - name: Install Dependencies 29 | run: | 30 | npm ci 31 | npm install 32 | - name: Build 33 | run: npm run build --if-present 34 | - name: Run API Tests 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | run: npm run test:api 39 | - name: Archive test results & coverage 40 | uses: actions/upload-artifact@v2 41 | with: 42 | name: server-unit-tests-code-coverage-report 43 | path: test-results/api -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | }, 9 | "setupFilesAfterEnv": [ 10 | "../test-tools/jest.setup.ts" 11 | ], 12 | "coverageDirectory": "../../test-results/api/coverage", 13 | "reporters": [ 14 | "default", 15 | [ 16 | "jest-stare", 17 | { 18 | "resultDir": "../test-results/api", 19 | "reportTitle": "jest-stare!", 20 | "additionalResultsProcessors": [ 21 | "jest-junit" 22 | ], 23 | "coverageLink": "./coverage/lcov-report/index.html" 24 | } 25 | ] 26 | ], 27 | "moduleNameMapper": { 28 | "^@kb-server$": "/../src", 29 | "^@kb-models$": "/../src/models/index", 30 | "^@kb-abstracts$": "/../src/abstracts/index", 31 | "^@kb-decorators$": "/../src/decorators/index", 32 | "^@kb-filters$": "/../src/filters/index", 33 | "^@kb-api$": "/../src/api/index", 34 | "^@kb-events$": "/../src/events/index", 35 | "^@kb-app$": "/../src/app/index", 36 | "^@kb-tasks": "/../src/tasks/index" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/server-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Server Unit Tests 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | paths: 6 | - 'server/**' 7 | push: 8 | paths: 9 | - 'server/**' 10 | 11 | jobs: 12 | serverunittest: 13 | name: Server Unit Tests 14 | runs-on: ubuntu-18.04 15 | steps: 16 | - name: Checkout Commit 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 12 24 | - name: Install Dependencies 25 | run: | 26 | npm ci 27 | npm install 28 | - name: Build 29 | run: npm run build --if-present 30 | - name: Run Server Tests 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | run: npm run test:server-unit 35 | - name: Archive test results & coverage 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: server-unit-tests-code-coverage-report 39 | path: test-results/server 40 | # - name: Code Coverage Report 41 | # uses: romeovs/lcov-reporter-action@v0.2.16 42 | # with: 43 | # github-token: ${{ secrets.GITHUB_TOKEN }} 44 | # lcov-file: coverage/server/lcov.info -------------------------------------------------------------------------------- /.github/workflows/client-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Client Unit Tests 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | paths: 6 | - 'client/**' 7 | push: 8 | paths: 9 | - 'client/**' 10 | 11 | jobs: 12 | clientunittest: 13 | name: Client Unit Tests 14 | runs-on: ubuntu-18.04 15 | steps: 16 | - name: Checkout Commit 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 12 24 | - name: Install Dependencies 25 | run: | 26 | npm ci 27 | npm install 28 | - name: Build 29 | run: npm run build --if-present 30 | - name: Run Client Tests 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | run: npm run test:client-unit 35 | - name: Archive test Results & Coverage 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: server-unit-tests-code-coverage-report 39 | path: test-results/client 40 | # - name: Code Coverage Report 41 | # uses: romeovs/lcov-reporter-action@v0.2.16 42 | # with: 43 | # github-token: ${{ secrets.GITHUB_TOKEN }} 44 | # lcov-file: coverage/client/report-html/lcov.info -------------------------------------------------------------------------------- /client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { 5 | AngularMaterialModule 6 | } from './angular-material/angular-material.module'; 7 | import { AppComponent } from './app.component'; 8 | 9 | describe('AppComponent', () => { 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [ 13 | RouterTestingModule, 14 | AngularMaterialModule 15 | ], 16 | declarations: [ 17 | AppComponent 18 | ] 19 | }).compileComponents(); 20 | }); 21 | 22 | it('should create the app', () => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.componentInstance; 25 | expect(app).toBeTruthy(); 26 | }); 27 | 28 | it(`should have as title 'kibibit client'`, () => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | const app = fixture.componentInstance; 31 | expect(app.title).toEqual('kibibit client'); 32 | }); 33 | 34 | it('should render title', () => { 35 | const fixture = TestBed.createComponent(AppComponent); 36 | fixture.detectChanges(); 37 | const compiled = fixture.nativeElement; 38 | expect(compiled.querySelector('#site-name').textContent) 39 | .toContain('kibibit Client Side'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tools/scripts/replace-template-string.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const replace = require('replace-in-file'); 3 | 4 | const projectNameArgs = process.argv.slice(2); 5 | const projectName = projectNameArgs[projectNameArgs.length - 1]; 6 | 7 | if (!projectName) { 8 | throw new Error('must pass a project name'); 9 | } else { 10 | console.log(projectName); 11 | // return; 12 | } 13 | 14 | const readmeFile = { 15 | files: './README.md', 16 | from: /kb-server-client-template/g, 17 | to: projectName, 18 | }; 19 | 20 | const contributorsFile = { 21 | files: './.all-contributorsrc', 22 | from: /kb-server-client-template/g, 23 | to: projectName, 24 | }; 25 | 26 | const packageFile = { 27 | files: './package.json', 28 | from: /kb-server-client-template/g, 29 | to: projectName, 30 | }; 31 | 32 | const packageLockFile = { 33 | files: './package-lock.json', 34 | from: /kb-server-client-template/g, 35 | to: projectName, 36 | }; 37 | 38 | 39 | 40 | (async () => { 41 | try { 42 | let results = []; 43 | results.push(await replace(readmeFile)); 44 | results.push(await replace(packageFile)); 45 | results.push(await replace(packageLockFile)); 46 | results.push(await replace(contributorsFile)); 47 | console.log('Replacement results:', results); 48 | } 49 | catch (error) { 50 | console.error('Error occurred:', error); 51 | } 52 | })(); 53 | -------------------------------------------------------------------------------- /server/src/abstracts/base.model.abstract.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, prop as PersistInDb } from '@typegoose/typegoose'; 2 | import { classToPlain, Exclude, Expose } from 'class-transformer'; 3 | import { Schema } from 'mongoose'; 4 | 5 | @Exclude() 6 | export abstract class BaseModel { 7 | @PersistInDb() 8 | createdDate?: Date; // provided by timestamps 9 | @Expose() 10 | @PersistInDb() 11 | updatedDate?: Date; // provided by timestamps 12 | 13 | // @Expose({ name: 'id' }) 14 | // @Transform(({ value }) => value && value.toString()) 15 | // tslint:disable-next-line: variable-name 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | _id?: any; 18 | 19 | id?: string; // is actually model._id getter 20 | 21 | // tslint:disable-next-line: variable-name 22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 23 | _v?: any; 24 | 25 | // add more to a base model if you want. 26 | 27 | toJSON() { 28 | return classToPlain(this); 29 | } 30 | 31 | toString() { 32 | return JSON.stringify(this.toJSON()); 33 | } 34 | 35 | static get schema(): Schema { 36 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 37 | return buildSchema(this as any, { 38 | timestamps: true, 39 | toJSON: { 40 | getters: true, 41 | virtuals: true 42 | } 43 | }); 44 | } 45 | 46 | static get modelName(): string { 47 | return this.name; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/src/events/events.gateway.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { 3 | MessageBody, 4 | OnGatewayConnection, 5 | SubscribeMessage, 6 | WebSocketGateway, 7 | WebSocketServer, 8 | WsResponse 9 | } from '@nestjs/websockets'; 10 | import { from, Observable } from 'rxjs'; 11 | import { map } from 'rxjs/operators'; 12 | import { Server } from 'socket.io'; 13 | 14 | @WebSocketGateway() 15 | export class EventsGateway implements OnGatewayConnection { 16 | private logger: Logger = new Logger('AppGateway'); 17 | 18 | @WebSocketServer() 19 | server: Server; 20 | 21 | constructor() { 22 | this.logger.log('testing, testing...'); 23 | } 24 | 25 | @SubscribeMessage('message') 26 | handleMessage(@MessageBody() data: unknown): WsResponse { 27 | this.logger.log('got here!'); 28 | 29 | return { 30 | event: 'message-response', 31 | data 32 | }; 33 | } 34 | 35 | @SubscribeMessage('events') 36 | onEvent(/* @MessageBody() data: unknown */): Observable> { 37 | const event = 'events'; 38 | const response = [1, 2, 3]; 39 | 40 | return from(response).pipe( 41 | map(data => ({ event, data })) 42 | ); 43 | } 44 | 45 | handleDisconnect() { 46 | this.logger.log('Client disconnected:'); 47 | // console.log(client); 48 | } 49 | 50 | handleConnection() { 51 | this.logger.log('Client connected:'); 52 | // console.log(client); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/src/api/api.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger } from '@nestjs/common'; 2 | import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; 3 | import { readJSON } from 'fs-extra'; 4 | import { chain } from 'lodash'; 5 | import { join } from 'path'; 6 | 7 | import { AppService } from '@kb-app'; 8 | import { ApiInfo } from '@kb-models'; 9 | 10 | @Controller('api') 11 | export class ApiController { 12 | readonly appRoot = new AppService().appRoot; 13 | 14 | private readonly logger = new Logger(ApiController.name); 15 | 16 | @Get() 17 | @ApiOperation({ summary: 'Get API Information' }) 18 | @ApiOkResponse({ 19 | description: 'Returns API info as a JSON', 20 | type: ApiInfo 21 | }) 22 | async getAPI() { 23 | const packageInfo = await readJSON( 24 | join(this.appRoot, './package.json') 25 | ); 26 | const details = new ApiInfo( 27 | chain(packageInfo) 28 | .pick([ 29 | 'name', 30 | 'description', 31 | 'version', 32 | 'license', 33 | 'repository', 34 | 'author', 35 | 'bugs' 36 | ]) 37 | .mapValues((val) => val.url ? val.url : val) 38 | .value() 39 | ); 40 | this.logger.log('Api information requested'); 41 | return details; 42 | } 43 | 44 | @Get('/nana') 45 | @ApiOperation({ 46 | deprecated: true 47 | }) 48 | async deprecationTest() { 49 | return new Promise((resolve) => setTimeout(() => resolve('hello'), 60000)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "tsconfigRootDir": __dirname, 17 | "createDefaultProgram": true 18 | }, 19 | "extends": [ 20 | "plugin:@angular-eslint/ng-cli-compat", 21 | "plugin:@angular-eslint/ng-cli-compat--formatting-add-on", 22 | "plugin:@angular-eslint/template/process-inline-templates" 23 | ], 24 | "rules": { 25 | 'eol-last': [ 2, 'windows' ], 26 | 'comma-dangle': [ 'error', 'never' ], 27 | 'max-len': [ 'error', { 'code': 80, "ignoreComments": true } ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "app", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@angular-eslint/directive-selector": [ 37 | "error", 38 | { 39 | "type": "attribute", 40 | "prefix": "app", 41 | "style": "camelCase" 42 | } 43 | ] 44 | } 45 | }, 46 | { 47 | "files": [ 48 | "*.html" 49 | ], 50 | "extends": [ 51 | "plugin:@angular-eslint/template/recommended" 52 | ], 53 | "rules": {} 54 | } 55 | ] 56 | }; 57 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | args: 9 | # [Choice] Node.js version: 14, 12, 10 10 | VARIANT: 14 11 | # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. 12 | USER_UID: 1000 13 | USER_GID: 1000 14 | 15 | volumes: 16 | - ..:/workspace:cached 17 | 18 | # Overrides default command so things don't shut down after the process ends. 19 | command: sleep infinity 20 | 21 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 22 | network_mode: service:db 23 | 24 | # Uncomment the next line to use a non-root user for all processes. 25 | # user: node 26 | 27 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 28 | # (Adding the "ports" property to this file will not forward from a Codespace.) 29 | 30 | db: 31 | image: mongo:latest 32 | restart: unless-stopped 33 | volumes: 34 | - mongodb-data:/data/db 35 | 36 | # Uncomment to change startup options 37 | # environment: 38 | # MONGO_INITDB_ROOT_USERNAME: root 39 | # MONGO_INITDB_ROOT_PASSWORD: example 40 | # MONGO_INITDB_DATABASE: your-database-here 41 | 42 | # Add "forwardPorts": ["27017"] to **devcontainer.json** to forward MongoDB locally. 43 | # (Adding the "ports" property to this file will not forward from a Codespace.) 44 | 45 | volumes: 46 | mongodb-data: -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "peacock.color": "#3334f4", 3 | "editor.tabSize": 2, 4 | "editor.insertSpaces": true, 5 | "editor.detectIndentation": false, 6 | "editor.rulers": [80], 7 | "editor.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace", 8 | "editor.matchBrackets": "always", 9 | // Terminal 10 | "terminal.integrated.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace", 11 | "terminal.integrated.fontSize": 14, 12 | // Workbench 13 | "workbench.colorTheme": "Andromeda", 14 | "workbench.editor.showIcons": true, 15 | "workbench.iconTheme": "vs-seti", 16 | // Bracket Pair Colorizer 17 | "bracketPairColorizer.colorMode": "Consecutive", 18 | "bracketPairColorizer.forceUniqueOpeningColor": true, 19 | "bracketPairColorizer.showBracketsInGutter": true, 20 | "window.title": "${activeEditorShort}${separator}${rootName} [kibibit]", 21 | "typescriptHero.imports.stringQuoteStyle": "'", 22 | "typescriptHero.imports.grouping": [ 23 | "Plains", 24 | "Modules", 25 | "/^@kb-/", 26 | "Workspace" 27 | 28 | ], 29 | "typescriptHero.imports.organizeOnSave": true, 30 | "typescriptHero.imports.multiLineTrailingComma": false, 31 | "typescriptHero.imports.multiLineWrapThreshold": 80, 32 | "typescriptHero.imports.insertSpaceBeforeAndAfterImportBraces": true, 33 | "typescriptHero.imports.insertSemicolons": true, 34 | "editor.codeActionsOnSave": { 35 | "source.fixAll.eslint": true, 36 | }, 37 | "eslint.format.enable": true, 38 | "vsicons.presets.angular": true, 39 | "eslint.workingDirectories": [ 40 | "client/", 41 | "server/" 42 | ], 43 | "svg.preview.background": "black" 44 | } -------------------------------------------------------------------------------- /server/test/sockets.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { observe } from 'rxjs-marbles/jest'; 3 | import { take, tap } from 'rxjs/operators'; 4 | 5 | import { AppModule } from '@kb-app'; 6 | 7 | import { SocketService } from './socket.service'; 8 | import { Utils } from './utils'; 9 | 10 | describe('Web Socket Events (e2e)', () => { 11 | let socket: SocketService; 12 | 13 | beforeEach(async () => { 14 | const testingModule: TestingModule = await Test.createTestingModule({ 15 | imports: [AppModule] 16 | }).compile(); 17 | 18 | await Utils.startServer(testingModule); 19 | // each test need a new socket connection 20 | socket = await Utils.createSocket(); 21 | }); 22 | 23 | afterEach(async () => { 24 | // each test need to release the connection for next 25 | await Utils.closeApp(); 26 | }); 27 | 28 | test('socket connection', observe(() => { 29 | return socket.once('connect') 30 | .pipe(tap(() => expect(true).toBeTruthy())); 31 | })); 32 | 33 | test('Events topic', observe(() => { 34 | let counter = 1; 35 | 36 | socket 37 | .once('connect') 38 | .pipe(tap(() => socket.emit('events', { test: 'test' }))) 39 | .subscribe(); 40 | 41 | return socket.on('events') 42 | .pipe( 43 | take(3), 44 | tap(data => expect(data).toBe(counter++)) 45 | ); 46 | })); 47 | 48 | test('Message topic', observe(() => { 49 | const messageData = { test: 'test' }; 50 | socket 51 | .once('connect') 52 | .pipe(tap(() => socket.emit('message', messageData))) 53 | .subscribe(); 54 | 55 | return socket.once('message-response') 56 | .pipe(tap(data => expect(data).toEqual(messageData))); 57 | })); 58 | }); 59 | -------------------------------------------------------------------------------- /tools/scripts/get-all-contributors.js: -------------------------------------------------------------------------------- 1 | const gitlog = require('gitlog').default; 2 | const { join } = require('path'); 3 | const githubUsername = require('github-username'); 4 | const shell = require('shelljs'); 5 | const { forEach, chain } = require('lodash'); 6 | const { readJson } = require('fs-extra'); 7 | 8 | 9 | (async () => { 10 | const allContributorsConfig = await readJson(join(__dirname, '..', '/.all-contributorsrc')); 11 | const data = gitlog({ 12 | repo: join(__dirname, '..'), 13 | fields: ["authorName", "authorEmail", "authorDate"] 14 | }); 15 | 16 | let result = {}; 17 | 18 | for (const commit of data) { 19 | const isCode = commit.files.find((item) => item.startsWith('server/src') || item.startsWith('client/src')); 20 | const isInfra = commit.files.find((item) => item.startsWith('.github/') || item.startsWith('tools/')); 21 | const isTests = commit.files.find((item) => item.endsWith('.spec.ts')); 22 | let types = []; 23 | if (isCode) { types.push('code'); } 24 | if (isInfra) { types.push('infra'); } 25 | if (isTests) { types.push('test'); } 26 | 27 | if (!result[commit.authorEmail]) { 28 | const githubLogin = await githubUsername(commit.authorEmail); 29 | result[commit.authorEmail] = { 30 | githubLogin, 31 | types 32 | }; 33 | } else { 34 | result[commit.authorEmail].types = result[commit.authorEmail].types.concat(types); 35 | } 36 | } 37 | 38 | forEach(result, (person, email) => { 39 | const existing = chain(allContributorsConfig.contributors) 40 | .find((item) => item.login === person.githubLogin) 41 | .get('contributions', []) 42 | .value(); 43 | const types = chain(person.types.concat(existing)).uniq().sortBy().value(); 44 | const command = `npm run contributors:add ${ person.githubLogin } ${ types.join(',') }`; 45 | console.log(command); 46 | shell.exec(command, { cwd: __dirname }); 47 | }) 48 | })(); -------------------------------------------------------------------------------- /client/src/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | // Custom Theming for Angular Material 3 | // For more information: https://material.angular.io/guide/theming 4 | @import '~@angular/material/theming'; 5 | // Plus imports for other components in your app. 6 | 7 | // Define a custom typography config that overrides the font-family as well as the 8 | // `headlines` and `body-1` levels. 9 | $custom-typography: mat-typography-config( 10 | $font-family: "'Comfortaa', cursive", 11 | $headline: mat-typography-level(32px, 48px, 700), 12 | $body-1: mat-typography-level(16px, 24px, 500) 13 | ); 14 | 15 | 16 | // Include the common styles for Angular Material. We include this here so that you only 17 | // have to load a single css file for Angular Material in your app. 18 | // Be sure that you only ever include this mixin once! 19 | @include mat-core($custom-typography); 20 | 21 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 22 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 23 | // hue. Available color palettes: https://material.io/design/color/ 24 | $client-primary: mat-palette($mat-indigo); 25 | $client-accent: mat-palette($mat-pink, A200, A100, A400); 26 | 27 | // The warn palette is optional (defaults to red). 28 | $client-warn: mat-palette($mat-red); 29 | 30 | // Create the theme object. A theme consists of configurations for individual 31 | // theming systems such as "color" or "typography". 32 | $client-theme: mat-dark-theme(( 33 | color: ( 34 | primary: $client-primary, 35 | accent: $client-accent, 36 | warn: $client-warn, 37 | ) 38 | )); 39 | 40 | // Include theme styles for core and each component used in your app. 41 | // Alternatively, you can import and @include the theme mixins for each component 42 | // that you are using. 43 | @include angular-material-theme($client-theme); 44 | 45 | /* You can add global styles to this file, and also import other style files */ 46 | 47 | html, body { height: 100%; } 48 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; background: #212121; } 49 | -------------------------------------------------------------------------------- /tools/initialize.js: -------------------------------------------------------------------------------- 1 | const { terminalConsoleLogo } = require('@kibibit/consologo'); 2 | const inquirer = require('inquirer'); 3 | const Foswig = require('foswig').default; 4 | const Sentencer = require('sentencer'); 5 | const Names = require('./data/names-dictionary'); 6 | const { kebabCase, toLower } = require('lodash'); 7 | const packageNameRegex = require('package-name-regex'); 8 | // const imageToAscii = require('image-to-ascii'); 9 | 10 | //load the words into the markov chain 11 | const chain = new Foswig(3, Names.dictionary); 12 | 13 | // imageToAscii("https://octodex.github.com/images/octofez.png", (err, converted) => { 14 | // console.log(err || converted); 15 | // }); 16 | 17 | const questions = [ 18 | { 19 | type: 'input', 20 | name: 'projectName', 21 | message: 'What is the name of this new project?', 22 | transformer(input) { return `@kibibit/${ input }`; }, 23 | validate(input) { 24 | return packageNameRegex.test(input) || `@kibibit/${ input } is not a valid NPM package name`; 25 | }, 26 | default: '@kibibit/' + toLower(chain.generate({ 27 | minLength: 5, 28 | maxLength: 10, 29 | allowDuplicates: false 30 | })) 31 | }, 32 | { 33 | type: 'input', 34 | name: 'projectDescription', 35 | message: 'Please write a short description for this project', 36 | default: Sentencer.make('This is {{ an_adjective }} {{ noun }}.') 37 | }, 38 | { 39 | type: 'input', 40 | name: 'author', 41 | message: 'Project owner\\author', 42 | default: 'githubusername ' 43 | }, 44 | { 45 | type: 'input', 46 | name: 'githubRepo', 47 | message: 'Paste a url to a github repo or a user/repo pair', 48 | default: 'kibibit/achievibit' 49 | } 50 | ]; 51 | 52 | (async () => { 53 | terminalConsoleLogo('Start a new project', 'Answer these questions to start a new project'); 54 | const answers = await inquirer.prompt(questions); 55 | 56 | answers.projectName = answers.projectName.startsWith('@kibibit/') ? 57 | answers.projectName : `@kibibit/${ answers.projectName }`; 58 | 59 | answers.projectNameSafe = kebabCase(answers.projectName); 60 | 61 | console.log(answers); 62 | })(); -------------------------------------------------------------------------------- /client/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | require('karma-spec-reporter'), 15 | require('karma-htmlfile-reporter') 16 | ], 17 | client: { 18 | captureConsole: false, 19 | clearContext: false // leave Jasmine Spec Runner output visible in browser 20 | }, 21 | preprocessors: { 22 | 'src/**/*!(test|spec).ts': ['coverage'] 23 | }, 24 | // coverageIstanbulReporter: { 25 | // dir: require('path').join(__dirname, '../coverage/client'), 26 | // reports: ['html', 'lcovonly', 'text-summary'], 27 | // fixWebpackSourcePaths: true 28 | // }, 29 | coverageReporter: { 30 | // type : 'html', 31 | dir : '../test-results/client/coverage', 32 | reporters: [ 33 | { type: 'lcov', subdir: 'report-lcov' }, 34 | { type: 'html', subdir: 'report-html' } 35 | ], 36 | fixWebpackSourcePaths: true 37 | }, 38 | 39 | reporters: ['spec', 'html'], 40 | htmlReporter: { 41 | outputFile: '../test-results/client/index.html', 42 | 43 | // Optional 44 | pageTitle: 'Client-Side Unit Tests', 45 | subPageTitle: 'A summary of test results', 46 | groupSuites: true, 47 | useCompactStyle: true, 48 | useLegacyStyle: true, 49 | showOnlyFailed: false 50 | }, 51 | port: 9876, 52 | colors: true, 53 | logLevel: config.LOG_INFO, 54 | autoWatch: true, 55 | browsers: [ 'Chrome_without_sandbox' ], 56 | customLaunchers: { 57 | Chrome_without_sandbox: { 58 | base: 'ChromeHeadless', 59 | flags: [ '--no-sandbox' ] // with sandbox it fails under Docker 60 | } 61 | }, 62 | singleRun: false, 63 | restartOnFileChange: true 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /server/src/swagger.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 3 | import axios from 'axios'; 4 | 5 | interface ISwaggerMethod { 6 | get: (attr: string) => string; 7 | } 8 | 9 | export class Swagger { 10 | static title = 'API Docs example'; 11 | static swaggerPath = 'api/docs'; 12 | static config = new DocumentBuilder() 13 | .setTitle(Swagger.title) 14 | .setDescription('The API description') 15 | .setVersion('1.0') 16 | .setContact( 17 | 'thatkookooguy', 18 | 'github.com/thatkookooguy', 19 | 'thatkookooguy@kibibit.io' 20 | ) 21 | .addTag( 22 | 'product', 23 | [ 24 | 'Product is an example module to show how to ', 25 | 'add **swagger** documentation' 26 | ].join(''), 27 | { 28 | url: 'https://github.com/kibibit', 29 | description: 'See Docs' 30 | } 31 | ) 32 | .build(); 33 | 34 | static getOperationsSorter() { 35 | return (a: ISwaggerMethod, b: ISwaggerMethod) => { 36 | const methodsOrder = [ 37 | 'get', 38 | 'post', 39 | 'put', 40 | 'patch', 41 | 'delete', 42 | 'options', 43 | 'trace' 44 | ]; 45 | let result = 46 | methodsOrder.indexOf( a.get('method') ) - 47 | methodsOrder.indexOf( b.get('method') ); 48 | 49 | if (result === 0) { 50 | result = a.get('path').localeCompare(b.get('path')); 51 | } 52 | 53 | return result; 54 | } 55 | } 56 | 57 | static async addSwagger(app: INestApplication) { 58 | const document = SwaggerModule.createDocument(app, Swagger.config); 59 | const swaggerCssResponse = await axios 60 | .get('https://kibibit.io/kibibit-assets/swagger/swagger.css'); 61 | const customCss = swaggerCssResponse.data; 62 | 63 | SwaggerModule.setup(Swagger.swaggerPath, app, document, { 64 | customSiteTitle: Swagger.title, 65 | customCss, 66 | customJs: '//kibibit.io/kibibit-assets/swagger/swagger.js', 67 | swaggerOptions: { 68 | docExpansion: 'none', 69 | apisSorter: 'alpha', 70 | operationsSorter: Swagger.getOperationsSorter() 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version: 14, 12, 10 4 | ARG VARIANT="14-buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} 6 | 7 | # Install MongoDB command line tools 8 | ARG MONGO_TOOLS_VERSION=4.2 9 | RUN curl -sSL "https://www.mongodb.org/static/pgp/server-${MONGO_TOOLS_VERSION}.asc" | (OUT=$(apt-key add - 2>&1) || echo $OUT) \ 10 | && echo "deb http://repo.mongodb.org/apt/debian $(lsb_release -cs)/mongodb-org/${MONGO_TOOLS_VERSION} main" | tee /etc/apt/sources.list.d/mongodb-org-${MONGO_TOOLS_VERSION}.list \ 11 | && apt-get update && export DEBIAN_FRONTEND=noninteractive \ 12 | && apt-get install -y mongodb-org-tools mongodb-org-shell \ 13 | && apt-get clean -y && rm -rf /var/lib/apt/lists/* 14 | 15 | # Install Chrome Driver 16 | RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - 17 | RUN echo 'deb http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list 18 | RUN apt-get update && apt-get install --no-install-recommends -y google-chrome-stable 19 | 20 | # Update args in docker-compose.yaml to set the UID/GID of the "node" user. 21 | ARG USER_UID=1000 22 | ARG USER_GID=$USER_UID 23 | RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then groupmod --gid $USER_GID node && usermod --uid $USER_UID --gid $USER_GID node; fi 24 | 25 | # [Optional] Uncomment this section to install additional OS packages. 26 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 27 | && apt-get -y install --no-install-recommends zsh 28 | RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true 29 | RUN wget https://raw.githubusercontent.com/zakaziko99/agnosterzak-ohmyzsh-theme/master/agnosterzak.zsh-theme -P /home/node/.oh-my-zsh/themes 30 | RUN sed -i 's/ZSH\_THEME\=\".*\"/ZSH_THEME\=\"agnosterzak\"/' /home/node/.zshrc 31 | RUN echo "git config --global core.editor \"code --wait\"" >> /home/node/.zshrc 32 | 33 | # [Optional] Uncomment if you want to install an additional version of node using nvm 34 | # ARG EXTRA_NODE_VERSION=10 35 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 36 | 37 | # [Optional] Uncomment if you want to install more global node packages 38 | RUN su node -c "npm install -g @angular/cli @nestjs/cli" 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | test-results 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # Snowpack dependency directory (https://snowpack.dev/) 48 | web_modules/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | .parcel-cache 81 | 82 | # Next.js build output 83 | .next 84 | out 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # yarn v2 115 | .yarn/cache 116 | .yarn/unplugged 117 | .yarn/build-state.yml 118 | .yarn/install-state.gz 119 | .pnp.* 120 | -------------------------------------------------------------------------------- /TEMPLATE_INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | # Template Usage Overview 2 | 3 | ## Starting Up 4 | 5 | ### Prequisits 6 | - [vscode](https://code.visualstudio.com/) 7 | - [Docker](https://www.docker.com/) (**OPTIONAL**) 8 | 9 | ### Install the repo 10 | 1. On your local computer, copy the repo using `git clone` 11 | 2. Open the repository in vscode. 12 | 3. you can work in the following modes: 13 | - **[Devcontainer](https://code.visualstudio.com/docs/remote/containers)** - Create a docker container that runs the app with all the tools and extensions necassery. vscode will popup a notification to ask you if it should re-open the project inside a devcontainer. This will also start a mongodb database for the app to use against 14 | - **open locally** - run the repo locally. Should work but you might have some OS differences at edge-cases. You will need to provice a mongodb database link for the app to work against a database. 15 | 4. Open a terminal and run `npm install` inside the main folder. This will install all dependencies. 16 | 17 | ### Rename to your project name 18 | After the dependencies are installed, you need to change the project name from `kb-server-client-template` to whatever project you're developing. In order to do that, you can run: 19 | ``` 20 | npm run init 21 | ``` 22 | This will replace the important parts of the templates to your project name (which should be npm compliant). 23 | 24 | ## Run in Development mode 25 | 26 | ### Server 27 | In the main repo folder, run `npm run start:server` to start the server 28 | in watch mode. The server will be available at `localhost:10102`. 29 | 30 | API swagger documentation is available at `localhost:10102/api/docs` (or through the client proxy). 31 | 32 | A `Product` module is included as an example for a full module\service\controller feature with db. 33 | 34 | ### Client 35 | In the main repo folder, run `npm run start:client` to start the client in local proxy mode. The client will be available at `localhost:10101`. This will re-route all rest calls to 36 | `localhost:10101/api` to the server application running on `localhost:10102`. 37 | 38 | ## Run Production App 39 | In the main repo folder, run the following commands 40 | ``` 41 | npm run build 42 | node . 43 | ``` 44 | 45 | ## Run tests 46 | 47 | ### Server Unit-Tests 48 | In vscode, at the footer, click on `TESTS: Server Unit-tests and Coverage` or run `npm run test:server-unit` 49 | ### Client Unit-tests 50 | In vscode, at the footer, click on `TESTS: Client Unit-tests and Coverage` or run `npm run test:client-unit` 51 | ### API Tests 52 | In vscode, at the footer, click on `TESTS: API-tests` or run `npm run test:api` 53 | ### E2E Tests 54 | In vscode, at the footer, click on `TESTS: E2E-tests` or run `npm run test:e2e` -------------------------------------------------------------------------------- /server/test/socket.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { debounceTime, first, share, tap } from 'rxjs/operators'; 3 | import { connect, Socket } from 'socket.io-client'; 4 | 5 | enum NATIVE_EVENTS { 6 | CONNECT = 'connect', 7 | DISCONNECT = 'disconnect', 8 | ERROR = 'error', 9 | RECONNECT = 'reconnect', 10 | } 11 | 12 | export class SocketService { 13 | private host = 'ws://localhost:10109'; 14 | private events$: Record> = {}; 15 | socket: typeof Socket; 16 | 17 | errors$: Observable; 18 | isConnected$: Observable; 19 | 20 | constructor(defer?: boolean) { 21 | this.socket = connect( 22 | this.host, 23 | { 24 | autoConnect: !defer, 25 | forceNew: true 26 | }, 27 | ); 28 | 29 | this.errors$ = this.on('disconnect-reason'); 30 | this.on(NATIVE_EVENTS.DISCONNECT) 31 | .pipe( 32 | tap(reason => `disconnected: ${reason}`), 33 | debounceTime(5000), 34 | ) 35 | .subscribe(reason => { 36 | if (reason === 'io server disconnect') { 37 | } 38 | }); 39 | } 40 | 41 | on(event: string): Observable { 42 | if (this.events$[event]) { 43 | return this.events$[event]; 44 | } 45 | 46 | this.events$[event] = new Observable(observer => { 47 | this.socket.on(event, observer.next.bind(observer)); 48 | 49 | return () => { 50 | this.socket.off(event); 51 | delete this.events$[event]; 52 | }; 53 | }).pipe(share()); 54 | 55 | return this.events$[event]; 56 | } 57 | 58 | once(event: string): Observable { 59 | return this.on(event).pipe(first()); 60 | } 61 | emit(event: string, data?: T, ack?: false): void; 62 | // This overloading is not working in TS 3.0.1 63 | // emit(event: string, data?: any, ack?: true): Observable; 64 | emit(event: string, data?: T, ack?: true): Observable; 65 | emit( 66 | event: string, 67 | data?: T, 68 | ack?: boolean 69 | ): void | Observable { 70 | if (ack) { 71 | return new Observable(observer => { 72 | this.socket.emit(event, data, observer.next.bind(observer)); 73 | }).pipe(first()); 74 | } else { 75 | this.socket.emit(event, data); 76 | } 77 | } 78 | 79 | open() { 80 | this.socket.connect(); 81 | } 82 | 83 | close() { 84 | this.socket.disconnect(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --port 10101 --poll=2000", 7 | "start:proxy": "ng serve --port 10101 --proxy-config ./proxy.conf.json --poll=2000", 8 | "build": "ng build", 9 | "test:watch": "ng test", 10 | "test": "ng test --no-watch --browsers Chrome_without_sandbox", 11 | "test:cov": "ng test --no-watch --code-coverage --browsers Chrome_without_sandbox", 12 | "test:headed": "ng test --no-watch --code-coverage --browsers=Chrome", 13 | "lint": "ng lint", 14 | "lint:fix": "ng lint client --fix", 15 | "lint:html": "prettyhtml ./src/**/*.html --tab-width 2 --print-width 80 --wrapAttributes true --sortAttributes", 16 | "e2e": "ng e2e", 17 | "e2e:headed": "ng e2e --browsers Chrome" 18 | }, 19 | "private": true, 20 | "dependencies": { 21 | "@angular/animations": "~11.1.2", 22 | "@angular/cdk": "^11.1.2", 23 | "@angular/common": "~11.1.2", 24 | "@angular/compiler": "~11.1.2", 25 | "@angular/core": "~11.1.2", 26 | "@angular/forms": "~11.1.2", 27 | "@angular/material": "^11.1.2", 28 | "@angular/platform-browser": "~11.1.2", 29 | "@angular/platform-browser-dynamic": "~11.1.2", 30 | "@angular/router": "~11.1.2", 31 | "@kibibit/consologo": "^1.2.0", 32 | "rxjs": "~6.6.0", 33 | "tslib": "^2.0.0", 34 | "zone.js": "~0.10.2" 35 | }, 36 | "devDependencies": { 37 | "@angular-devkit/build-angular": "~0.1101.4", 38 | "@angular-eslint/builder": "1.2.0", 39 | "@angular-eslint/eslint-plugin": "1.2.0", 40 | "@angular-eslint/eslint-plugin-template": "1.2.0", 41 | "@angular-eslint/schematics": "1.2.0", 42 | "@angular-eslint/template-parser": "1.2.0", 43 | "@angular/cli": "^11.1.4", 44 | "@angular/compiler-cli": "~11.1.2", 45 | "@starptech/prettyhtml": "^0.10.0", 46 | "@types/jasmine": "~3.5.0", 47 | "@types/jasminewd2": "~2.0.3", 48 | "@types/node": "^14.14.25", 49 | "@typescript-eslint/eslint-plugin": "4.14.2", 50 | "@typescript-eslint/parser": "4.14.2", 51 | "codelyzer": "^6.0.0", 52 | "eslint": "^7.19.0", 53 | "eslint-plugin-import": "2.22.1", 54 | "eslint-plugin-jsdoc": "31.6.1", 55 | "eslint-plugin-prefer-arrow": "1.2.2", 56 | "jasmine-core": "~3.6.0", 57 | "jasmine-spec-reporter": "~5.0.0", 58 | "karma": "^5.2.3", 59 | "karma-chrome-launcher": "~3.1.0", 60 | "karma-coverage": "^2.0.3", 61 | "karma-coverage-istanbul-reporter": "~3.0.2", 62 | "karma-jasmine": "~4.0.0", 63 | "karma-jasmine-html-reporter": "^1.5.0", 64 | "karma-spec-reporter": "0.0.32", 65 | "protractor": "~7.0.0", 66 | "ts-node": "~9.1.1", 67 | "tslint": "~6.1.0", 68 | "typescript": "~4.1.3" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /** 3 | * This file includes polyfills needed by Angular and is loaded before the app. 4 | * You can add your own extra polyfills to this file. 5 | * 6 | * This file is divided into 2 sections: 7 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 8 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 9 | * file. 10 | * 11 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 12 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 13 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 14 | * 15 | * Learn more in https://angular.io/guide/browser-support 16 | */ 17 | 18 | /*************************************************************************************************** 19 | * BROWSER POLYFILLS 20 | */ 21 | 22 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 23 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 24 | 25 | /** 26 | * Web Animations `@angular/platform-browser/animations` 27 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 28 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 29 | */ 30 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 31 | 32 | /** 33 | * By default, zone.js will patch all possible macroTask and DomEvents 34 | * user can disable parts of macroTask/DomEvents patch by setting following flags 35 | * because those flags need to be set before `zone.js` being loaded, and webpack 36 | * will put import in the top of bundle, so user need to create a separate file 37 | * in this directory (for example: zone-flags.ts), and put the following flags 38 | * into that file, and then add the following code before importing zone.js. 39 | * import './zone-flags'; 40 | * 41 | * The flags allowed in zone-flags.ts are listed here. 42 | * 43 | * The following flags will work for all browsers. 44 | * 45 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 46 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 47 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 48 | * 49 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 50 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 51 | * 52 | * (window as any).__Zone_enable_cross_context_check = true; 53 | * 54 | */ 55 | 56 | /*************************************************************************************************** 57 | * Zone JS is required by default for Angular itself. 58 | */ 59 | import 'zone.js/dist/zone'; // Included with Angular CLI. 60 | 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | -------------------------------------------------------------------------------- /client/src/app/angular-material/angular-material.module.ts: -------------------------------------------------------------------------------- 1 | import { OverlayModule } from '@angular/cdk/overlay'; 2 | import { PortalModule } from '@angular/cdk/portal'; 3 | import { CdkTreeModule } from '@angular/cdk/tree'; 4 | import { CommonModule } from '@angular/common'; 5 | import { NgModule } from '@angular/core'; 6 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 7 | import { MatBadgeModule } from '@angular/material/badge'; 8 | import { MatButtonModule } from '@angular/material/button'; 9 | import { MatButtonToggleModule } from '@angular/material/button-toggle'; 10 | import { MatCardModule } from '@angular/material/card'; 11 | import { MatCheckboxModule } from '@angular/material/checkbox'; 12 | import { MatChipsModule } from '@angular/material/chips'; 13 | import { MatRippleModule } from '@angular/material/core'; 14 | import { MatDatepickerModule } from '@angular/material/datepicker'; 15 | import { MatDividerModule } from '@angular/material/divider'; 16 | import { MatExpansionModule } from '@angular/material/expansion'; 17 | import { MatFormFieldModule } from '@angular/material/form-field'; 18 | import { MatGridListModule } from '@angular/material/grid-list'; 19 | import { MatIconModule } from '@angular/material/icon'; 20 | import { MatInputModule } from '@angular/material/input'; 21 | import { MatListModule } from '@angular/material/list'; 22 | import { MatMenuModule } from '@angular/material/menu'; 23 | import { MatPaginatorModule } from '@angular/material/paginator'; 24 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 25 | import { MatRadioModule } from '@angular/material/radio'; 26 | import { MatSelectModule } from '@angular/material/select'; 27 | import { MatSidenavModule } from '@angular/material/sidenav'; 28 | import { MatSnackBarModule } from '@angular/material/snack-bar'; 29 | import { MatSortModule } from '@angular/material/sort'; 30 | import { MatTableModule } from '@angular/material/table'; 31 | import { MatTabsModule } from '@angular/material/tabs'; 32 | import { MatToolbarModule } from '@angular/material/toolbar'; 33 | import { MatTooltipModule } from '@angular/material/tooltip'; 34 | import { MatTreeModule } from '@angular/material/tree'; 35 | 36 | 37 | const materialModules = [ 38 | CdkTreeModule, 39 | MatAutocompleteModule, 40 | MatButtonModule, 41 | MatCardModule, 42 | MatCheckboxModule, 43 | MatChipsModule, 44 | MatDividerModule, 45 | MatExpansionModule, 46 | MatIconModule, 47 | MatInputModule, 48 | MatListModule, 49 | MatMenuModule, 50 | MatProgressSpinnerModule, 51 | MatPaginatorModule, 52 | MatRippleModule, 53 | MatSelectModule, 54 | MatSidenavModule, 55 | MatSnackBarModule, 56 | MatSortModule, 57 | MatTableModule, 58 | MatTabsModule, 59 | MatToolbarModule, 60 | MatFormFieldModule, 61 | MatButtonToggleModule, 62 | MatTreeModule, 63 | OverlayModule, 64 | PortalModule, 65 | MatBadgeModule, 66 | MatGridListModule, 67 | MatRadioModule, 68 | MatDatepickerModule, 69 | MatTooltipModule 70 | ]; 71 | 72 | @NgModule({ 73 | imports: [ 74 | CommonModule, 75 | ...materialModules 76 | ], 77 | exports: [ 78 | ...materialModules 79 | ] 80 | }) 81 | 82 | export class AngularMaterialModule { } 83 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "dev:start", 6 | "detail": "Start both server & client for development", 7 | "dependsOn": [ 8 | "dev:server", 9 | "dev:client-proxy" 10 | ], 11 | "problemMatcher": [], 12 | "options": { 13 | "statusbar": { 14 | "color": "#35AC5E" 15 | } 16 | } 17 | }, 18 | { 19 | "label": "dev:client-proxy", 20 | "detail": "client in proxy\\watch mode", 21 | "type": "npm", 22 | "script": "start:proxy", 23 | "path": "client/", 24 | "problemMatcher": [], 25 | "presentation": { 26 | "reveal": "always", 27 | "panel": "shared", 28 | "group": "devwatch" 29 | }, 30 | "options": { 31 | "statusbar": { 32 | "hide": true, 33 | "color": "#35AC5E" 34 | } 35 | } 36 | }, 37 | { 38 | "label": "dev:server", 39 | "detail": "server in watch mode", 40 | "type": "npm", 41 | "script": "start:server", 42 | "problemMatcher": [], 43 | "presentation": { 44 | "reveal": "always", 45 | "panel": "shared", 46 | "group": "devwatch" 47 | }, 48 | "options": { 49 | "statusbar": { 50 | "hide": true, 51 | "color": "#35AC5E" 52 | } 53 | } 54 | }, 55 | { 56 | "type": "npm", 57 | "script": "build", 58 | "group": "build", 59 | "problemMatcher": [], 60 | "label": "Build Production", 61 | "detail": "Will build both Server & Client. Output in ./dist/", 62 | "options": { 63 | "statusbar": { 64 | "color": "#209CEE" 65 | } 66 | } 67 | }, 68 | { 69 | "type": "npm", 70 | "script": "test:cov", 71 | "path": "server/", 72 | "problemMatcher": [], 73 | "label": "TESTS: Server", 74 | "detail": "Run Server Unit-tests and Coverage", 75 | "options": { 76 | "statusbar": { 77 | "color": "#FFDD57" 78 | } 79 | } 80 | }, 81 | { 82 | "type": "npm", 83 | "script": "test:cov", 84 | "path": "client/", 85 | "group": "test", 86 | "label": "TEST: Client", 87 | "detail": "Run Client Unit-tests and Coverage", 88 | "isBackground": true, 89 | "problemMatcher": "$karma-jasmine2", 90 | "options": { 91 | "statusbar": { 92 | "color": "#FFDD57" 93 | } 94 | } 95 | }, 96 | { 97 | "type": "npm", 98 | "script": "test:e2e", 99 | "path": "server/", 100 | "problemMatcher": [], 101 | "label": "TESTS: API", 102 | "detail": "Run API Tests (server e2e)", 103 | "options": { 104 | "statusbar": { 105 | "color": "#FFDD57" 106 | } 107 | } 108 | }, 109 | { 110 | "type": "npm", 111 | "script": "e2e", 112 | "path": "client/", 113 | "problemMatcher": [], 114 | "label": "TESTS: E2E", 115 | "detail": "Run E2E tests (client e2e. includes HTML interaction)", 116 | "options": { 117 | "statusbar": { 118 | "color": "#FFDD57" 119 | } 120 | } 121 | }, 122 | 123 | ] 124 | } -------------------------------------------------------------------------------- /server/src/api/product/product.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Logger, 5 | NotFoundException, 6 | Param, 7 | UseFilters 8 | } from '@nestjs/common'; 9 | import { ApiTags } from '@nestjs/swagger'; 10 | 11 | import { 12 | GetAll, 13 | GetOne, 14 | KbDelete, 15 | KbPatch, 16 | KbPost, 17 | KbPut 18 | } from '@kb-decorators'; 19 | import { KbValidationExceptionFilter } from '@kb-filters'; 20 | import { Product } from '@kb-models'; 21 | 22 | import { ProductService } from './product.service'; 23 | 24 | @Controller('api/product') 25 | @ApiTags('product') 26 | @UseFilters(new KbValidationExceptionFilter()) 27 | export class ProductController { 28 | private readonly logger = new Logger(ProductController.name); 29 | 30 | constructor(private readonly productService: ProductService) {} 31 | 32 | @GetAll(Product) 33 | async getAllProducts() { 34 | const productsDB = await this.productService.findAllAsync(); 35 | const products = productsDB 36 | .map((productDb) => new Product(productDb.toObject())); 37 | 38 | return products; 39 | } 40 | 41 | @GetOne(Product, ':name') 42 | async getProduct(@Param('name') name: string) { 43 | const product = await this.productService.findByName(name); 44 | 45 | if (!product) { 46 | throw new NotFoundException(`Product with name ${ name } not found`); 47 | } 48 | 49 | // will show secret fields as well! 50 | this.logger.log('Full Product'); 51 | // will log only public fields! 52 | this.logger.log(product); 53 | // DANGER! WILL LOG EVERYTHING! 54 | console.log(product); 55 | 56 | // will only include exposed fields 57 | return product; 58 | } 59 | 60 | @KbPost(Product) 61 | async createProduct(@Body() item: Product) { 62 | 63 | const product = await this.productService.create(item); 64 | 65 | return product 66 | } 67 | 68 | @KbPatch(Product, ':name') 69 | async changeProduct( 70 | @Param('name') name: string, 71 | @Body() changes: Product 72 | ) { 73 | const existingProductDB = await this.productService.findOneAsync({ name }); 74 | 75 | if (!existingProductDB) { 76 | throw new NotFoundException(`Product with name ${ name } not found`); 77 | } 78 | 79 | const existingProduct = new Product(existingProductDB.toObject()); 80 | const product = await this.productService.updateAsync({ 81 | ...existingProduct, 82 | ...changes 83 | }) 84 | return product.toObject(); 85 | } 86 | 87 | @KbPut(Product, ':name') 88 | async changeProduct2( 89 | @Param('name') name: string, 90 | @Body() changes: Product 91 | ) { 92 | const existingProductDB = await this.productService.findOneAsync({ name }); 93 | const existingProduct = new Product(existingProductDB.toObject()); 94 | const product = await this.productService.updateAsync({ 95 | ...existingProduct, 96 | ...changes 97 | }) 98 | return product.toObject(); 99 | } 100 | 101 | @KbDelete(Product, ':name') 102 | async deleteProduct(@Param('name') name: string) { 103 | const product = await this.productService.deleteAsync({ name }); 104 | 105 | const parsed = new Product(product.toObject()); 106 | 107 | this.logger.log(parsed); 108 | 109 | // will only include exposed fields 110 | return parsed; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "generate-barrels": "barrelsby --delete -d ./src -l below -q --exclude spec.ts", 12 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 13 | "start": "nest start", 14 | "start:dev": "nest start --watch", 15 | "start:debug": "nest start --debug --watch", 16 | "start:prod": "node dist/main", 17 | "lint": "eslint -c ./.eslintrc.js \"{src,apps,libs,test}/**/*.ts\"", 18 | "lint:fix": "eslint -c ./.eslintrc.js \"{src,apps,libs,test}/**/*.ts\" --fix", 19 | "test": "jest", 20 | "test:watch": "jest --watch --verbose", 21 | "test:cov": "jest --coverage --verbose", 22 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 23 | "test:e2e": "jest --config ./test/jest-e2e.json --verbose --detectOpenHandles --coverage" 24 | }, 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "@nestjs/cli": "^7.5.4", 28 | "@nestjs/schematics": "^7.2.7", 29 | "@nestjs/testing": "^7.6.11", 30 | "@types/express": "^4.17.11", 31 | "@types/fs-extra": "^9.0.7", 32 | "@types/jest": "^26.0.20", 33 | "@types/lodash": "^4.14.168", 34 | "@types/node": "^14.14.25", 35 | "@types/socket.io-client": "^1.4.36", 36 | "@types/supertest": "^2.0.10", 37 | "@typescript-eslint/eslint-plugin": "^4.14.2", 38 | "@typescript-eslint/parser": "^4.14.2", 39 | "barrelsby": "^2.2.0", 40 | "eslint": "^7.19.0", 41 | "eslint-config-prettier": "^7.2.0", 42 | "eslint-plugin-prettier": "^3.3.1", 43 | "jest": "^26.6.3", 44 | "jest-mock-req-res": "^1.0.2", 45 | "prettier": "^2.2.1", 46 | "rxjs-marbles": "^6.0.1", 47 | "supertest": "^6.1.3", 48 | "ts-jest": "^26.5.0", 49 | "ts-loader": "^8.0.16", 50 | "ts-node": "^9.1.1", 51 | "tsconfig-paths": "^3.9.0", 52 | "typescript": "^4.1.3" 53 | }, 54 | "jest": { 55 | "moduleFileExtensions": [ 56 | "js", 57 | "json", 58 | "ts" 59 | ], 60 | "rootDir": "src", 61 | "testRegex": ".*\\.spec\\.ts$", 62 | "transform": { 63 | "^.+\\.(t|j)s$": "ts-jest" 64 | }, 65 | "setupFilesAfterEnv": [ 66 | "../test-tools/jest.setup.ts" 67 | ], 68 | "collectCoverageFrom": [ 69 | "**/*.(t|j)s", 70 | "!**/*.decorator.ts", 71 | "!**/index.ts", 72 | "!**/*.module.ts" 73 | ], 74 | "reporters": [ 75 | "default", 76 | [ 77 | "jest-stare", 78 | { 79 | "resultDir": "../test-results/server", 80 | "reportTitle": "jest-stare!", 81 | "additionalResultsProcessors": [ 82 | "jest-junit" 83 | ], 84 | "coverageLink": "./coverage/lcov-report/index.html" 85 | } 86 | ] 87 | ], 88 | "coverageDirectory": "../../test-results/server/coverage", 89 | "testEnvironment": "node", 90 | "moduleNameMapper": { 91 | "^@kb-server$": "", 92 | "^@kb-models$": "/models/index", 93 | "^@kb-abstracts$": "/abstracts/index", 94 | "^@kb-decorators$": "/decorators/index", 95 | "^@kb-filters$": "/filters/index", 96 | "^@kb-api$": "/api/index", 97 | "^@kb-events$": "/events/index", 98 | "^@kb-app$": "/app/index", 99 | "^@kb-tasks": "/tasks/index" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | achievibit Logo 3 | 4 |

5 | @kibibit/kb-server-client-template 6 |

7 |

8 |

9 | 10 |

11 |

12 | 13 | Server Unit Tests 14 | 15 | 16 | Client Unit Tests 17 | 18 | 19 | API Tests 20 | 21 | 22 | E2E Tests 23 | 24 | 25 | All Contributors 26 | 27 |

28 |

29 | DESCRIPTION 30 |

31 |
32 | 33 | ## Installation 34 | ## Usage 35 | 36 | ## Contributors ✨ 37 | 38 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |

Neil Kalman

💻 📖 🎨 🚧 🚇 ⚠️
47 | 48 | 49 | 50 | 51 | 52 | 53 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome! 54 | 55 | ## Stay in touch 56 | 57 | - Author - [Neil Kalman](https://github.com/thatkookooguy) 58 | - Website - [https://github.com/kibibit](https://github.com/kibibit) 59 | - StackOverflow - [thatkookooguy](https://stackoverflow.com/users/1788884/thatkookooguy) 60 | - Twitter - [@thatkookooguy](https://twitter.com/thatkookooguy) 61 | - Twitter - [@kibibit_opensrc](https://twitter.com/kibibit_opensrc) 62 | -------------------------------------------------------------------------------- /client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-return-shorthand": true, 15 | "curly": true, 16 | "deprecation": { 17 | "severity": "warning" 18 | }, 19 | "eofline": true, 20 | "import-blacklist": [ 21 | true, 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": { 26 | "options": [ 27 | "spaces" 28 | ] 29 | }, 30 | "max-classes-per-file": false, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-console": [ 47 | true, 48 | "debug", 49 | "info", 50 | "time", 51 | "timeEnd", 52 | "trace" 53 | ], 54 | "no-empty": false, 55 | "no-inferrable-types": [ 56 | true, 57 | "ignore-params" 58 | ], 59 | "no-non-null-assertion": true, 60 | "no-redundant-jsdoc": true, 61 | "no-switch-case-fall-through": true, 62 | "no-var-requires": false, 63 | "object-literal-key-quotes": [ 64 | true, 65 | "as-needed" 66 | ], 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "semicolon": { 72 | "options": [ 73 | "always" 74 | ] 75 | }, 76 | "space-before-function-paren": { 77 | "options": { 78 | "anonymous": "never", 79 | "asyncArrow": "always", 80 | "constructor": "never", 81 | "method": "never", 82 | "named": "never" 83 | } 84 | }, 85 | "typedef": [ 86 | true, 87 | "call-signature" 88 | ], 89 | "typedef-whitespace": { 90 | "options": [ 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | }, 98 | { 99 | "call-signature": "onespace", 100 | "index-signature": "onespace", 101 | "parameter": "onespace", 102 | "property-declaration": "onespace", 103 | "variable-declaration": "onespace" 104 | } 105 | ] 106 | }, 107 | "variable-name": { 108 | "options": [ 109 | "ban-keywords", 110 | "check-format", 111 | "allow-pascal-case" 112 | ] 113 | }, 114 | "whitespace": { 115 | "options": [ 116 | "check-branch", 117 | "check-decl", 118 | "check-operator", 119 | "check-separator", 120 | "check-type", 121 | "check-typecast" 122 | ] 123 | }, 124 | "component-class-suffix": true, 125 | "contextual-lifecycle": true, 126 | "directive-class-suffix": true, 127 | "no-conflicting-lifecycle": true, 128 | "no-host-metadata-property": true, 129 | "no-input-rename": true, 130 | "no-inputs-metadata-property": true, 131 | "no-output-native": true, 132 | "no-output-on-prefix": true, 133 | "no-output-rename": true, 134 | "no-outputs-metadata-property": true, 135 | "template-banana-in-box": true, 136 | "template-no-negated-async": true, 137 | "use-lifecycle-interface": true, 138 | "use-pipe-transform-interface": true, 139 | "directive-selector": [ 140 | true, 141 | "attribute", 142 | "app", 143 | "camelCase" 144 | ], 145 | "component-selector": [ 146 | true, 147 | "element", 148 | "app", 149 | "kebab-case" 150 | ] 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.158.0/containers/typescript-node 3 | { 4 | "name": "kb-server-client-template", 5 | "dockerComposeFile": "docker-compose.yml", 6 | "service": "app", 7 | "workspaceFolder": "/workspace", 8 | // Set *default* container specific settings.json values on container create. 9 | "settings": { 10 | "terminal.integrated.shell.linux": "/bin/zsh", 11 | "peacock.color": "#3334f4", 12 | "editor.tabSize": 2, 13 | "editor.insertSpaces": true, 14 | "editor.detectIndentation": false, 15 | "editor.rulers": [80], 16 | "editor.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace", 17 | "editor.matchBrackets": "always", 18 | // Terminal 19 | "terminal.integrated.fontFamily": "Hack, Menlo, Monaco, 'Courier New', monospace", 20 | "terminal.integrated.fontSize": 14, 21 | // Workbench 22 | "workbench.colorTheme": "Andromeda", 23 | "workbench.editor.showIcons": true, 24 | "workbench.iconTheme": "vs-seti", 25 | // Bracket Pair Colorizer 26 | "bracketPairColorizer.colorMode": "Consecutive", 27 | "bracketPairColorizer.forceUniqueOpeningColor": true, 28 | "bracketPairColorizer.showBracketsInGutter": true, 29 | "window.title": "${activeEditorShort}${separator}${rootName} [kibibit]", 30 | "typescriptHero.imports.stringQuoteStyle": "'", 31 | "typescriptHero.imports.grouping": [ 32 | "Plains", 33 | "Modules", 34 | "/^@kb-/", 35 | "Workspace" 36 | 37 | ], 38 | "typescriptHero.imports.organizeOnSave": true, 39 | "typescriptHero.imports.multiLineTrailingComma": false, 40 | "typescriptHero.imports.multiLineWrapThreshold": 80, 41 | "typescriptHero.imports.insertSpaceBeforeAndAfterImportBraces": true, 42 | "typescriptHero.imports.insertSemicolons": true, 43 | "editor.codeActionsOnSave": { 44 | "source.fixAll.eslint": true, 45 | }, 46 | "eslint.format.enable": true, 47 | "vsicons.presets.angular": true, 48 | "eslint.workingDirectories": [ 49 | "client/", 50 | "server/" 51 | ] 52 | }, 53 | 54 | // Add the IDs of extensions you want installed when the container is created. 55 | "extensions": [ 56 | "dbaeumer.vscode-eslint", 57 | "rbbit.typescript-hero", 58 | "coenraads.bracket-pair-colorizer", 59 | "orta.vscode-jest", 60 | "wix.vscode-import-cost", 61 | "actboy168.tasks", 62 | "johnpapa.vscode-peacock", 63 | "angular.ng-template", 64 | "abhijoybasak.nestjs-files", 65 | "eamodio.gitlens", 66 | "codeandstuff.package-json-upgrade", 67 | "mongodb.mongodb-vscode", 68 | "ms-azuretools.vscode-docker", 69 | "jock.svg" 70 | ], 71 | 72 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 73 | "forwardPorts": [ 74 | 10101, // dev client 75 | 10102, // dev server 76 | 27017 // mongodb instance 77 | ], 78 | "portsAttributes": { 79 | "10101": { 80 | "label": "Client", 81 | "onAutoForward": "notify" 82 | }, 83 | "10102": { 84 | "label": "Server", 85 | "onAutoForward": "notify" 86 | }, 87 | "27017": { 88 | "label": "MongoDB", 89 | "onAutoForward": "notify" 90 | }, 91 | "9876": { 92 | "label": "TESTS: Client Unit Tests", 93 | "onAutoForward": "notify" 94 | }, 95 | "4200": { 96 | "label": "TESTS: Client E2E", 97 | "onAutoForward": "notify" 98 | }, 99 | }, 100 | 101 | // Use 'postCreateCommand' to run commands after the container is created. 102 | // "postCreateCommand": "yarn install", 103 | // "mounts": [ 104 | // "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume" 105 | // ], 106 | 107 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 108 | "remoteUser": "node" 109 | } -------------------------------------------------------------------------------- /client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "client": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "../dist/client", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "src/styles.scss" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "extractCss": true, 47 | "namedChunks": false, 48 | "extractLicenses": true, 49 | "vendorChunk": false, 50 | "buildOptimizer": true, 51 | "budgets": [ 52 | { 53 | "type": "initial", 54 | "maximumWarning": "2mb", 55 | "maximumError": "5mb" 56 | }, 57 | { 58 | "type": "anyComponentStyle", 59 | "maximumWarning": "6kb", 60 | "maximumError": "10kb" 61 | } 62 | ] 63 | } 64 | } 65 | }, 66 | "serve": { 67 | "builder": "@angular-devkit/build-angular:dev-server", 68 | "options": { 69 | "browserTarget": "client:build" 70 | }, 71 | "configurations": { 72 | "production": { 73 | "browserTarget": "client:build:production" 74 | } 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "client:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "karmaConfig": "karma.conf.js", 90 | "assets": [ 91 | "src/favicon.ico", 92 | "src/assets" 93 | ], 94 | "styles": [ 95 | "src/styles.scss" 96 | ], 97 | "scripts": [] 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-eslint/builder:lint", 102 | "options": { 103 | "lintFilePatterns": [ 104 | "src/**/*.ts", 105 | "src/**/*.html" 106 | ] 107 | } 108 | }, 109 | "e2e": { 110 | "builder": "@angular-devkit/build-angular:protractor", 111 | "options": { 112 | "protractorConfig": "e2e/protractor.conf.js", 113 | "devServerTarget": "client:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "client:serve:production" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "client" 125 | } 126 | -------------------------------------------------------------------------------- /server/src/abstracts/base.service.abstract.ts: -------------------------------------------------------------------------------- 1 | import { InternalServerErrorException } from '@nestjs/common'; 2 | import { DocumentType, ReturnModelType } from '@typegoose/typegoose'; 3 | import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'; 4 | import { MongoError } from 'mongodb'; 5 | import { Document, DocumentQuery, Query, Types } from 'mongoose'; 6 | 7 | import { BaseModel } from './base.model.abstract'; 8 | 9 | 10 | type QueryList = DocumentQuery< 11 | Array>, 12 | DocumentType 13 | >; 14 | type QueryItem = DocumentQuery< 15 | DocumentType, 16 | DocumentType 17 | >; 18 | 19 | export abstract class BaseService { 20 | protected model: ReturnModelType>; 21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 22 | protected class: any; 23 | 24 | protected constructor( 25 | model: ReturnModelType>, 26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 27 | serviceClass: any 28 | ) { 29 | this.model = model; 30 | this.class = serviceClass; 31 | } 32 | 33 | protected static throwMongoError(err: MongoError): void { 34 | throw new InternalServerErrorException(err, err.errmsg); 35 | } 36 | 37 | protected static toObjectId(id: string): Types.ObjectId { 38 | try { 39 | return Types.ObjectId(id); 40 | } catch (e) { 41 | this.throwMongoError(e); 42 | } 43 | } 44 | 45 | createModel(doc?: Partial): T { 46 | return new this.model(doc); 47 | } 48 | 49 | findAll(filter = {}): QueryList { 50 | return this.model.find(filter); 51 | } 52 | 53 | async findAllAsync(filter = {}): Promise>> { 54 | try { 55 | return await this.findAll(filter).exec(); 56 | } catch (e) { 57 | BaseService.throwMongoError(e); 58 | } 59 | } 60 | 61 | findOne(filter = {}): QueryItem { 62 | return this.model.findOne(filter); 63 | } 64 | 65 | async findOneAsync(filter = {}): Promise> { 66 | try { 67 | return await this.findOne(filter).exec(); 68 | } catch (e) { 69 | BaseService.throwMongoError(e); 70 | } 71 | } 72 | 73 | findById(id: string): QueryItem { 74 | return this.model.findById(BaseService.toObjectId(id)); 75 | } 76 | 77 | async findByIdAsync(id: string): Promise> { 78 | try { 79 | return await this.findById(id).exec(); 80 | } catch (e) { 81 | BaseService.throwMongoError(e); 82 | } 83 | } 84 | 85 | async create(item: T): Promise { 86 | try { 87 | const persistedItem = await this.model.create(item); 88 | return new this.class(persistedItem.toObject()); 89 | } catch (e) { 90 | BaseService.throwMongoError(e); 91 | } 92 | } 93 | 94 | delete(filter = {}): QueryItem { 95 | return this.model.findOneAndDelete(filter); 96 | } 97 | 98 | async deleteAsync(filter = {}): Promise> { 99 | try { 100 | return await this.delete(filter).exec(); 101 | } catch (e) { 102 | BaseService.throwMongoError(e); 103 | } 104 | } 105 | 106 | deleteById(id: string): QueryItem { 107 | return this.model.findByIdAndDelete(BaseService.toObjectId(id)); 108 | } 109 | 110 | async deleteByIdAsync(id: string): Promise> { 111 | try { 112 | return await this.deleteById(id).exec(); 113 | } catch (e) { 114 | BaseService.throwMongoError(e); 115 | } 116 | } 117 | 118 | update(item: Partial): QueryItem { 119 | return this.model.findByIdAndUpdate(BaseService.toObjectId(item.id), item, { 120 | new: true 121 | }); 122 | } 123 | 124 | async updateAsync(item: Partial): Promise> { 125 | try { 126 | return await this.update(item).exec(); 127 | } catch (e) { 128 | BaseService.throwMongoError(e); 129 | } 130 | } 131 | 132 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 133 | count(filter = {}): Query> { 134 | return this.model.count(filter); 135 | } 136 | 137 | async countAsync(filter = {}): Promise { 138 | try { 139 | return await this.count(filter); 140 | } catch (e) { 141 | BaseService.throwMongoError(e); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kibibit/kb-server-client-template", 3 | "version": "0.0.0-development", 4 | "description": "", 5 | "main": "dist/server/main.js", 6 | "scripts": { 7 | "prune-branches": "node ./tools/scripts/prune-untrackted-branches.js", 8 | "contributors:all": "cross-env HUSKY_SKIP_HOOKS=1 node ./tools/get-all-contributors.js", 9 | "contributors:add": "cross-env HUSKY_SKIP_HOOKS=1 all-contributors add", 10 | "contributors:generate": "cross-env HUSKY_SKIP_HOOKS=1 all-contributors generate", 11 | "install": "npm run install:server && npm run install:client", 12 | "install:client": "cd client && npm install", 13 | "install:server": "cd server && npm install", 14 | "build": "npm run build:server && npm run build:client", 15 | "build:client": "cd ./client && npm run build", 16 | "build:server": "cd ./server && npm run build", 17 | "start:client": "cd ./client && npm run start", 18 | "start:server": "cd ./server && npm run start:dev", 19 | "init": "node ./tools/replace-template-string.js", 20 | "generate-barrels": "barrelsby --delete -d ./src -l below -q", 21 | "semantic-release:setup": "semantic-release-cli setup", 22 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"", 23 | "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 24 | "test": "echo \"Error: no test specified\" && exit 1", 25 | "test:server-unit": "cd server && npm run test:cov", 26 | "test:client-unit": "cd client && npm run test:cov", 27 | "test:api": "cd server && npm run test:e2e", 28 | "test:e2e": "cd client && npm run e2e", 29 | "semantic-release": "semantic-release" 30 | }, 31 | "author": "thatkookooguy ", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "@commitlint/cli": "^11.0.0", 35 | "@commitlint/config-angular": "^11.0.0", 36 | "@commitlint/config-conventional": "^11.0.0", 37 | "@types/cron": "^1.7.2", 38 | "@types/find-root": "^1.1.2", 39 | "@types/mongoose": "^5.10.3", 40 | "@types/socket.io": "^2.1.13", 41 | "@typescript-eslint/eslint-plugin": "^4.14.2", 42 | "@typescript-eslint/parser": "^4.14.2", 43 | "all-contributors-cli": "^6.20.0", 44 | "cli-table": "^0.3.6", 45 | "commitizen": "^4.2.3", 46 | "cross-env": "^7.0.3", 47 | "cz-conventional-changelog": "^3.3.0", 48 | "cz-conventional-changelog-emoji": "^0.1.0", 49 | "eslint": "^7.19.0", 50 | "foswig": "^3.0.1", 51 | "github-username": "^6.0.0", 52 | "gitlog": "^4.0.4", 53 | "husky": "^4.3.8", 54 | "inquirer": "^8.0.0", 55 | "jest-stare": "^2.2.1", 56 | "karma-htmlfile-reporter": "^0.3.8", 57 | "ncp": "^2.0.0", 58 | "package-name-regex": "^1.0.11", 59 | "replace-in-file": "^6.2.0", 60 | "semantic-release": "^17.3.8", 61 | "semantic-release-cli": "^5.4.3", 62 | "sentencer": "^0.2.1", 63 | "shelljs": "^0.8.4", 64 | "simple-git": "^2.37.0", 65 | "ts-node": "^9.1.1", 66 | "typescript": "^4.1.3" 67 | }, 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/Kibibit/kb-server-client-template.git" 71 | }, 72 | "bugs": { 73 | "url": "https://github.com/Kibibit/kb-server-client-template/issues" 74 | }, 75 | "dependencies": { 76 | "@kibibit/consologo": "^1.2.0", 77 | "@kibibit/kb-error": "^1.0.3", 78 | "@nestjs/common": "^7.6.11", 79 | "@nestjs/core": "^7.6.11", 80 | "@nestjs/mongoose": "^7.0.4", 81 | "@nestjs/platform-express": "^7.6.11", 82 | "@nestjs/platform-socket.io": "^7.6.15", 83 | "@nestjs/platform-ws": "^7.6.15", 84 | "@nestjs/schedule": "^0.4.3", 85 | "@nestjs/swagger": "^4.7.12", 86 | "@nestjs/websockets": "^7.6.15", 87 | "@typegoose/typegoose": "^7.6.0", 88 | "axios": "^0.21.1", 89 | "class-transformer": "^0.3.2", 90 | "class-validator": "^0.13.1", 91 | "find-root": "^1.1.0", 92 | "fs-extra": "^9.1.0", 93 | "lodash": "^4.17.20", 94 | "mongoose": "^5.10.19", 95 | "reflect-metadata": "^0.1.13", 96 | "rimraf": "^3.0.2", 97 | "rxjs": "^6.6.3", 98 | "swagger-ui-express": "^4.1.6" 99 | }, 100 | "config": { 101 | "commitizen": { 102 | "path": "./node_modules/cz-conventional-changelog-emoji" 103 | } 104 | }, 105 | "husky": { 106 | "hooks": { 107 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true", 108 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 109 | } 110 | }, 111 | "publishConfig": { 112 | "access": "public" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tools/scripts/prune-untrackted-branches.js: -------------------------------------------------------------------------------- 1 | const simpleGit = require('simple-git/promise'); 2 | const { chain, trim, map } = require('lodash'); 3 | const inquirer = require('inquirer'); 4 | const Table = require('cli-table'); 5 | 6 | const git = simpleGit(); 7 | const remoteInfoRegex = /^\[(.*?)\]\s/g; 8 | const MAIN_BRANCHES = [ 9 | 'master', 10 | 'main' 11 | ]; 12 | 13 | const chars = { 14 | 'top': '═', 'top-mid': '╤', 'top-left': '╔', 'top-right': '╗' 15 | , 'bottom': '═', 'bottom-mid': '╧', 'bottom-left': '╚', 'bottom-right': '╝' 16 | , 'left': '║', 'left-mid': '╟', 'mid': '─', 'mid-mid': '┼' 17 | , 'right': '║', 'right-mid': '╢', 'middle': '│' 18 | }; 19 | 20 | (async () => { 21 | try { 22 | await git.fetch(['-p']); // prune? is it necassery? 23 | const branchSummaryResult = await git.branch(['-vv']); 24 | const localBranches = branchSummaryResult.branches; 25 | const localBranchesWithGoneRemotes = chain(localBranches) 26 | .filter((item) => !MAIN_BRANCHES.includes(item.name)) 27 | .forEach((item) => { 28 | // console.log('an item appeared!', item); 29 | const remoteInfo = item.label.match(remoteInfoRegex); 30 | 31 | if (remoteInfo) { 32 | const parsedRemoteInfo = trim(remoteInfo[0], '[] '); 33 | const isMerged = parsedRemoteInfo.endsWith(': gone'); 34 | const remoteBranchName = parsedRemoteInfo.replace(': gone', ''); 35 | 36 | item.isMerged = isMerged; 37 | item.remoteName = remoteBranchName; 38 | } 39 | }) 40 | .filter((item) => item.isMerged) 41 | .value(); 42 | const branchNames = chain(localBranchesWithGoneRemotes).map('name').value(); 43 | 44 | if (!branchNames.length) { 45 | console.log('PROJECT IS CLEAN! WELL DONE!'); 46 | process.exit(0); 47 | } 48 | 49 | // interaction! 50 | const table = new Table({ 51 | head: ['Branch Name', 'Origin Branch Name', 'Origin Branch Status'], 52 | chars 53 | }); 54 | 55 | localBranchesWithGoneRemotes.forEach((item) => table.push([item.name, item.remoteName, 'GONE'])); 56 | 57 | console.log(`${table.toString()}\n`); 58 | 59 | const ACTION_ANSWERS = { 60 | PRUNE_ALL: 'prune all gone branches', 61 | SELECT_BRANCHES: 'Selected Individual branches to delete' 62 | } 63 | 64 | const answers = await inquirer 65 | .prompt([ 66 | { 67 | type: 'list', 68 | name: 'whatToDo', 69 | message: 'These branches have been deleted from the origin. What do you want to do with them?', 70 | choices: chain(ACTION_ANSWERS).values().value() 71 | } 72 | ]); 73 | 74 | if (answers.whatToDo === ACTION_ANSWERS.PRUNE_ALL) { 75 | await moveToAnotherBranchIfNeeded(branchSummaryResult, branchNames); 76 | 77 | const result = await git.deleteLocalBranches(branchNames, true); 78 | console.log('DONE'); 79 | chain(result.all) 80 | .map((item) => `${ item.branch }: ${ item.success ? 'DELETED' : 'FAILED' }`) 81 | .forEach((item) => console.log(item)); 82 | return; 83 | } 84 | 85 | if (answers.whatToDo === ACTION_ANSWERS.SELECT_BRANCHES) { 86 | const answers = await inquirer 87 | .prompt([ 88 | { 89 | type: 'checkbox', 90 | message: 'These branches have been deleted from the origin. Do you want to prune them?', 91 | name: 'pruneBranches', 92 | choices: branchNames 93 | } 94 | ]); 95 | 96 | await moveToAnotherBranchIfNeeded(branchSummaryResult, branchNames); 97 | 98 | const result = await Promise.all(answers.pruneBranches.map((branchName) => git.deleteLocalBranch(branchName, true))); 99 | console.log('DONE'); 100 | console.log(result); 101 | chain(result.all) 102 | .map((item) => `${ item.branch }: ${ item.success ? 'DELETED' : 'FAILED' }`) 103 | .forEach((item) => console.log(item)); 104 | return; 105 | } 106 | } catch (err) { 107 | console.error(err); 108 | process.exit(1); 109 | } 110 | })(); 111 | 112 | async function moveToAnotherBranchIfNeeded(branchSummaryResult, branchesToDelete) { 113 | const suspectedMainBranch = branchSummaryResult.all.find((branchName) => MAIN_BRANCHES.includes(branchName)); 114 | const currentCheckedoutBranch = branchSummaryResult.current; 115 | 116 | // console.log('main branch:', suspectedMainBranch); 117 | 118 | if (branchesToDelete.includes(currentCheckedoutBranch)) { 119 | console.warn(`trying to delete checkedout branch ${ currentCheckedoutBranch }. moving to ${ suspectedMainBranch }`); 120 | await git.checkout(suspectedMainBranch); 121 | } 122 | } -------------------------------------------------------------------------------- /tools/data/names-dictionary.js: -------------------------------------------------------------------------------- 1 | exports.dictionary = [ 2 | "coke", 3 | "bootstrap", 4 | "ikSelect", 5 | "selectik", 6 | "tinycon", 7 | "foswiki", 8 | "socketstream", 9 | "elFinder", 10 | "basket.js", 11 | "jaws", 12 | "tinyhook", 13 | "Sozi", 14 | "Zeppelin", 15 | "Propilex", 16 | "fontomas", 17 | "lernanta", 18 | "Sentimental", 19 | "sink.js", 20 | "towerjs.org", 21 | "cloud9", 22 | "openlayers", 23 | "phonegap", 24 | "prototype", 25 | "APIConnect", 26 | "collusion", 27 | "less.js", 28 | "nodeclub", 29 | "gaia", 30 | "CodeMirror2", 31 | "arbor", 32 | "scriptish", 33 | "stylebot", 34 | "experiments", 35 | "backbone", 36 | "ember.js", 37 | "zotero", 38 | "jStorage", 39 | "richard", 40 | "duino", 41 | "underscore", 42 | "mediaQueryBookmarklet", 43 | "morpheus", 44 | "cryptocat", 45 | "backbone.marionette", 46 | "Modernizr", 47 | "supersized", 48 | "animatable", 49 | "connect", 50 | "jshint", 51 | "turn.js", 52 | "impress.js", 53 | "emscripten", 54 | "WAT", 55 | "wysihtml5", 56 | "node", 57 | "FitVids.js", 58 | "deployinator", 59 | "kartograph.js", 60 | "broadway", 61 | "tako", 62 | "passport", 63 | "docs", 64 | "nanoblocks", 65 | "dwb", 66 | "jake", 67 | "galleria", 68 | "ShareJS", 69 | "d3", 70 | "MasteringJQuery", 71 | "express", 72 | "history.js", 73 | "R2", 74 | "EaselJS", 75 | "Iconic", 76 | "KineticJS", 77 | "semantic.gs", 78 | "buster", 79 | "chosen", 80 | "pdf.js", 81 | "wapama", 82 | "transparency", 83 | "droparea", 84 | "jade", 85 | "shelljs", 86 | "autodafe", 87 | "cdnjs", 88 | "antiscroll", 89 | "esprima", 90 | "Unslider", 91 | "mocha", 92 | "three.js", 93 | "spine", 94 | "fluidinfo.js", 95 | "nutzdemo", 96 | "funcunit", 97 | "scriptaculous", 98 | "nohm", 99 | "jitsu", 100 | "timePicker", 101 | "markdowndeep", 102 | "maqetta", 103 | "qunit", 104 | "formhub", 105 | "studio3", 106 | "picturefill", 107 | "zepto", 108 | "suite", 109 | "scrollpath", 110 | "mulberry", 111 | "FlightDeck", 112 | "notifer.js", 113 | "jbrowse", 114 | "jappix", 115 | "yui3", 116 | "Geolib", 117 | "bootstrap4gwt", 118 | "patio", 119 | "CATMAID", 120 | "Respond", 121 | "jScrollPane", 122 | "faye", 123 | "lesscss.org", 124 | "ThreeNodes.js", 125 | "dgrid", 126 | "gfx", 127 | "Reel", 128 | "jsbin", 129 | "progit", 130 | "jurlp", 131 | "liquid.js", 132 | "browserid", 133 | "py2js", 134 | "locomotive", 135 | "translators", 136 | "moment", 137 | "png.js", 138 | "ace", 139 | "hypernotes", 140 | "Scribite", 141 | "noty", 142 | "use.js", 143 | "Idle.js", 144 | "Haraka", 145 | "ql.io", 146 | "Leaflet", 147 | "UglifyJS", 148 | "maptail", 149 | "wro4j", 150 | "WikipediaMobile", 151 | "dialect", 152 | "angular.js", 153 | "translate.js", 154 | "gury", 155 | "i18next", 156 | "mapknitter", 157 | "jsperanto", 158 | "mustache.js", 159 | "numeric", 160 | "Phraseanet", 161 | "MathJax", 162 | "AnythingSlider", 163 | "html5demos", 164 | "wysihat", 165 | "showoff", 166 | "NES", 167 | "J3D", 168 | "JSLint", 169 | "scrumblr", 170 | "chaplin", 171 | "jsduck", 172 | "uikit", 173 | "chartjunk", 174 | "tquery", 175 | "recanvas", 176 | "Amanda", 177 | "MotionCAPTCHA", 178 | "sproutcore", 179 | "recollection", 180 | "Kalendae", 181 | "visualsearch", 182 | "jPlayer", 183 | "TiShadow", 184 | "binaergewitter.github.com", 185 | "lionbars", 186 | "hexagame", 187 | "Namespace.js", 188 | "howtonode.org", 189 | "jasmine", 190 | "Sinon.JS", 191 | "Mandible", 192 | "Mandible2", 193 | "JSJaC", 194 | "AntiMap", 195 | "whiskey", 196 | "EurekaJ", 197 | "github", 198 | "uptime", 199 | "tesseract", 200 | "At.js", 201 | "reveal.js", 202 | "grunt", 203 | "ltx", 204 | "jstat", 205 | "Skeleton", 206 | "facetview", 207 | "restler", 208 | "Espresso", 209 | "faux", 210 | "envisionjs", 211 | "hieroglyph", 212 | "nervous", 213 | "ed.gridpak", 214 | "blossom", 215 | "Crafty", 216 | "examples", 217 | "WeiboSDK", 218 | "Tattoo", 219 | "easyXDM", 220 | "tweet", 221 | "h5Validate", 222 | "curl", 223 | "topcube", 224 | "yiiext.github.com", 225 | "onyx", 226 | "PDFObject", 227 | "SlickGrid", 228 | "enyo", 229 | "microjs.com", 230 | "stanfordacm.github.com", 231 | "readabilitySAX", 232 | "sjcl", 233 | "data", 234 | "node.couch.js", 235 | "umd", 236 | "CodePen", 237 | "engine.io", 238 | "melonJS", 239 | "help.github.com", 240 | "jQTouch", 241 | "Hotot", 242 | "jath", 243 | "mathquill", 244 | "fullcalendar", 245 | "nodester", 246 | "Sprite3D.js", 247 | "CloudEdit", 248 | "money.js", 249 | "gmap3", 250 | "node.io", 251 | "MiniCart", 252 | "tilemill", 253 | "RPG", 254 | "bbclonemail", 255 | "amber", 256 | "jRumble", 257 | "paper.js", 258 | "fulcrum", 259 | "todomvc", 260 | "mongojs", 261 | "NewsBlur", 262 | "vexchords", 263 | "Aardwolf", 264 | "asyncblock", 265 | "buzz", 266 | "Locker", 267 | "AncientBeast", 268 | "collective.github.com", 269 | "hammer.js", 270 | "puffer", 271 | "sigma.js", 272 | "subway", 273 | "carrier", 274 | "csslint", 275 | "EpicEditor", 276 | "Font.js", 277 | "SyntaxHighlighter", 278 | "TinyNav.js", 279 | "backstrapp", 280 | "datacouch", 281 | "transform", 282 | "cycle", 283 | "cufon", 284 | "Breakout", 285 | "highlight.js", 286 | "CanvasLoader", 287 | "mailcheck", 288 | "ejs", 289 | "autosize", 290 | "idiomatic.js", 291 | "bouncy", 292 | "rickshaw", 293 | "ringojs", 294 | "haibu", 295 | "quarkjs", 296 | "TheodoRogerCmsBundle", 297 | "jQ.Mobi", 298 | "nodemon", 299 | "BrowserQuest", 300 | "knockout", 301 | "Laro", 302 | "smokescreen", 303 | "snack", 304 | "requirejs", 305 | "QueryLoader2", 306 | "filtrify", 307 | "jslider", 308 | "knockout.mapping", 309 | "JAK", 310 | "hook.io", 311 | "cloudifysource.github.com", 312 | "jcarousel", 313 | "Tachyon", 314 | "expresso", 315 | "lectric", 316 | "core", 317 | "Han", 318 | "Lungo.js", 319 | "bonzo", 320 | "npm", 321 | "rainbow", 322 | "jQuery.superLabels", 323 | "fancyBox", 324 | "elRTE", 325 | "remo", 326 | "Monocle", 327 | "socket.io", 328 | "wireit", 329 | "bean", 330 | "libredocs", 331 | "backbone.modelbinding", 332 | "geonode", 333 | "jsdoc", 334 | "Bazar", 335 | "apf", 336 | "deployd", 337 | "natural", 338 | "gm", 339 | "leafclusterer", 340 | "ni", 341 | "jsDAV", 342 | "cockpit.js", 343 | "Proj4Leaflet", 344 | "winston", 345 | "xsschef", 346 | "kanso", 347 | "TypedJS", 348 | "klass", 349 | "backbonetutorials", 350 | "pl", 351 | "hnmobile", 352 | "Impressionist", 353 | "DropKick", 354 | "nios", 355 | "create", 356 | "cartodb", 357 | "website", 358 | "fireworks", 359 | "Ender", 360 | "quicksearch", 361 | "zoomooz", 362 | "poserver", 363 | "swipe", 364 | "mViewer", 365 | "mongoose", 366 | "deck.js", 367 | "siwapp", 368 | "ImageMapster", 369 | "firebug", 370 | "procstreams", 371 | "jsPlumb", 372 | "cube", 373 | "Timeline", 374 | "Slatwall", 375 | "pouchdb", 376 | "statsd", 377 | "x3dom", 378 | "mailparser", 379 | "MaSha", 380 | "Cocktail", 381 | "curtain.js", 382 | "Flotr2", 383 | "topup", 384 | "tty.js", 385 | "volo", 386 | "planet", 387 | "Wappalyzer", 388 | "imgg", 389 | "deferred", 390 | "jsrender", 391 | "fbgraph", 392 | "stsh", 393 | "TrelloScrum", 394 | "node.gzip", 395 | "inliner", 396 | "Sequence", 397 | "chromeless", 398 | "taffydb", 399 | "buildy", 400 | "isotope", 401 | "soda", 402 | "store.js", 403 | "Slides", 404 | "KickJS", 405 | "kissy", 406 | "modules", 407 | "superagent", 408 | "stacey", 409 | "xitrum", 410 | "highcharts.com", 411 | "jsconsole", 412 | "seajs", 413 | "cromagjs", 414 | "getUserMedia.js", 415 | "davis.js", 416 | "phpjs", 417 | "nightlytt", 418 | "jscex", 419 | "Thruk", 420 | "jqGrid", 421 | "robnyman.github.com", 422 | "html5test", 423 | "WebSonic", 424 | "jsdeferred", 425 | "erjjones.github.com", 426 | "mxui", 427 | "ViMbAdmin", 428 | "git.js", 429 | "FitText.js", 430 | "wnframework", 431 | "vimfiles", 432 | "coveraje", 433 | "jmpress.js", 434 | "learningthreejs.com", 435 | "tiddlywiki", 436 | "Camera", 437 | "inputex", 438 | "audiojs", 439 | "soulmate.js", 440 | "ccdump", 441 | "webshim", 442 | "GloveBox", 443 | "nodemock", 444 | "tipsy", 445 | "gbone.js", 446 | "threefab", 447 | "H5F", 448 | "versionator", 449 | "DeliveryTracker", 450 | "pngy", 451 | "wtfjs", 452 | "Backbone.SharePoint", 453 | "gzippo", 454 | "sandbox", 455 | "Copper", 456 | "swfobject", 457 | "tinymce", 458 | "qooxdoo", 459 | "rlightbox2", 460 | "ezjscore", 461 | "building", 462 | "playpainter", 463 | "liteAccordion", 464 | "alleup", 465 | "globalize", 466 | "BookClub", 467 | "qTip2", 468 | "jPages", 469 | "smoothie", 470 | "immortal", 471 | "raphael", 472 | "orbital", 473 | "infiniverse", 474 | "Ext.ux.Router", 475 | "mercury", 476 | "karl", 477 | "perl6.org", 478 | "inflickity", 479 | "Sugar", 480 | "sizzle", 481 | "hogan.js", 482 | "substance", 483 | "jQRangeSlider", 484 | "imagesloaded", 485 | "html5dashboard", 486 | "o2.js", 487 | "twitter.github.com", 488 | "abaaso", 489 | "Tangle", 490 | "move.js", 491 | "water", 492 | "selectorgadget", 493 | "artwork", 494 | "streamlinejs", 495 | "validity", 496 | "nodeunit", 497 | "PhotoSwipe", 498 | "winkstart", 499 | "shareabouts", 500 | "actionHero", 501 | "mockJSON", 502 | "PxLoader", 503 | "nimbupani.github.com", 504 | "chico", 505 | "js2coffee", 506 | "ttTools", 507 | "script.js", 508 | "DeftJS", 509 | "aws2js", 510 | "pulley", 511 | "Products.TinyMCE", 512 | "ResponsiveSlides.js", 513 | "marked", 514 | "diveintohtml5", 515 | "mozmill", 516 | "zeromq.node", 517 | "noodletalk", 518 | "agility", 519 | "URI.js", 520 | "contextify", 521 | "nanoScrollerJS", 522 | "formalize", 523 | "Hasher", 524 | "compact", 525 | "json3", 526 | "yuidocjs", 527 | "watch", 528 | "form", 529 | "webidl.js", 530 | "datajam", 531 | "frontend", 532 | "eol", 533 | "openbadges", 534 | "Log.io", 535 | "async", 536 | "jslitmus", 537 | "areweplayingyet", 538 | "billy", 539 | "repl.it", 540 | "WebGL101", 541 | "cdf", 542 | "toc", 543 | "scriptular", 544 | "dochub", 545 | "bananabomber", 546 | "libcanvas", 547 | "ws", 548 | "scrolldeck.js", 549 | "html5boilerplate.com", 550 | "accessifyhtml5.js", 551 | "Pagify", 552 | "CubicVR.js", 553 | "should.js", 554 | "1.x", 555 | "underscore.string", 556 | "scrollorama", 557 | "geddy", 558 | "jsviews", 559 | "MQTT.js", 560 | "sagecell", 561 | "jribbble", 562 | "datavore", 563 | "openwebapps", 564 | "Thinktecture.IdentityModel.Http", 565 | "linguist", 566 | "fleet", 567 | "W", 568 | "scrolly", 569 | "radio", 570 | "backbone.iobind", 571 | "pure", 572 | "HighRoller", 573 | "320andup", 574 | "dom.js", 575 | "fileupload", 576 | "plates", 577 | "scala.github.com", 578 | "jqfundamentals", 579 | "classList.js", 580 | "WatchMen", 581 | "Monorail.js", 582 | "clicktoplugin", 583 | "bigdesk", 584 | "ichabod", 585 | "ueberDB", 586 | "coweb", 587 | "PiplMesh", 588 | "Seriously.js", 589 | "XRegExp", 590 | "benchmark.js", 591 | "fabric.js", 592 | "ghostdriver", 593 | "procouch", 594 | "when", 595 | "orbited2", 596 | "dojo", 597 | "everyauth", 598 | "csso", 599 | "ZeParser", 600 | "Swipe", 601 | "sequelize", 602 | "tablesorter", 603 | "foresight.js", 604 | "butter", 605 | "cytoscapeweb", 606 | "messenger.js", 607 | "nools", 608 | "taberareloo", 609 | "bamboo", 610 | "youtify", 611 | "nodrr", 612 | "textualizer", 613 | "html2canvas", 614 | "ui", 615 | "molt", 616 | "NodObjC", 617 | "dance", 618 | "Anemometer", 619 | "sumorio", 620 | "r.js", 621 | "Services.bundle", 622 | "WebSqlSync", 623 | "Backbone.localStorage", 624 | "roy", 625 | "Perspectives", 626 | "instachrome", 627 | "jslm32", 628 | "Noisy", 629 | "heatmap.js", 630 | "top3c", 631 | "gitterb", 632 | "evroneCrop", 633 | "jwcrypto", 634 | "BitmapData.js", 635 | "jsper", 636 | "issues", 637 | "browserbuild", 638 | "backbone.paginator", 639 | "headjs", 640 | "gmaps", 641 | "codo", 642 | "nude.js", 643 | "papermill", 644 | "wire", 645 | "jump", 646 | "CocoaChineseDoc", 647 | "dabblet", 648 | "tabletop", 649 | "masonry", 650 | "lowkick", 651 | "roundabout", 652 | "autopagerize", 653 | "memchaser", 654 | "jGallery", 655 | "tracer", 656 | "Gritter", 657 | "backbone.layoutmanager", 658 | "jsOAuth", 659 | "spazcore", 660 | "glfx.js", 661 | "saepythondevguide", 662 | "SignalR", 663 | "FlexSlider", 664 | "jugglingdb", 665 | "tweereal", 666 | "humongous", 667 | "Shortly", 668 | "LatticeGrid", 669 | "cramp", 670 | "QuickWeb", 671 | "Emberwind", 672 | "calipso", 673 | "SimpLESS", 674 | "augment.js", 675 | "xui", 676 | "Nodemailer", 677 | "uniform", 678 | "WebIRC", 679 | "std.js", 680 | "queue", 681 | "iscroll", 682 | "Tilt", 683 | "hookbox", 684 | "step", 685 | "sap", 686 | "zcov", 687 | "creationix", 688 | "Socialite", 689 | "speak.js", 690 | "knox", 691 | "p5", 692 | "cconf.github.com", 693 | "agilefant", 694 | "EntityJS", 695 | "polymaps", 696 | "jGauge", 697 | "reified", 698 | "juggernaut", 699 | "localStore", 700 | "turntable.fm", 701 | "koshinuke", 702 | "yslow", 703 | "keymaster", 704 | "akihabara", 705 | "stellar.js", 706 | "whenever.js", 707 | "kissyteam.github.com", 708 | "vrome", 709 | "elasticsearch.github.com", 710 | "html5please", 711 | "gdash", 712 | "ezautosave", 713 | "hekyll", 714 | "TEditor", 715 | "session.js", 716 | "propelorm.github.com", 717 | "suit", 718 | "flurid", 719 | "staticdocs", 720 | "fbootstrapp", 721 | "livestyle", 722 | "firequery", 723 | "gameQuery", 724 | "stylus", 725 | "WAF", 726 | "jQCloud", 727 | "slidedown", 728 | "easie", 729 | "nvd3", 730 | "Desligado", 731 | "flatiron", 732 | "agent8ball", 733 | "consolidate.js", 734 | "ezstyleeditor", 735 | "unify", 736 | "ZenCodingNetBeansPlugin", 737 | "handlebars.js", 738 | "ezoe", 739 | "MiniSub", 740 | "Dorrie", 741 | "Lettering.js", 742 | "request", 743 | "jessie", 744 | "tatami", 745 | "msgboy", 746 | "PaintbrushJS", 747 | "jsonld.js", 748 | "jstree", 749 | "strophejs", 750 | "bubbletree", 751 | "prettyPrint.js", 752 | "springy", 753 | "CSSS", 754 | "threads", 755 | "RiverTrail", 756 | "ntwitter", 757 | "multiselect", 758 | "amplify", 759 | "HNHalfLife", 760 | "casperjs", 761 | "Selenium2", 762 | "websocket.io", 763 | "messageformat.js", 764 | "h5ai", 765 | "gazel", 766 | "bootstrap.stylus", 767 | "placeholder", 768 | "OpenTreeMap", 769 | "jQueryAutocompletePlugin", 770 | "jsdom", 771 | "enchant.js", 772 | "leihs", 773 | "jsrepl", 774 | "timepicker", 775 | "markdownR", 776 | "toast", 777 | "railsyardcms", 778 | "digitalfoosball", 779 | "yepnope.js", 780 | "TransformJS", 781 | "shadowbox", 782 | "bxslider", 783 | "callsite", 784 | "candy", 785 | "nodeslide", 786 | "pattern", 787 | "jwysiwyg", 788 | "union", 789 | "bgiframe", 790 | "dragon", 791 | "panda", 792 | "wax", 793 | "datui", 794 | "crossfilter", 795 | "LazyBoy", 796 | "doubleSuggest", 797 | "ezie", 798 | "atomjs", 799 | "bsmSelect", 800 | "Kickstart", 801 | "backboneboilerplate", 802 | "xiaotiantian", 803 | "helenus", 804 | "3dtransforms", 805 | "documentcloud", 806 | "postal.js", 807 | "limejs", 808 | "redback", 809 | "wrapup", 810 | "jomohojs", 811 | "App", 812 | "dustjs", 813 | "nodePhpSessions", 814 | "SilkJS", 815 | "faker", 816 | "diskusagereports", 817 | "holla", 818 | "canjs", 819 | "twss.js", 820 | "Kickstrap", 821 | "luca", 822 | "pjscrape", 823 | "requestpolicy", 824 | "osmus", 825 | "SlidingDrawer", 826 | "superslides", 827 | "Speeqe", 828 | "joli.api.js", 829 | "jax", 830 | "bbb", 831 | "joli.js", 832 | "pages.github.com", 833 | "Selene", 834 | "bbUI.js", 835 | "JFavicon", 836 | "lazyload", 837 | "jscache", 838 | "tinker", 839 | "Glisse.js", 840 | "KoGrid", 841 | "composer.js", 842 | "OpenESPI", 843 | "adapt", 844 | "PagedList", 845 | "Chatty", 846 | "dracula", 847 | "jQuery.fracs", 848 | "embedjs", 849 | "TermKit", 850 | "colorbox", 851 | "couchtato", 852 | "Evergreen", 853 | "arquillian.github.com", 854 | "Frameless", 855 | "Acebug", 856 | "nodeblog", 857 | "mootools2", 858 | "Mu", 859 | "vimium", 860 | "spin.js", 861 | "Blueberry", 862 | "X", 863 | "JSARToolKit", 864 | "jihua", 865 | "jqiphoneslide", 866 | "EventEmitter2", 867 | "sgn", 868 | "bvjs", 869 | "djblog", 870 | "iLepra", 871 | "Scale", 872 | "parcycle", 873 | "openphin", 874 | "jQuery.fn.autoResize", 875 | "LiipMonitorBundle", 876 | "ice", 877 | "minispade", 878 | "gmap", 879 | "caterwaul", 880 | "websocketpp", 881 | "q", 882 | "filtrr", 883 | "ElyCharts", 884 | "gcli", 885 | "mothereffinganimatedgif", 886 | "EventVat", 887 | "boomerang", 888 | "html5Widgets", 889 | "spraycan", 890 | "Moth", 891 | "almond", 892 | "mercadolibre.js", 893 | "members", 894 | "uglifier", 895 | "virtua", 896 | "matchMedia.js", 897 | "FeatherGL", 898 | "mana", 899 | "jit", 900 | "kue", 901 | "BigText", 902 | "prime", 903 | "jDataView", 904 | "glee", 905 | "gladius", 906 | "GeoExplorer", 907 | "duckduckgo", 908 | "dailyjs", 909 | "base12", 910 | "waitForImages", 911 | "mustache.couch.js", 912 | "chai", 913 | "SoundManager2", 914 | "minion", 915 | "hurl", 916 | "Blocks", 917 | "code52.github.com", 918 | "platform.js", 919 | "Tempus", 920 | "PIE", 921 | "NuGetDocs", 922 | "rabbit.js", 923 | "Toolkit", 924 | "CodeBook", 925 | "xstats.js", 926 | "uranium", 927 | "HearsayRequireJSBundle", 928 | "Raphael.FreeTransform", 929 | "SwipeView", 930 | "prefixfree", 931 | "Kizzy", 932 | "BasicsOfThreeJS", 933 | "Punchy", 934 | "sagenb", 935 | "forever", 936 | "cors", 937 | "pegjs", 938 | "NodeBeginnerBook", 939 | "db.js", 940 | "carto", 941 | "sitequery", 942 | "tweetanium", 943 | "OpenKaya", 944 | "resumable.js", 945 | "treehugger", 946 | "pophealth.github.com", 947 | "pixastic", 948 | "monomi", 949 | "Growl", 950 | "thimble", 951 | "jrac", 952 | "shumway", 953 | "DataTables", 954 | "somajs", 955 | "accounting.js", 956 | "nipster", 957 | "shapado", 958 | "snucode", 959 | "openmind", 960 | "jobberrails", 961 | "g.raphael", 962 | "Alice", 963 | "FireTray", 964 | "StickyTableHeaders", 965 | "heatcanvas", 966 | "paynode", 967 | "jslint.vim", 968 | "jsHtmlToText", 969 | "gxp", 970 | "OSMTM", 971 | "infusion", 972 | "resttesttest", 973 | "blacksmith", 974 | "folder2sprite", 975 | "cssFx", 976 | "html5shiv", 977 | "jqTransform", 978 | "orion.client", 979 | "zip.js", 980 | "Extensible", 981 | "slides.htm", 982 | "csswarp.js", 983 | "knockback", 984 | "shoulda.js", 985 | "cannon.js", 986 | "jsshell", 987 | "Cutter.js", 988 | "dygraphs", 989 | "Portamento", 990 | "trycatch", 991 | "node.couchapp.js", 992 | "videoPlayer", 993 | "Squire", 994 | "rcarousel", 995 | "DaumEditor", 996 | "opencomparison", 997 | "codersociety.github.com", 998 | "sparks.js", 999 | "joyride", 1000 | "wru", 1001 | "greasemonkey", 1002 | "kohai", 1003 | "RaccoonBlog", 1004 | "RESTClient", 1005 | "rrestjs", 1006 | "ursa", 1007 | "imgareaselect", 1008 | "wheat", 1009 | "PubSubJS", 1010 | "TitanTricks", 1011 | "slither", 1012 | "art", 1013 | "backbone.validations", 1014 | "backbone.validation", 1015 | "list", 1016 | "shim", 1017 | "jslintmate", 1018 | "KitchenSinkiPad", 1019 | "html5Preloader.js", 1020 | "hiro", 1021 | "orbit", 1022 | "IndexedDB", 1023 | "Do", 1024 | "Notificon", 1025 | "gut", 1026 | "sylvester", 1027 | "IDBWrapper", 1028 | "youkuhtml5playerbookmark", 1029 | "MyExtensions", 1030 | "audiolib.js", 1031 | "dictionary", 1032 | "TableDnD", 1033 | "backbone.memento", 1034 | "sqlabs", 1035 | "unveil", 1036 | "webfontloader", 1037 | "JX", 1038 | "Adplayer", 1039 | "include.js", 1040 | "backbone.io", 1041 | "Arctic.js", 1042 | "Ext.data.proxy.IndexedDB", 1043 | "LooseLeaf", 1044 | "trisano", 1045 | "validate.js", 1046 | "backbone.analytics", 1047 | "Overscroll", 1048 | "curriculum", 1049 | "stats.js", 1050 | "CAAT", 1051 | "dropshado.ws", 1052 | "jqTree", 1053 | "shred", 1054 | "nedis", 1055 | "support", 1056 | "treesaver", 1057 | "jsdiff", 1058 | "MailCheckerMinus", 1059 | "locust", 1060 | "JQtextile", 1061 | "iceScrum", 1062 | "dox", 1063 | "mxn", 1064 | "cannoli", 1065 | "SNORQL", 1066 | "socketbug", 1067 | "Clamp.js", 1068 | "wikistream", 1069 | "Capsule", 1070 | "MashupsLibrary", 1071 | "jslinuxstorage", 1072 | "ndoc", 1073 | "owp", 1074 | "BazingaExposeTranslationBundle", 1075 | "devnull", 1076 | "shower", 1077 | "spine.app", 1078 | "tooltipsy", 1079 | "keepasshttp", 1080 | "dreadnot", 1081 | "readium", 1082 | "nodelint", 1083 | "localtodos", 1084 | "valentine", 1085 | "sameplace", 1086 | "fun", 1087 | "Jcrop", 1088 | "trafficcone", 1089 | "FastLegS", 1090 | "reveal", 1091 | "synapse", 1092 | "matador", 1093 | "quicksand", 1094 | "steal", 1095 | "vintageJS", 1096 | "flot", 1097 | "WebIntents", 1098 | "Kibana", 1099 | "labs", 1100 | "BabelExt", 1101 | "reMarked.js", 1102 | "sausage", 1103 | "jclock", 1104 | "joostina", 1105 | "Sample.RSS", 1106 | "lawnchair", 1107 | "Sample.Todo", 1108 | "nodemanual.org", 1109 | "xstyle", 1110 | "blockui", 1111 | "ICanHaz.js", 1112 | "corner", 1113 | "Smartupdater", 1114 | "OpenACD", 1115 | "Struct", 1116 | "FCBKcomplete", 1117 | "mongorito", 1118 | "HelloPhoneGap", 1119 | "irc", 1120 | "ical.js", 1121 | "ixf", 1122 | "geo", 1123 | "query", 1124 | "read", 1125 | "utile", 1126 | "TraceKit", 1127 | "Fuse.js", 1128 | "gas", 1129 | "lua.js", 1130 | "queryIndexedDB", 1131 | "diva.js", 1132 | "kickoff", 1133 | "backbone.offline", 1134 | "Jed", 1135 | "lightningjs", 1136 | "cluster", 1137 | "tomatoes", 1138 | "zoom.js", 1139 | "selectToAutocomplete", 1140 | "ioreader", 1141 | "datePicker", 1142 | "contracts.coffee", 1143 | "turing.js", 1144 | "json2html", 1145 | "maki", 1146 | "coa", 1147 | "anvil.js", 1148 | "jsGameSoup", 1149 | "pyzotero", 1150 | "DropZone", 1151 | "yeti", 1152 | "jQuery.Gantt", 1153 | "audia", 1154 | "Glimpse", 1155 | "EventEmitter", 1156 | "CrossSlide", 1157 | "doctorjs", 1158 | "asciifi", 1159 | "toxiclibsjs", 1160 | "jstouchslide", 1161 | "mediaelement", 1162 | "CSS1K", 1163 | "corejs", 1164 | "facebox", 1165 | "browserjs", 1166 | "mechanic", 1167 | "jMediaelement", 1168 | "proper", 1169 | "spidernode", 1170 | "tilestream", 1171 | "axel", 1172 | "webservice.js", 1173 | "juiceui", 1174 | "www.luffy.cx", 1175 | "nssocket", 1176 | "HoverBattles", 1177 | "kyuri", 1178 | "reds", 1179 | "Znode", 1180 | "Laudio", 1181 | "jTweetsAnywhere", 1182 | "Faker.js", 1183 | "Movuca", 1184 | "bootbox", 1185 | "cepmon", 1186 | "raintpl3", 1187 | "iisnode", 1188 | "response.js", 1189 | "fin", 1190 | "js.class", 1191 | "fingerpoken", 1192 | "jsparse", 1193 | "clarinet", 1194 | "fmpstandards", 1195 | "zendblog", 1196 | "gridy", 1197 | "bloog", 1198 | "jQR", 1199 | "declarative", 1200 | "grumble.js", 1201 | "iviewer", 1202 | "webSlide", 1203 | "neo4js", 1204 | "lokka", 1205 | "js.io", 1206 | "websockify", 1207 | "Pot.js", 1208 | "develop.github.com", 1209 | "lostdecade.github.com", 1210 | "abba", 1211 | "roominator", 1212 | "smoke.js", 1213 | "sprite.js", 1214 | "xdate", 1215 | "Fireworks", 1216 | "bonescript", 1217 | "difflet", 1218 | "JSONSelect", 1219 | "webxray", 1220 | "expressling", 1221 | "oegyscroll", 1222 | "glDatePicker", 1223 | "Keyboard", 1224 | "dropfile", 1225 | "noVNC", 1226 | "MigrationsMap.net", 1227 | "alice", 1228 | "seaport", 1229 | "load.js", 1230 | "PopulationPyramid.net", 1231 | "jsHelpers", 1232 | "npm2debian", 1233 | "wunderlist", 1234 | "CIP", 1235 | "Magic", 1236 | "thegrid", 1237 | "ZIA", 1238 | "CeeBox", 1239 | "swig", 1240 | "plone.app.deco", 1241 | "foounit", 1242 | "DGSE", 1243 | "AnythingZoomer", 1244 | "cradle", 1245 | "reqwest", 1246 | "vmcjs", 1247 | "Bleextop", 1248 | "knockout.aspnetmvcdemos", 1249 | "bowser", 1250 | "jScroll", 1251 | "dnode", 1252 | "zest", 1253 | "twig.js", 1254 | "sisyphus", 1255 | "tasket", 1256 | "ncluster", 1257 | "KnobKnob", 1258 | "frank", 1259 | "narcissus", 1260 | "IodineGBA", 1261 | "wander", 1262 | "dromaeo", 1263 | "plasm.js", 1264 | "nib", 1265 | "PubMed", 1266 | "JPlus", 1267 | "mosaic", 1268 | "bones", 1269 | "datetime", 1270 | "hinclude", 1271 | "Fool.js", 1272 | "protos", 1273 | "Regions.js", 1274 | "Jester", 1275 | "Overthrow", 1276 | "control", 1277 | "Morse.js", 1278 | "jsGB", 1279 | "TransportDublin", 1280 | "jquip", 1281 | "doT", 1282 | "rt", 1283 | "always", 1284 | "Queuebert", 1285 | "role", 1286 | "mediafrontpage", 1287 | "nTunes", 1288 | "pleft", 1289 | "graphiti", 1290 | "Audiolet", 1291 | "backrub", 1292 | "com.woltlab.wcf.message", 1293 | "PikaChoose", 1294 | "ipadpeek", 1295 | "piratescript", 1296 | "JSAMF", 1297 | "Tutorials", 1298 | "syn", 1299 | "CodeHaacks", 1300 | "Codestrong", 1301 | "Flickable.js", 1302 | "units.js", 1303 | "jsonview", 1304 | "persistencejs", 1305 | "hyve", 1306 | "microblog", 1307 | "Windshaft", 1308 | "ssu", 1309 | "EasyWebsocket", 1310 | "porthole", 1311 | "documentjs", 1312 | "javascriptmvc", 1313 | "jamal", 1314 | "inject", 1315 | "HandlebarJS", 1316 | "dmv", 1317 | "trollicons", 1318 | "Nustache", 1319 | "jo", 1320 | "livereload", 1321 | "VisualEvent", 1322 | "Ghost.py", 1323 | "boastful", 1324 | "superfastmatch", 1325 | "selectivizr", 1326 | "LearningRegistry", 1327 | "useragent", 1328 | "jQuery.twinkle", 1329 | "piler", 1330 | "assetgraph", 1331 | "LucidJS", 1332 | "jOrgChart", 1333 | "erkie.github.com", 1334 | "LivelyKernel", 1335 | "emotiface", 1336 | "pop", 1337 | "tornskel", 1338 | "vows", 1339 | "websocket.MQ", 1340 | "vtp", 1341 | "twitter", 1342 | "Avisota", 1343 | "masteringnode", 1344 | "microevent.js", 1345 | "now", 1346 | "pushover", 1347 | "OpenMercury", 1348 | "quickui", 1349 | "mmstats", 1350 | "visage", 1351 | "glow", 1352 | "YiiBlocks", 1353 | "Lu", 1354 | "needle", 1355 | "dijit", 1356 | "dojox", 1357 | "brisket", 1358 | "picky", 1359 | "youRhere", 1360 | "nock", 1361 | "todos", 1362 | "upnode", 1363 | "mobiscroll", 1364 | "Tagedit", 1365 | "TextboxList", 1366 | "LABjs", 1367 | "feather", 1368 | "jcanvas", 1369 | "director", 1370 | "ER", 1371 | "Struct.js", 1372 | "spm", 1373 | "LightWrite", 1374 | "Emphasis", 1375 | "musicalpha", 1376 | "lavish", 1377 | "compose", 1378 | "SquishIt", 1379 | "distribute", 1380 | "JAIL", 1381 | "openpixels", 1382 | "meteor", 1383 | "pacman", 1384 | "knockout.live.plugin", 1385 | "Radios", 1386 | "tagmate", 1387 | "ndb", 1388 | "FlixelJS", 1389 | "exhibit3", 1390 | "LanguageFundamentals", 1391 | "entityspaces.js", 1392 | "BigDecimal.js", 1393 | "expect.js", 1394 | "beardcomb", 1395 | "virtualjoystick.js", 1396 | "Plinth", 1397 | "henrik.nyh.se", 1398 | "middlefiddle", 1399 | "html5sql", 1400 | "raty", 1401 | "waterbear", 1402 | "palette", 1403 | "hborecycling", 1404 | "RapidFTR", 1405 | "wherestheparty", 1406 | "Backbone.Subset", 1407 | "nide", 1408 | "SURVIVOR", 1409 | "craftyjstut", 1410 | "PrusaMendel", 1411 | "orderly.js", 1412 | "protovis", 1413 | "bloomfilter.js", 1414 | "c3", 1415 | "jSlide", 1416 | "lscache", 1417 | "DragDrop", 1418 | "Squirrel.js", 1419 | "Algorithms", 1420 | "holman.github.com", 1421 | "NicknameTabComplete", 1422 | "spec", 1423 | "Radio", 1424 | "jqNode", 1425 | "redirectory", 1426 | "placeholder.js", 1427 | "hummingbird", 1428 | "jelly", 1429 | "MovingBoxes", 1430 | "javascript.tmbundle", 1431 | "graph.tk", 1432 | "zkybase", 1433 | "jsftp", 1434 | "Dataview.js", 1435 | "domready", 1436 | "CuteTime", 1437 | "jsqrcode", 1438 | "nclosure", 1439 | "csonv.js", 1440 | "tablefilter", 1441 | "Core", 1442 | "styleguide", 1443 | "VIE", 1444 | "eve", 1445 | "pageflipper", 1446 | "vexflow", 1447 | "slabText", 1448 | "WebViewer", 1449 | "html5media", 1450 | "namespace", 1451 | "bogart", 1452 | "gee", 1453 | "3gears", 1454 | "kataspace", 1455 | "katajs", 1456 | "tween.js", 1457 | "dsp.js", 1458 | "ranviermud", 1459 | "runloop", 1460 | "jszip", 1461 | "WKTouch", 1462 | "shake.js", 1463 | "book", 1464 | "autoComplete.js", 1465 | "breizhcamp", 1466 | "Downloadify", 1467 | "bitarray.js", 1468 | "uedit", 1469 | "jog", 1470 | "flarevideo", 1471 | "QRCode", 1472 | "mindmaps", 1473 | "perldoc.perl.org", 1474 | "exhibitor", 1475 | "jQuery.Tagify", 1476 | "commander.js", 1477 | "BiSON.js", 1478 | "biwascheme", 1479 | "Knockout.Extensions", 1480 | "AppBoilerplate", 1481 | "surface", 1482 | "RESTduino", 1483 | "kitchensink", 1484 | "realie", 1485 | "keysnail", 1486 | "CalVis3", 1487 | "SocialBootstrapApi", 1488 | "coco", 1489 | "remoteStorage.js", 1490 | "cubes", 1491 | "simplemodal", 1492 | "jack", 1493 | "LexikTranslationBundle", 1494 | "metro.css", 1495 | "healthjs", 1496 | "seamcarving", 1497 | "nullmq", 1498 | "restconsole", 1499 | "PacketQ", 1500 | "logule", 1501 | "DMS", 1502 | "zenircbot", 1503 | "modul8", 1504 | "d3talk", 1505 | "mintrayr", 1506 | "autoproxy", 1507 | "d3x", 1508 | "sammy", 1509 | "warp", 1510 | "viewporter", 1511 | "EventSource", 1512 | "TrafficCop", 1513 | "boilerstrap", 1514 | "apollo", 1515 | "jsface", 1516 | "Dashboard", 1517 | "jQuery.countdown", 1518 | "mongo3", 1519 | "GLGE", 1520 | "Captionator", 1521 | "lab", 1522 | "zoom", 1523 | "monte", 1524 | "gridx", 1525 | "shell", 1526 | "sibilant", 1527 | "journey", 1528 | "breakpoints", 1529 | "jqPagination", 1530 | "coapp.org", 1531 | "cassis", 1532 | "multifox", 1533 | "modules.zendframework.com", 1534 | "HouseAgent", 1535 | "tap.js", 1536 | "jsonlintdotcom", 1537 | "VisualPHPUnit", 1538 | "livequery", 1539 | "jison", 1540 | "Odin", 1541 | "jui", 1542 | "tiddlyspace", 1543 | "mozillians", 1544 | "formaline", 1545 | "TiddlyWiki5", 1546 | "Boomgraph", 1547 | "localModel", 1548 | "tag", 1549 | "bigpipe", 1550 | "kidsruby", 1551 | "mochaui", 1552 | "Sticky", 1553 | "jmapping", 1554 | "TouchScroll", 1555 | "Peanutty", 1556 | "strata", 1557 | "doctor", 1558 | "Flow", 1559 | "angularjs.org", 1560 | "css3please", 1561 | "EnvJasmine", 1562 | "redmon", 1563 | "RedmineThemePixelCookers", 1564 | "NicEdit", 1565 | "Backbone.Meninges", 1566 | "Scribl", 1567 | "AudioStreamer", 1568 | "nano", 1569 | "Siege", 1570 | "tobi", 1571 | "shellshape", 1572 | "rubeque", 1573 | "math", 1574 | "djenofdjango", 1575 | "jq.carousel", 1576 | "kindeditor", 1577 | "Control", 1578 | "feedme.js", 1579 | "12306", 1580 | "filter.js", 1581 | "MilkChart", 1582 | "fluid960gs", 1583 | "chromesniffer", 1584 | "profxmpp", 1585 | "PyOfWave", 1586 | "Steppe", 1587 | "AwesomeChartJS", 1588 | "Hoverizr", 1589 | "rewriter", 1590 | "WikiSocium", 1591 | "startpage.rwrt", 1592 | "wob.js", 1593 | "tracksapp.github.com", 1594 | "nerve", 1595 | "kranium", 1596 | "snapbird", 1597 | "treestyletab", 1598 | "magi", 1599 | "prepareTransition", 1600 | "geoext", 1601 | "RdvZ", 1602 | "ipull", 1603 | "weld", 1604 | "plax", 1605 | "InlineEditor", 1606 | "SuperChromePass", 1607 | "libreprojects", 1608 | "punycode.js", 1609 | "slugr", 1610 | "NodeSSH", 1611 | "beamjs", 1612 | "melpa", 1613 | "luna", 1614 | "mpns", 1615 | "SlideShow", 1616 | "flapjax", 1617 | "loupe", 1618 | "currency.io", 1619 | "pocketchangeapp", 1620 | "passifox", 1621 | "nodepad", 1622 | "backpusher", 1623 | "cqrsjourney.github.com", 1624 | "remark", 1625 | "amplesdk", 1626 | "pmxdr", 1627 | "iGesture", 1628 | "JsChilicat", 1629 | "samplemvc", 1630 | "samples", 1631 | "nwmatcher", 1632 | "Vanadium", 1633 | "banking.js", 1634 | "bullet", 1635 | "jsdifflib", 1636 | "qwery", 1637 | "smoosh", 1638 | "techradar", 1639 | "KeyCandy", 1640 | "stapes", 1641 | "webdriverjs", 1642 | "tile5", 1643 | "revolver", 1644 | "jsUri", 1645 | "knockout.namespaces", 1646 | "dispatch", 1647 | "thomasdavis.github.com", 1648 | "LiipVieBundle", 1649 | "Booktype", 1650 | "getpython3.com", 1651 | "ignition", 1652 | "xauth", 1653 | "objs", 1654 | "PixelPerfect", 1655 | "jQuery.threesixty", 1656 | "jQote2", 1657 | "crossroads.js", 1658 | "metrics", 1659 | "MagicTouch", 1660 | "design.io", 1661 | "rsted", 1662 | "esmorph", 1663 | "SubAppDemo", 1664 | "enablePlaceholder", 1665 | "gordon", 1666 | "processingjstool", 1667 | "ARti", 1668 | "anchor", 1669 | "keysocket", 1670 | "ChesterGL", 1671 | "shortcut.io", 1672 | "waldo", 1673 | "logref", 1674 | "stoopid", 1675 | "dynamo", 1676 | "itsalltext", 1677 | "browsermirror", 1678 | "DVL", 1679 | "WTFEngine", 1680 | "MapCraft", 1681 | "mooeditable", 1682 | "polymapper", 1683 | "prof", 1684 | "boilerplate", 1685 | "uki", 1686 | "panorama360", 1687 | "Apprise", 1688 | "gamejs", 1689 | "coffeejade", 1690 | "pega.io", 1691 | "xmpp4moz", 1692 | "interleave", 1693 | "Granule", 1694 | "xjst", 1695 | "gamepad.js", 1696 | "Coulisse", 1697 | "publisher.js", 1698 | "GraphGL", 1699 | "up", 1700 | "response", 1701 | "stack", 1702 | "lilac", 1703 | "JSDev", 1704 | "pystar", 1705 | "dashboard", 1706 | "kartograph.org", 1707 | "vogue", 1708 | "freshereditor", 1709 | "himera", 1710 | "inca", 1711 | "ansi.js", 1712 | "qq", 1713 | "soca", 1714 | "codermn", 1715 | "Ext.ux.touch.grid", 1716 | "cleanslate", 1717 | "pathjs", 1718 | "jslint4java", 1719 | "revalidator", 1720 | "QuickSlides", 1721 | "bujagali", 1722 | "railwayjs.com", 1723 | "prettyphoto", 1724 | "jecookie", 1725 | "mockery", 1726 | "CSS3D.js", 1727 | "nimble", 1728 | "tablefixedheader", 1729 | "Codeshelver", 1730 | "vvvv.js", 1731 | "slang", 1732 | "twinkle", 1733 | "supplement.js", 1734 | "nwm", 1735 | "mongous", 1736 | "Firemacs", 1737 | "instiki", 1738 | "cloudsave", 1739 | "GithubSharp", 1740 | "libgit2.github.com", 1741 | "wymeditor", 1742 | "BeerMe", 1743 | "snob", 1744 | "scroller", 1745 | "ScrollFix", 1746 | "OverflowScrollingFix", 1747 | "nodegit", 1748 | "dryice", 1749 | "jparallax", 1750 | "tappable", 1751 | "csshttprequest", 1752 | "screenfull.js", 1753 | "jQRange", 1754 | "recline", 1755 | "iodocs", 1756 | "xmppjs", 1757 | "roco", 1758 | "qwrap", 1759 | "efrepo", 1760 | "tree", 1761 | "wildfire", 1762 | "localStorageDB", 1763 | "input.js", 1764 | "jsmockito", 1765 | "move", 1766 | "FluentIL", 1767 | "cachirulovalleydirectory", 1768 | "StaticSite", 1769 | "XWMM", 1770 | "introducinghtml5", 1771 | "gooddata.github.com", 1772 | "MockHttpRequest", 1773 | "peity", 1774 | "Kernel.js", 1775 | "scrumboard", 1776 | "BrickPile", 1777 | "cssSandpaper", 1778 | "JigLibJS2", 1779 | "shareDesk", 1780 | "clusterhub", 1781 | "archetypes", 1782 | "webforms2", 1783 | "interact", 1784 | "color", 1785 | "brain", 1786 | "ccss", 1787 | "html5", 1788 | "nexpect", 1789 | "therabbithole", 1790 | "Chili", 1791 | "ralph", 1792 | "highlight", 1793 | "wolfram", 1794 | "modulargrid", 1795 | "vnc.js", 1796 | "tcp.js", 1797 | "jsgrep", 1798 | "livemap", 1799 | "shardjs", 1800 | "OpenTag", 1801 | "pinify", 1802 | "Shipyard", 1803 | "openhercules", 1804 | "scraper", 1805 | "sofa", 1806 | "Resizer", 1807 | "propagit", 1808 | "webactors", 1809 | "NodeChat", 1810 | "extra", 1811 | "canvas", 1812 | "layout", 1813 | "leviroutes", 1814 | "torquebox.org", 1815 | "spludo", 1816 | "Enterprise", 1817 | "launch", 1818 | "gissues", 1819 | "node.dbslayer.js", 1820 | "ms.js", 1821 | "Awwation", 1822 | "spark", 1823 | "fermata", 1824 | "ReviewR", 1825 | "rocketcharts", 1826 | "miniTip", 1827 | "pow", 1828 | "Touchy.js", 1829 | "webjs", 1830 | "CSSAnimation", 1831 | "gapvis", 1832 | "satisfy", 1833 | "microjungle", 1834 | "chromus", 1835 | "deCSS3", 1836 | "fathom", 1837 | "jukebox", 1838 | "slippy", 1839 | "greatbigcrane", 1840 | "shining", 1841 | "Springboard", 1842 | "sip.js", 1843 | "nakedpassword", 1844 | "Bundler", 1845 | "htracr", 1846 | "grasshopper", 1847 | "asEvented", 1848 | "scroll.js", 1849 | "rubythankful", 1850 | "huboard", 1851 | "machina.js", 1852 | "ape", 1853 | "hubticle.github.com", 1854 | "filter", 1855 | "nfe", 1856 | "AceGWT", 1857 | "csg.js", 1858 | "kneath.github.com", 1859 | "tryocaml", 1860 | "jsfx", 1861 | "FlipPics", 1862 | "ready.js", 1863 | "mongode", 1864 | "minimal", 1865 | "inflect", 1866 | "lightgl.js", 1867 | "postmark.js", 1868 | "foxjs", 1869 | "moofx", 1870 | "pd", 1871 | "float.js", 1872 | "mote", 1873 | "log.js", 1874 | "Rubex", 1875 | "SVG.toDataURL", 1876 | "finance", 1877 | "vibou.gTile", 1878 | "webfs", 1879 | "traceur", 1880 | "activejs", 1881 | "eyeballs.js", 1882 | "stalker", 1883 | "futures", 1884 | "evercookie", 1885 | "microwave", 1886 | "ybuild", 1887 | "spider", 1888 | "Search", 1889 | "trackiffer", 1890 | "Backbonist", 1891 | "KaboomJS", 1892 | "Geometry.js", 1893 | "music.js", 1894 | "jsmad", 1895 | "elmo", 1896 | "XAudioJS", 1897 | "pcmdata.js", 1898 | "binary.js", 1899 | "player", 1900 | "counter", 1901 | "Badger", 1902 | "quora", 1903 | "slidy", 1904 | "10er10", 1905 | "quill", 1906 | "hashgrid", 1907 | "stack.io", 1908 | "simplegraph", 1909 | "loadrunner", 1910 | "rekapi", 1911 | "iframework", 1912 | "jsmidi", 1913 | "josediazgonzalez.com", 1914 | "shifty", 1915 | "EngageNet", 1916 | "Love", 1917 | "playdar.js", 1918 | "debug", 1919 | "favs", 1920 | "JSCORM", 1921 | "monocles", 1922 | "stativus", 1923 | "dynamicaudio.js", 1924 | "dynamoDB", 1925 | "sift.js", 1926 | "JSV", 1927 | "emacsbook", 1928 | "railsapps.github.com", 1929 | "escodegen", 1930 | "TicTacToe", 1931 | "lucid", 1932 | "IronJS", 1933 | "txn", 1934 | "nodeQuery", 1935 | "philogl", 1936 | "brewer.js", 1937 | "TiHandson", 1938 | "outlet", 1939 | "mongee", 1940 | "axonome", 1941 | "cookies", 1942 | "store", 1943 | "TryRuby", 1944 | "javelin", 1945 | "Booking", 1946 | "cft", 1947 | "colors.js", 1948 | "keycode.js", 1949 | "NBlog", 1950 | "myformbuilder", 1951 | "EnhanceJS", 1952 | "codesurgeon", 1953 | "Zoombox", 1954 | "wheresmyschoolbus", 1955 | "resourceful", 1956 | "wmd", 1957 | "ncore", 1958 | "NinjaKit", 1959 | "raphael.serialize", 1960 | "cucumis", 1961 | "thorax", 1962 | "cacique", 1963 | "maps.stamen.com", 1964 | "yate", 1965 | "hijs", 1966 | "rdf.js", 1967 | "talks", 1968 | "drtwoot", 1969 | "noUiSlider", 1970 | "spine.todos", 1971 | "Craftyslide", 1972 | "Joose", 1973 | "tabzilla", 1974 | "formfactor", 1975 | "cde", 1976 | "openstreetbugs", 1977 | "rotate3Di", 1978 | "sourcekit", 1979 | "nestor", 1980 | "MavensMate", 1981 | "mothereff.in", 1982 | "emphasize.js", 1983 | "behavior", 1984 | "randexp.js", 1985 | "Parallaxjs", 1986 | "fstream", 1987 | "wrapjs", 1988 | "maphilight", 1989 | "SourceSquare", 1990 | "Ext.ux.touch.Rating", 1991 | "Spritemapper", 1992 | "davidblog", 1993 | "testosterone", 1994 | "tombloo", 1995 | "slickback", 1996 | "CodeMirror", 1997 | "Maple.js", 1998 | "BackboneJsAndFullCalendar", 1999 | "joint", 2000 | "keydown", 2001 | "Template.Tabbed", 2002 | "Slidifier", 2003 | "dynode", 2004 | "cookbook", 2005 | "protocol", 2006 | "nodejs.ir", 2007 | "codex", 2008 | "nyroModal", 2009 | "eyes.js", 2010 | "CrashReporterDemo", 2011 | "blend", 2012 | "css3test", 2013 | "WeScheme", 2014 | "Ext.ux.TouchCalendar", 2015 | "dew", 2016 | "jsii", 2017 | "blocker", 2018 | "owtf", 2019 | "sticky", 2020 | "youtubeplaylist", 2021 | "Sphere", 2022 | "graphite", 2023 | "twit", 2024 | "slick", 2025 | "pietimer", 2026 | "muralapp", 2027 | "TypeSequence", 2028 | "nconf", 2029 | "pathmenu.js", 2030 | "synchrotron", 2031 | "science.js", 2032 | "donut", 2033 | "askken", 2034 | "erljs", 2035 | "jsonlint", 2036 | "jsPDF", 2037 | "html5support", 2038 | "audiodata", 2039 | "has.js", 2040 | "griffin.editor", 2041 | "delegate", 2042 | "FlexiColorPicker", 2043 | "box2d.js", 2044 | "wmuSlider", 2045 | "Articles", 2046 | "nue", 2047 | "roto", 2048 | "konphyg", 2049 | "Rumpetroll", 2050 | "gaebar", 2051 | "sawpf", 2052 | "Dandelion", 2053 | "orbium", 2054 | "Happy.js", 2055 | "gurupi.org", 2056 | "Lungo.Sugars", 2057 | "kaiten", 2058 | "three.dart", 2059 | "scrollability", 2060 | "KickAssets", 2061 | "cowl", 2062 | "jsnes", 2063 | "JSBoy", 2064 | "SpecsFor", 2065 | "import", 2066 | "Extendables", 2067 | "swinger", 2068 | "shapesmith", 2069 | "occupywallst", 2070 | "binb", 2071 | "api.js", 2072 | "ircd.js", 2073 | "impactConnect", 2074 | "tempo", 2075 | "CodeStoryStep2", 2076 | "ElementStack", 2077 | "html5slider", 2078 | "alokmenghrajani.github.com", 2079 | "scenejs", 2080 | "gerbil", 2081 | "404", 2082 | "schema", 2083 | "instapoppin", 2084 | "BootstrapMVC", 2085 | "ThreeDots", 2086 | "eventproxy", 2087 | "postmessage", 2088 | "audionode", 2089 | "JSONStream", 2090 | "ducttape", 2091 | "expound", 2092 | "jWorkflow", 2093 | "lifesinger.github.com", 2094 | "In", 2095 | "fugue", 2096 | "monwarp", 2097 | "runjs", 2098 | "marmot", 2099 | "simplesmtp", 2100 | "Bookmarkly", 2101 | "cansecurity", 2102 | "TiMetro", 2103 | "injectr", 2104 | "mobilemenu", 2105 | "spartify", 2106 | "jKey", 2107 | "UserInfuser", 2108 | "functools", 2109 | "diggy", 2110 | "ymacs", 2111 | "TiWinSlider", 2112 | "player.vas3k.ru", 2113 | "Kirin", 2114 | "diagonalFade", 2115 | "GSet", 2116 | "conductor", 2117 | "fastFrag", 2118 | "ghterm", 2119 | "geowebcache", 2120 | "bolt", 2121 | "gitdocs", 2122 | "Crunch", 2123 | "jschannel", 2124 | "Balloons.IO", 2125 | "nodeigniter", 2126 | "GLOW", 2127 | "threex", 2128 | "taboo", 2129 | "agiletickets", 2130 | "volkszaehler.org", 2131 | "haml.js", 2132 | "tijscore", 2133 | "jsmirrors", 2134 | "polyfills", 2135 | "sh.js", 2136 | "hypher", 2137 | "jiveapps", 2138 | "lumbar", 2139 | "statusdashboard", 2140 | "scrubyt", 2141 | "worzone", 2142 | "rockwood", 2143 | "flexie", 2144 | "FaxJs", 2145 | "KitchenSink", 2146 | "mixr", 2147 | "cpm", 2148 | "CalEnder", 2149 | "lrthw.github.com", 2150 | "Lockets", 2151 | "capt", 2152 | "Pajinate", 2153 | "TiStoreKit", 2154 | "imageCache", 2155 | "minimatch", 2156 | "sherpa", 2157 | "vidchat", 2158 | "short", 2159 | "slidify", 2160 | "cleditor", 2161 | "jQueryFileUpload.Net", 2162 | "syze", 2163 | "jspp", 2164 | "emile", 2165 | "jazz", 2166 | "conkeror", 2167 | "terrificjs", 2168 | "QuoJS", 2169 | "webhookit", 2170 | "botio", 2171 | "nbl", 2172 | "JSON.minify", 2173 | "960gridder", 2174 | "confess", 2175 | "oauthorizer", 2176 | "polychart.js", 2177 | "do", 2178 | "androjs", 2179 | "protolicious", 2180 | "Son", 2181 | "cascade", 2182 | "cliff", 2183 | "BroadleafCommerceDemoSite", 2184 | "JSManipulate", 2185 | "vapor.js", 2186 | "polyfill.js", 2187 | "IntroD3", 2188 | "jsshaper", 2189 | "reddit.tv", 2190 | "IPCNode", 2191 | "NextJS", 2192 | "osgjs", 2193 | "borschik", 2194 | "darkstrap", 2195 | "def.js", 2196 | "Timecop.js", 2197 | "classtalk", 2198 | "MooPlay", 2199 | "mozrepl", 2200 | "daemon.node", 2201 | "TodoList", 2202 | "TeamFeeder", 2203 | "underscore.Deferred", 2204 | "judge", 2205 | "scrollbar", 2206 | "gauss", 2207 | "Markup.js", 2208 | "VivaGraphJS", 2209 | "echonest.streamgraphing.org", 2210 | "measures", 2211 | "userChrome.js", 2212 | "perstore", 2213 | "sparkseditor", 2214 | "X2", 2215 | "JSLINQ", 2216 | "html5edit", 2217 | "Fotorama", 2218 | "scotch", 2219 | "nestedSortable", 2220 | "combohandler", 2221 | "barista", 2222 | "matcha", 2223 | "streamgraph.js", 2224 | "hailwhale", 2225 | "ak", 2226 | "dropshare", 2227 | "OriginJS", 2228 | "jshint.vim", 2229 | "edgelisp", 2230 | "fusebox", 2231 | "choreographer", 2232 | "graphquire", 2233 | "bob", 2234 | "prospector", 2235 | "todolist", 2236 | "releasenotes", 2237 | "buzzbird", 2238 | "1kg.org", 2239 | "jlayout", 2240 | "TiGrid", 2241 | "jbar", 2242 | "dollardom", 2243 | "vision", 2244 | "dnsserver.js", 2245 | "ncp", 2246 | "filer.js", 2247 | "morestreams", 2248 | "MvcContrib", 2249 | "quixe", 2250 | "Donatello", 2251 | "just", 2252 | "metamorph.js", 2253 | "form2js", 2254 | "visibility.js", 2255 | "limestone", 2256 | "stevelosh", 2257 | "detectmobile.js", 2258 | "Flags", 2259 | "setochka", 2260 | "ingenioJS", 2261 | "zoey", 2262 | "ocanvas", 2263 | "timvc", 2264 | "Ajaxmanager", 2265 | "ain", 2266 | "deckem", 2267 | "ExamplesByMesh", 2268 | "berlinjs.org", 2269 | "goto", 2270 | "modalbox", 2271 | "finchjs", 2272 | "MassiveCover", 2273 | "nodeEventStore", 2274 | "OverlappingMarkerSpiderfier", 2275 | "Delivery.js", 2276 | "twitterlib", 2277 | "diveintopython3", 2278 | "SafariKeywordSearch", 2279 | "html5validator", 2280 | "wowhead", 2281 | "V5", 2282 | "watchman", 2283 | "twicli", 2284 | "firmin", 2285 | "w3fools", 2286 | "Timed", 2287 | "hub.me", 2288 | "milkshake", 2289 | "brite", 2290 | "DartVectorMath", 2291 | "burst", 2292 | "neuron", 2293 | "hnet", 2294 | "respect.github.com", 2295 | "bindWithDelay", 2296 | "nodepress", 2297 | "eidogo", 2298 | "OzJS", 2299 | "onde", 2300 | "entropy.js", 2301 | "invoke", 2302 | "darkbox", 2303 | "fab", 2304 | "JSinJS", 2305 | "mondrian", 2306 | "dalliance", 2307 | "CraftyBox2D", 2308 | "mdoc", 2309 | "TiDrop", 2310 | "Jang", 2311 | "es5.github.com", 2312 | "browserl", 2313 | "nodesynth", 2314 | "cooltips", 2315 | "jellyfish", 2316 | "bloody.go", 2317 | "recursive", 2318 | "Prevel", 2319 | "npmjs.org", 2320 | "circleplayer", 2321 | "clientside", 2322 | "aceditable", 2323 | "Cuore.js", 2324 | "sdoc", 2325 | "jtab", 2326 | "osmplayer", 2327 | "narrowthegapp", 2328 | "capsela", 2329 | "mobipick", 2330 | "phonegapcontacts", 2331 | "ligament.js", 2332 | "jsgif", 2333 | "JSONH", 2334 | "play.js", 2335 | "company", 2336 | "site", 2337 | "sqwish", 2338 | "apejs", 2339 | "suggestr.js", 2340 | "datadigitizer", 2341 | "kanade", 2342 | "vegas", 2343 | "srchr", 2344 | "tasscss", 2345 | "wink", 2346 | "jSlideto", 2347 | "ngist", 2348 | "touche", 2349 | "domcrypt", 2350 | "serve", 2351 | "wreq", 2352 | "jeesh", 2353 | "exValidation", 2354 | "kratko.js", 2355 | "ping", 2356 | "harmony", 2357 | "aToolTip", 2358 | "jslim", 2359 | "kerning.js", 2360 | "spah", 2361 | "cereal", 2362 | "twerp", 2363 | "blode", 2364 | "NodeMUD", 2365 | "Mobile", 2366 | "tmpltr", 2367 | "password123", 2368 | "fedena", 2369 | "media", 2370 | "domlint", 2371 | "js3", 2372 | "emailjs", 2373 | "movethewebforward", 2374 | "simplepager", 2375 | "Blog", 2376 | "FlashJS", 2377 | "browsertiles", 2378 | "exemplos", 2379 | "sly", 2380 | "detexify", 2381 | "multiview", 2382 | "link.js", 2383 | "system.js", 2384 | "GoogleMapsColorizr", 2385 | "mongoq", 2386 | "chain.js", 2387 | "boxbox", 2388 | "hbs", 2389 | "deuxdrop", 2390 | "batch", 2391 | "URLON", 2392 | "seed", 2393 | "unicons", 2394 | "planetarium", 2395 | "complete", 2396 | "qrx", 2397 | "escort", 2398 | "Golingo", 2399 | "demos", 2400 | "jsconfus", 2401 | "MyGeoCloud", 2402 | "awesomecomplete", 2403 | "initr", 2404 | "8ball", 2405 | "rock.js", 2406 | "cupboard", 2407 | "hypervm", 2408 | "jarTT", 2409 | "ECUI", 2410 | "battleship", 2411 | "simpledb", 2412 | "eventreactor", 2413 | "lepture.com", 2414 | "scripty2", 2415 | "backnode", 2416 | "openseadragon", 2417 | "GammaJS", 2418 | "SampleProjects", 2419 | "cardorizer", 2420 | "petrify", 2421 | "nariya", 2422 | "ROMManagerManifest", 2423 | "JSONPath", 2424 | "stickySectionHeaders", 2425 | "sleight", 2426 | "wormz", 2427 | "newsmonger", 2428 | "jQueryPlugins", 2429 | "blowfish.js", 2430 | "SenchaTouch2MVCHelloworld", 2431 | "ClassIE", 2432 | "MembershipStarterKit", 2433 | "djangode", 2434 | "vbench", 2435 | "visual6502", 2436 | "StackExchangeScripts", 2437 | "windmill", 2438 | "bricks", 2439 | "Thinktecture.Web.Http", 2440 | "nstore", 2441 | "regula", 2442 | "rePublish", 2443 | "Ux.locale.Manager", 2444 | "TiAir", 2445 | "ico", 2446 | "inherits", 2447 | "logging", 2448 | "kibo", 2449 | "chromeEyeDropper", 2450 | "CNPROG", 2451 | "thoonk.js", 2452 | "forms", 2453 | "flexnav", 2454 | "FotoRatan", 2455 | "SharpLinter", 2456 | "TiBall", 2457 | "jsref", 2458 | "couchforms", 2459 | "gowiththeflow.js", 2460 | "guides", 2461 | "todo", 2462 | "cli", 2463 | "Calloway", 2464 | "npmtop", 2465 | "quora2", 2466 | "oia", 2467 | "stackvm", 2468 | "Viper", 2469 | "jTag", 2470 | "forge", 2471 | "jCanvaScript", 2472 | "searchjs", 2473 | "sass.js", 2474 | "gitpaste", 2475 | "kettu", 2476 | "VECNIK", 2477 | "jsRayTracer", 2478 | "fatty", 2479 | "prototype.node.js", 2480 | "FireMobileSimulator", 2481 | "DOMination", 2482 | "futon4mongo", 2483 | "jush", 2484 | "pocketio", 2485 | "fileUploader", 2486 | "MobileWebAppsExamples", 2487 | "nextVal", 2488 | "morf", 2489 | "tartJS", 2490 | "miftree", 2491 | "moorte", 2492 | "Ext.ux.Exporter", 2493 | "clojuredocs", 2494 | "cssbeautify", 2495 | "firegestures", 2496 | "friar", 2497 | "CSSOM", 2498 | "ko.editables", 2499 | "dronejs", 2500 | "Playr", 2501 | "jinja.js", 2502 | "RestBugs", 2503 | "iTunes", 2504 | "githubarchive.org", 2505 | "rawkets", 2506 | "leanModal.js", 2507 | "fontmetrics.js", 2508 | "farbtastic", 2509 | "cujo", 2510 | "SimpleCQRS", 2511 | "fayer", 2512 | "jwerty", 2513 | "unicodetiles.js", 2514 | "servitude", 2515 | "JSSceneGraph", 2516 | "echo", 2517 | "JsonDiffPatch", 2518 | "MailCheckerPlus", 2519 | "l10n.js", 2520 | "structr.js", 2521 | "Niceforms", 2522 | "resistance", 2523 | "genData", 2524 | "geekslides", 2525 | "whiskers.js", 2526 | "comb", 2527 | "narwhal", 2528 | "GoogleClientLogin", 2529 | "myfavoritebeer.org", 2530 | "timekeeper", 2531 | "concrete", 2532 | "lingo", 2533 | "spaces", 2534 | "Meow", 2535 | "heso", 2536 | "Skytoop", 2537 | "upbeat", 2538 | "airport", 2539 | "SkitterSlideshow", 2540 | "muddy", 2541 | "firesass", 2542 | "WebGLU", 2543 | "FoBo", 2544 | "blingalytics", 2545 | "sudosocial", 2546 | "dombuilder", 2547 | "chromewithtwitter", 2548 | "FlodJS", 2549 | "pentabarf", 2550 | "nodeload", 2551 | "canvascropper", 2552 | "textshadow", 2553 | "shindig", 2554 | "stepy", 2555 | "ChromeToPaper", 2556 | "gritter", 2557 | "readability.py", 2558 | "haz", 2559 | "thingiview.js", 2560 | "birthdaypicker", 2561 | "hexlib", 2562 | "droppy", 2563 | "jsgamebench", 2564 | "tween", 2565 | "environ", 2566 | "authom", 2567 | "Texy.js", 2568 | "Gitview", 2569 | "magix", 2570 | "floom", 2571 | "Shibastagram", 2572 | "webui", 2573 | "speller", 2574 | "Janis", 2575 | "ySchool", 2576 | "specit", 2577 | "kwestion", 2578 | "writeCapture", 2579 | "NodeDeploy", 2580 | "SoundBankPlayer", 2581 | "com.woltlab.wcf.ckeditor", 2582 | "chmsee", 2583 | "selfish", 2584 | "NST", 2585 | "primer", 2586 | "D8", 2587 | "Training", 2588 | "jshtml", 2589 | "Backbone.amplify", 2590 | "rubyconf.tw", 2591 | "FlashCanvas", 2592 | "webglearth", 2593 | "slideNote", 2594 | "dillinger", 2595 | "JSONplus", 2596 | "liquidmetal", 2597 | "TeamCitySharp", 2598 | "usim", 2599 | "remoteagent", 2600 | "www", 2601 | "mibbu", 2602 | "smartr", 2603 | "ConsoleDummy.js", 2604 | "jqVideoBox", 2605 | "openpgpjs", 2606 | "message", 2607 | "fbdc", 2608 | "RouletteTok", 2609 | "run.js", 2610 | "async.js", 2611 | "Autolinker.js", 2612 | "btvwag.github.com", 2613 | "bibliotype", 2614 | "notestack", 2615 | "rotor.js", 2616 | "Pokki", 2617 | "Deluge.app", 2618 | "Raphael.Export", 2619 | "httpmock", 2620 | "webattle.js", 2621 | "LocalStorageDB.js", 2622 | "PlatformDemo", 2623 | "fbgl1", 2624 | "newtwitter", 2625 | "upstage", 2626 | "dropup", 2627 | "buftabs", 2628 | "ImpactStorage", 2629 | "shorty", 2630 | "drev", 2631 | "firmata", 2632 | "Convergence", 2633 | "phnx", 2634 | "escapes.js", 2635 | "node.websocket.js", 2636 | "telemote", 2637 | "spine.contacts", 2638 | "HowIsTheWeather", 2639 | "cqs", 2640 | "nog", 2641 | "infuser", 2642 | "mooMasonry", 2643 | "audionode.js", 2644 | "AjaxIM", 2645 | "CeraBox", 2646 | "lmd", 2647 | "task.js", 2648 | "zztmmo", 2649 | "teagrams", 2650 | "im.kayac.com", 2651 | "MorningStar", 2652 | "maddy", 2653 | "FileSaver.js", 2654 | "InstantSprite", 2655 | "background", 2656 | "mcms", 2657 | "slides", 2658 | "tamejs", 2659 | "polyglot", 2660 | "arclite", 2661 | "callme", 2662 | "Saito.js", 2663 | "bsonspec.org", 2664 | "LazyLoad", 2665 | "jsonparse", 2666 | "ttplus", 2667 | "MultiBox", 2668 | "ImageMenu", 2669 | "spksrc", 2670 | "cartoset", 2671 | "jasmid", 2672 | "scenario", 2673 | "RouteMap", 2674 | "ChuckNorrisException", 2675 | "MinPubSub", 2676 | "Appmator", 2677 | "jQswipe", 2678 | "customSelect", 2679 | "station", 2680 | "cartagen", 2681 | "CodeSnippet", 2682 | "HBFav", 2683 | "clay.js", 2684 | "Ext.layout.AccordionLayout", 2685 | "riab", 2686 | "nodejuice", 2687 | "react", 2688 | "mosaiqy", 2689 | "TheRenderEngine", 2690 | "TableTools", 2691 | "sideline", 2692 | "nodes", 2693 | "hoard", 2694 | "Pause", 2695 | "mediaboxAdvanced", 2696 | "echowaves", 2697 | "PinScroll", 2698 | "virgil", 2699 | "combyne.js", 2700 | "jQuery.placeHoldize", 2701 | "toocool", 2702 | "evernode", 2703 | "Editr", 2704 | "pjax", 2705 | "TabMemFree", 2706 | "LambdaJS", 2707 | "Colors", 2708 | "searchbox", 2709 | "ycChrome", 2710 | "syntaxhl", 2711 | "popupwindow", 2712 | "Fomatto", 2713 | "otherinbox", 2714 | "popcorn.kernel", 2715 | "vlandham.github.com", 2716 | "rotp", 2717 | "popcorn.sequence", 2718 | "postmile", 2719 | "Datejs", 2720 | "ForceDotBundle", 2721 | "jpgjs", 2722 | "Injector.js", 2723 | "xmlhttprequest", 2724 | "KeyboardJS", 2725 | "rdflib.js", 2726 | "pavlov", 2727 | "Thumbnails", 2728 | "zip", 2729 | "transformie", 2730 | "USTORE.js", 2731 | "errs", 2732 | "spell", 2733 | "NQUnit", 2734 | "ubiquity", 2735 | "canvasfilters", 2736 | "sequencer.js", 2737 | "svarx", 2738 | "html5bytebeat", 2739 | "vertebrae", 2740 | "JSGestureRecognizer", 2741 | "140medley", 2742 | "stream.js", 2743 | "Persistence", 2744 | "jazzrecord", 2745 | "lookups", 2746 | "openoverflow", 2747 | "NinjaScript", 2748 | "magaka", 2749 | "Helium", 2750 | "watermark.js", 2751 | "boxy", 2752 | "AutoObjectDocumentation", 2753 | "Apricot", 2754 | "grafico", 2755 | "thumbs.js", 2756 | "ccc", 2757 | "tinyalert", 2758 | "Templator", 2759 | "manor", 2760 | "Watchlist", 2761 | "pages", 2762 | "youtube5", 2763 | "TabCloud", 2764 | "lanyard", 2765 | "worklog", 2766 | "ROCA", 2767 | "alphaTab", 2768 | "parchment", 2769 | "github.commits.widget", 2770 | "armstrong.hatband", 2771 | "ValidateSimple", 2772 | "Ext.ux.TouchGridPanel", 2773 | "CanvasDisplayList", 2774 | "replicate", 2775 | "routes.js", 2776 | "tmpload", 2777 | "2011.beercamp.com", 2778 | "timesheets.js", 2779 | "mustache.github.com", 2780 | "Glog", 2781 | "lsjs", 2782 | "konamicode", 2783 | "startthedark", 2784 | "mochikit", 2785 | "FileDownLoadApp", 2786 | "myngo", 2787 | "redcouch", 2788 | "qleelulu.github.com", 2789 | "hornet", 2790 | "filed", 2791 | "tdl", 2792 | "summerTorrent", 2793 | "jefe", 2794 | "newebe", 2795 | "soundmanager2", 2796 | "feedback", 2797 | "primarywall", 2798 | "firepicker", 2799 | "color.js", 2800 | "colorwheel", 2801 | "wsbench", 2802 | "Typesetter.js", 2803 | "nodecpt.github.com", 2804 | "Sonic", 2805 | "foundation7", 2806 | "basecampjs", 2807 | "rx", 2808 | "jcoffeescript", 2809 | "follow", 2810 | "sizeit.js", 2811 | "timeframe", 2812 | "library", 2813 | "simpli5", 2814 | "playgrub", 2815 | "Ext.ux.touch.ListPullRefresh", 2816 | "node.ws.js", 2817 | "chriseppstein.github.com", 2818 | "mColorPicker", 2819 | "nodedc.github.com", 2820 | "happypointer", 2821 | "TiPopover", 2822 | "malsup.github.com", 2823 | "coconut", 2824 | "Raphael.JSON", 2825 | "peanut", 2826 | "chocolatechip", 2827 | "rosnodejs", 2828 | "thingjs", 2829 | "goggles", 2830 | "ckanjs", 2831 | "jSnake", 2832 | "html5tutorial", 2833 | "prompt", 2834 | "ChatSocks", 2835 | "hotspots", 2836 | "chickenfoot", 2837 | "cssParentSelector", 2838 | "Nodetuts", 2839 | "browser", 2840 | "enums", 2841 | "SproutCoreNative", 2842 | "dat.globe", 2843 | "ResponsiveThumbnailGallery", 2844 | "urljs", 2845 | "whoops", 2846 | "oorja", 2847 | "tipp", 2848 | "backbone.couchdb.js", 2849 | "clientcide", 2850 | "reformed", 2851 | "Turan", 2852 | "matchme", 2853 | "skywriter", 2854 | "ircloggr", 2855 | "api", 2856 | "Foundation", 2857 | "glossy", 2858 | "Codeita", 2859 | "resume.github.com", 2860 | "Ockley", 2861 | "spine.bitly", 2862 | "Absolution", 2863 | "preware", 2864 | "TheBox", 2865 | "flickrbomb", 2866 | "fastclick", 2867 | "Ajax.JSONRequest", 2868 | "kinout", 2869 | "OpaDo", 2870 | "dmWidgetGalleryPlugin", 2871 | "seventhings", 2872 | "spinners", 2873 | "userscripts", 2874 | "hydra", 2875 | "epub", 2876 | "streamie", 2877 | "OperaLink.js", 2878 | "Robohash", 2879 | "Exercise", 2880 | "pintura", 2881 | "activo", 2882 | "fredit", 2883 | "JaDE", 2884 | "nodeshot", 2885 | "MobileReadyFramework", 2886 | "fireunit", 2887 | "yui2", 2888 | "pfc", 2889 | "Ext.ux.Cover", 2890 | "tekkub.github.com", 2891 | "dr.js", 2892 | "nodecraft", 2893 | "Porter", 2894 | "jsctags", 2895 | "js.js", 2896 | "Handy.sugar", 2897 | "dom3d", 2898 | "JSTouchController", 2899 | "suncalc", 2900 | "redminetoolbar", 2901 | "amp", 2902 | "jQuery.emoji", 2903 | "opentodo", 2904 | "hellonode", 2905 | "s6", 2906 | "googlephonefix", 2907 | "MicroCache.js", 2908 | "microphysics.js", 2909 | "dizzy.js", 2910 | "NodeInterval", 2911 | "calendar", 2912 | "emoncms3", 2913 | "PNGStore", 2914 | "Incrementable", 2915 | "mapquery", 2916 | "celeri", 2917 | "customTabBar", 2918 | "TitaniORM", 2919 | "tempalias", 2920 | "TitaniumCachedImageView", 2921 | "CacheManager", 2922 | "whatajong", 2923 | "screencasts", 2924 | "nomnom", 2925 | "Atlas", 2926 | "PathFinding.js", 2927 | "RWThemeKit", 2928 | "relevancy.js", 2929 | "soldash", 2930 | "tail.io", 2931 | "rtvote", 2932 | "mobileGmap", 2933 | "eagerfeet", 2934 | "iFrame", 2935 | "nodeCQRS", 2936 | "ShortScroll", 2937 | "Knockout.Unobtrusive", 2938 | "sydjs", 2939 | "anticontainer", 2940 | "StoryNavigator", 2941 | "Presentations", 2942 | "oui", 2943 | "snoopy", 2944 | "Gatling", 2945 | "ExtJS.ux.HtmlEditor.Plugins", 2946 | "inventory", 2947 | "readability", 2948 | "Tabbed", 2949 | "browserbible", 2950 | "eyedee.me", 2951 | "kestrelweb", 2952 | "packnode", 2953 | "schema.js", 2954 | "oatv", 2955 | "Nowtable", 2956 | "Pik6", 2957 | "patternizer.js", 2958 | "mapchat", 2959 | "testling", 2960 | "jsup", 2961 | "horaa", 2962 | "floatingFixed", 2963 | "RavenGallery", 2964 | "dusterjs", 2965 | "usmap", 2966 | "lens", 2967 | "gmailr", 2968 | "jqapi", 2969 | "bespinclient", 2970 | "holler", 2971 | "retrospectiveapp", 2972 | "eson", 2973 | "simplerpg", 2974 | "SonatajQueryBundle", 2975 | "jQuery.Switchable", 2976 | "reticulatorjs", 2977 | "Eirene", 2978 | "workshop", 2979 | "twostroke", 2980 | "Tutti", 2981 | "BeerShift", 2982 | "bootstrap.js", 2983 | "pre3d", 2984 | "databases", 2985 | "SlideBlast", 2986 | "jQuery.BlackAndWhite", 2987 | "elastic", 2988 | "beeline", 2989 | "DirtyShare", 2990 | "jsContract", 2991 | "AppLayout", 2992 | "brew", 2993 | "reflect.js", 2994 | "elasticfox", 2995 | "QUnitTestRunnerPlugin", 2996 | "easey", 2997 | "moni", 2998 | "cdlre", 2999 | "timout", 3000 | "ceng202hestek", 3001 | "LayerStyles", 3002 | "spray", 3003 | "Beseda", 3004 | "socialcalc", 3005 | "metajack.im", 3006 | "jsdefer", 3007 | "xkcdfools", 3008 | "pmrpc", 3009 | "println", 3010 | "mixin", 3011 | "cast", 3012 | "dynts", 3013 | "minj", 3014 | "webglreport", 3015 | "StickyScroll", 3016 | "KitchenSinkDesktop", 3017 | "util", 3018 | "hoxy", 3019 | "bulldog", 3020 | "Aidori", 3021 | "abcsWriter", 3022 | "monstars.js", 3023 | "pjs", 3024 | "stick", 3025 | "dominiate", 3026 | "CallbackBillingPlugin", 3027 | "articles", 3028 | "recorder.js", 3029 | "webloop", 3030 | "CoderDeck", 3031 | "svgboilerplate", 3032 | "Colony", 3033 | "protobot", 3034 | "bless.js", 3035 | "notepages", 3036 | "hapi", 3037 | "Kwicks", 3038 | "glue.js", 3039 | "lonelytype", 3040 | "presentations", 3041 | "Face.js", 3042 | "plate", 3043 | "choicescript", 3044 | "broadcast", 3045 | "nlogger", 3046 | "MicroStream", 3047 | "markview", 3048 | "deck.rb", 3049 | "textus", 3050 | "xmlview", 3051 | "NodeCombo", 3052 | "drupal", 3053 | "okshadow", 3054 | "Thoth", 3055 | "KnockoutJSLive", 3056 | "jLinq", 3057 | "later", 3058 | "BazingaJqueryUiBundle", 3059 | "Tweetanium", 3060 | "traer.physics.js", 3061 | "nodestalker", 3062 | "Sumon", 3063 | "c3dl", 3064 | "springsprout", 3065 | "mio", 3066 | "panojs", 3067 | "kaffeine", 3068 | "blueprint", 3069 | "cubee.github.com", 3070 | "UglifyCSS", 3071 | "collective", 3072 | "jaml", 3073 | "Reston", 3074 | "userChromeJS", 3075 | "AjaxStack", 3076 | "frameworks", 3077 | "lsd", 3078 | "WebGitNet", 3079 | "trackyt.net", 3080 | "Riddle.js", 3081 | "rimraf", 3082 | "touchdown", 3083 | "sparklines.js", 3084 | "taskr", 3085 | "evently", 3086 | "GeoExt4", 3087 | "helicopter", 3088 | "ethercalc", 3089 | "lightnode", 3090 | "WebCraft", 3091 | "djax", 3092 | "yourworldoftext", 3093 | "djangotodos", 3094 | "omnibar", 3095 | "MGSE", 3096 | "LiveFilter", 3097 | "poshytip", 3098 | "CatchGame", 3099 | "webConsole", 3100 | "easteregg.in", 3101 | "Templify", 3102 | "nopt", 3103 | "CrossBrowse", 3104 | "strftime", 3105 | "undum", 3106 | "HistoryManager", 3107 | "RTree", 3108 | "json", 3109 | "Sheethub", 3110 | "smoothgallery", 3111 | "arc.js", 3112 | "gitmarks", 3113 | "CrowLib", 3114 | "corpus", 3115 | "cuepoint", 3116 | "smus.com", 3117 | "capty", 3118 | "drag2up", 3119 | "drupal.api.js", 3120 | "skybase", 3121 | "redis2json", 3122 | "tryhaskell", 3123 | "kibi", 3124 | "maxim.github.com", 3125 | "jianmian", 3126 | "nodewiki", 3127 | "StartupDataTrends", 3128 | "iOSEmoji", 3129 | "xlsuite", 3130 | "stackview", 3131 | "PixelPlatformer", 3132 | "sctable", 3133 | "vimari", 3134 | "craftscape", 3135 | "rediskit", 3136 | "transpochoices", 3137 | "cornerz", 3138 | "puppet", 3139 | "jarvis", 3140 | "Jerk", 3141 | "red", 3142 | "FrenchPress.sugar", 3143 | "Qanban", 3144 | "Scrumie", 3145 | "box2dnode", 3146 | "AutoSuggest", 3147 | "dejavis", 3148 | "syntux", 3149 | "tapestry5inaction", 3150 | "Booki", 3151 | "js", 3152 | "djangobase", 3153 | "CoffeeCoffee", 3154 | "string", 3155 | "Mouse.Touch", 3156 | "nodules", 3157 | "boilerplates", 3158 | "wd", 3159 | "mongolia", 3160 | "fcbkListSelection", 3161 | "couchpubtato", 3162 | "twebz", 3163 | "elmah.mvc.controller", 3164 | "uncommonjs", 3165 | "a3", 3166 | "hotdot", 3167 | "MaDe", 3168 | "dfm.github.com", 3169 | "try.redis", 3170 | "jshamcrest", 3171 | "say.js", 3172 | "Dagron", 3173 | "TitaniumSamples", 3174 | "teleport", 3175 | "suggest", 3176 | "thingler", 3177 | "Postman", 3178 | "ukijs.org", 3179 | "tilelive.js", 3180 | "wallet", 3181 | "nabe", 3182 | "filternet", 3183 | "DDDSample.Net", 3184 | "Macto", 3185 | "firerainbow", 3186 | "firelogger", 3187 | "yproject", 3188 | "mimelib", 3189 | "RealTimeMonitor", 3190 | "flensed", 3191 | "quip", 3192 | "rfc3339date.js", 3193 | "NavigationController", 3194 | "amdefine", 3195 | "lessless", 3196 | "fire", 3197 | "webshell", 3198 | "spotlight.js", 3199 | "facyBox", 3200 | "crux", 3201 | "htmlcanvas", 3202 | "RxJSKoans", 3203 | "karait", 3204 | "pastebin", 3205 | "ExpandingTextareas", 3206 | "siobench", 3207 | "hotglue2", 3208 | "Leaflet.zoomslider", 3209 | "yaselect", 3210 | "cssx", 3211 | "nTenjin", 3212 | "thunder", 3213 | "fidel", 3214 | "Anychrome.crx", 3215 | "Tweetable", 3216 | "dbemitter", 3217 | "adventure", 3218 | "jQuery.zip2addr", 3219 | "MIDI.js", 3220 | "asset", 3221 | "escrito", 3222 | "stash", 3223 | "Tasks", 3224 | "radioSwitch", 3225 | "cluster.exception", 3226 | "delorean.js", 3227 | "m2node", 3228 | "BIMsurfer", 3229 | "jsBVH", 3230 | "bangbang", 3231 | "resty", 3232 | "raphael.draggable", 3233 | "speakeasy", 3234 | "modplug", 3235 | "tinkerbin", 3236 | "chromeshack", 3237 | "kern.js", 3238 | "RaphaelViz", 3239 | "RaphaelViews", 3240 | "ineedmoretime", 3241 | "spectrum", 3242 | "TipTip", 3243 | "RacerJS", 3244 | "Events.js", 3245 | "AsciiTracer", 3246 | "LightFace", 3247 | "jmeta", 3248 | "webtreemap", 3249 | "intercom", 3250 | "livecss", 3251 | "swimlanes", 3252 | "CoolClock", 3253 | "Ext.ux.Printer", 3254 | "sight", 3255 | "cssess", 3256 | "courtside", 3257 | "thewall", 3258 | "nseg", 3259 | "tadagraph", 3260 | "Safari960", 3261 | "zAccordion", 3262 | "jsmodplayer", 3263 | "Sanitize.js", 3264 | "spine.infinite", 3265 | "everyjs.com", 3266 | "picoCSS", 3267 | "OpenPCR", 3268 | "Gitgraph", 3269 | "knvs", 3270 | "filereader.js", 3271 | "etch", 3272 | "powereditor", 3273 | "TreeBundle", 3274 | "sfxr.js", 3275 | "ClojureCoffeeScriptGame", 3276 | "endash", 3277 | "jgrowl", 3278 | "photosnear.me", 3279 | "chainvas", 3280 | "HelloPhoneGap1.0", 3281 | "totem", 3282 | "uilayer", 3283 | "jscouch", 3284 | "contour", 3285 | "chirp", 3286 | "jqm.carousel", 3287 | "grockets", 3288 | "newforms", 3289 | "thumbreel", 3290 | "jezebel", 3291 | "jsAvroPhonetic", 3292 | "QTransform", 3293 | "R.js", 3294 | "Levenshtein", 3295 | "jackdaw", 3296 | "Skull.io", 3297 | "nodechat", 3298 | "tumblrrr", 3299 | "ADsafe", 3300 | "javascript", 3301 | "scielobooks", 3302 | "yabble", 3303 | "buckets", 3304 | "michromeformats", 3305 | "tcx2nikeplus", 3306 | "jskata", 3307 | "DJS", 3308 | "canvasthumber", 3309 | "Cordova", 3310 | "jim", 3311 | "io", 3312 | "mturk", 3313 | "stitches", 3314 | "eCSStender.js", 3315 | "underscore.date", 3316 | "PrincetonStartWeek", 3317 | "caustic", 3318 | "livecomments.go", 3319 | "gtween.js", 3320 | "learn.js", 3321 | "birdhouse", 3322 | "blog.js", 3323 | "pulverizr", 3324 | "albumdy", 3325 | "fusejs", 3326 | "aboutme", 3327 | "arduinojs", 3328 | "yuno", 3329 | "audiojedit", 3330 | "Crude", 3331 | "Nyars", 3332 | "githubbub", 3333 | "JigLibJS", 3334 | "spade", 3335 | "backbone.CQRS", 3336 | "rell", 3337 | "neovigator", 3338 | "happen", 3339 | "soapjs", 3340 | "prenup", 3341 | "tquery.jsartoolkit", 3342 | "msgpack.js", 3343 | "cloudpanic", 3344 | "tweetfilter.github.com", 3345 | "Ext.LocaleManager", 3346 | "couchmail", 3347 | "multiplayer", 3348 | "rubyonrio", 3349 | "Tranquil", 3350 | "sideflow", 3351 | "mariohtml5", 3352 | "Play20StartApp", 3353 | "spark2", 3354 | "arrispwgen", 3355 | "NoSQLite", 3356 | "UITest", 3357 | "LinkifyURL", 3358 | "ICanHandlebarz.js", 3359 | "PoliteCaptcha", 3360 | "rhatter", 3361 | "WKSlider", 3362 | "iphone.css", 3363 | "jorder", 3364 | "require", 3365 | "fargo", 3366 | "mashi", 3367 | "remora", 3368 | "Selfsurfing", 3369 | "inception", 3370 | "blog", 3371 | "mapsonastick", 3372 | "banzai", 3373 | "PaperCube", 3374 | "multiplayerchess.com", 3375 | "AndroidGrocerySync", 3376 | "jQuery.birdseye", 3377 | "tweetstream", 3378 | "Skylight", 3379 | "jSquares", 3380 | "ExtJS.ux.DataDrop", 3381 | "Ext.ux.filebrowser", 3382 | "jWizard", 3383 | "ghostwriter", 3384 | "qTip1", 3385 | "meryl", 3386 | "wordcloud", 3387 | "flipBook", 3388 | "Html5MvcTemplates", 3389 | "html5expense", 3390 | "asereje", 3391 | "tutorials", 3392 | "oms", 3393 | "JxLib", 3394 | "Pano", 3395 | "mumbl", 3396 | "geocoder", 3397 | "firefly", 3398 | "noladex.org", 3399 | "cssie", 3400 | "dyluni", 3401 | "vertex.js", 3402 | "rifgraf", 3403 | "cron.js", 3404 | "delivR", 3405 | "popupjs", 3406 | "Presidents", 3407 | "wormhole", 3408 | "cfg.js", 3409 | "NavSimple", 3410 | "MooTune", 3411 | "tabswitcher", 3412 | "Flexigrid", 3413 | "logger.js", 3414 | "wobot", 3415 | "Raphaelle", 3416 | "osc4node", 3417 | "spassky", 3418 | "VLCcontrols", 3419 | "qbe", 3420 | "XcodeAPI", 3421 | "enchantMapEditor", 3422 | "vim.safariextension", 3423 | "restmvc.js", 3424 | "schampignon", 3425 | "ranger", 3426 | "jReject", 3427 | "client.express.js", 3428 | "nserv", 3429 | "graphitejs", 3430 | "docbook.github.com", 3431 | "pokernode", 3432 | "BlobBuilder.js", 3433 | "csster", 3434 | "turtlewax", 3435 | "reed", 3436 | "xregexp", 3437 | "Grid", 3438 | "nerdie", 3439 | "animatePNG", 3440 | "backbone.js", 3441 | "ofxNodejs", 3442 | "Numbas", 3443 | "codenode", 3444 | "chattrr", 3445 | "drty", 3446 | "vex", 3447 | "motionjs", 3448 | "AdaWiki2", 3449 | "bookreader", 3450 | "jQuery.html5loader", 3451 | "Responsly.js", 3452 | "hook.js", 3453 | "jchess", 3454 | "polypage", 3455 | "BrandAnalytics", 3456 | "JsBarcode", 3457 | "GDLogTool", 3458 | "stereo", 3459 | "donortrust", 3460 | "pilgrim", 3461 | "service.js", 3462 | "Dropp", 3463 | "MvcIntegrationTestFramework", 3464 | "TinyColor", 3465 | "foobar3000", 3466 | "Redbeard", 3467 | "twirlymol", 3468 | "editablegrid", 3469 | "visibly.js", 3470 | "WebAL", 3471 | "jsandbox", 3472 | "PP.js", 3473 | "Brochurno", 3474 | "melt", 3475 | "trinity", 3476 | "cygnuscms", 3477 | "dowhen", 3478 | "Facelist", 3479 | "imsto", 3480 | "bluff", 3481 | "Extremes", 3482 | "jQuery.keyboard", 3483 | "ffffallback", 3484 | "readygxp", 3485 | "WebGLCraft", 3486 | "jsts", 3487 | "TileJSON", 3488 | "jssvggraph", 3489 | "uploader", 3490 | "Formly", 3491 | "Ext.ux.touch.InfiniteCarousel", 3492 | "minimal.js", 3493 | "extjs", 3494 | "email.js", 3495 | "acewidget", 3496 | "dui", 3497 | "ModuleJS", 3498 | "ExtJS.ux.GMapPanel", 3499 | "Focus", 3500 | "olelo", 3501 | "livevalidation", 3502 | "Simples.SkypeChatStyle", 3503 | "mobilefuton", 3504 | "selleck", 3505 | "hedgehog", 3506 | "terminus", 3507 | "PaintWeb", 3508 | "groovywebconsole", 3509 | "CSSAnimationKeyframeEvents", 3510 | "lyonrb", 3511 | "smoothscroll", 3512 | "aries", 3513 | "big", 3514 | "growlhub", 3515 | "gitforked", 3516 | "parsely", 3517 | "taconite", 3518 | "VFW", 3519 | "WebWorksQNXWidgets", 3520 | "logan", 3521 | "raindrop", 3522 | ]; 3523 | --------------------------------------------------------------------------------