├── .eslintignore
├── .github
├── crush-pics
│ └── config.json
└── workflows
│ ├── auto-merge.yml
│ ├── codeql-analysis.yml
│ ├── node.js-solution.yml
│ ├── node.js.yml
│ ├── optimize-images.yml
│ └── ts-nightly-tests.yml
├── .gitignore
├── .vscode
└── settings.json
├── COURSE_FILES
├── 01-yarn-workspaces
│ └── types
│ │ ├── package.json
│ │ ├── src
│ │ ├── index.ts
│ │ ├── type-guards.ts
│ │ └── types.ts
│ │ ├── tests
│ │ ├── tsconfig.json
│ │ └── type-guard.test.ts
│ │ └── tsconfig.json
├── 02-second-package
│ └── utils
│ │ ├── package.json
│ │ ├── src
│ │ ├── api.ts
│ │ ├── date.ts
│ │ ├── deferred.ts
│ │ ├── error.ts
│ │ ├── http-error.ts
│ │ ├── index.ts
│ │ └── networking.ts
│ │ ├── tests
│ │ ├── date.test.ts
│ │ ├── deferred.test.ts
│ │ ├── error.test.ts
│ │ ├── http-error.test.ts
│ │ └── tsconfig.json
│ │ └── tsconfig.json
├── 04-linting
│ └── .eslintrc
├── 05-lerna
│ └── lerna.json
├── 06-scripty
│ └── scripts
│ │ ├── packages
│ │ ├── build.sh
│ │ ├── lint.sh
│ │ └── test.sh
│ │ └── workspace
│ │ ├── build.sh
│ │ ├── clean.sh
│ │ ├── lint.sh
│ │ └── test.sh
├── 07-commitlint-and-changelogs
│ └── commitlint.config.js
├── 09-internal-dependents
│ ├── data
│ │ ├── .babelrc
│ │ ├── .eslintrc
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── channels.ts
│ │ │ ├── index.ts
│ │ │ ├── messages.ts
│ │ │ └── teams.ts
│ │ ├── tests
│ │ │ ├── channel.test.ts
│ │ │ ├── teams.test.ts
│ │ │ └── tsconfig.json
│ │ └── tsconfig.json
│ └── ui
│ │ ├── .babelrc
│ │ ├── .eslintrc
│ │ ├── API_EXAMPLES.http
│ │ ├── assets
│ │ ├── app.css
│ │ └── img
│ │ │ ├── angry-cat.jpg
│ │ │ ├── avengers.jpg
│ │ │ ├── boss.jpg
│ │ │ ├── cat.jpg
│ │ │ ├── clippy.png
│ │ │ ├── colonel-meow.jpg
│ │ │ ├── desk_flip.jpg
│ │ │ ├── dilbert.jpg
│ │ │ ├── drstrange.jpg
│ │ │ ├── ironman.jpg
│ │ │ ├── jquery.png
│ │ │ ├── js.png
│ │ │ ├── linkedin.png
│ │ │ ├── lisa.jpeg
│ │ │ ├── maru.jpg
│ │ │ ├── microsoft.png
│ │ │ ├── mike.jpeg
│ │ │ ├── node.png
│ │ │ ├── office97.png
│ │ │ ├── thor.jpg
│ │ │ └── ts.png
│ │ ├── db.json
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── server
│ │ ├── api-server.js
│ │ └── server.js
│ │ ├── src
│ │ ├── App.tsx
│ │ ├── components
│ │ │ ├── Channel.tsx
│ │ │ ├── Channel
│ │ │ │ ├── Footer.tsx
│ │ │ │ ├── Header.tsx
│ │ │ │ └── Message.tsx
│ │ │ ├── Loading.tsx
│ │ │ ├── SelectedChannel.tsx
│ │ │ ├── SelectedTeam.tsx
│ │ │ ├── Team.tsx
│ │ │ ├── TeamSelector.tsx
│ │ │ ├── TeamSelector
│ │ │ │ └── TeamLink.tsx
│ │ │ ├── TeamSidebar.tsx
│ │ │ └── TeamSidebar
│ │ │ │ └── ChannelLink.tsx
│ │ └── index.ts
│ │ ├── tests
│ │ ├── components
│ │ │ ├── Channel.test.tsx
│ │ │ ├── ChannelFooter.test.tsx
│ │ │ ├── ChannelHeader.test.tsx
│ │ │ ├── ChannelMessage.test.tsx
│ │ │ ├── TeamSelector.test.tsx
│ │ │ ├── TeamSidebar.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ ├── Channel.test.tsx.snap
│ │ │ │ ├── ChannelFooter.test.tsx.snap
│ │ │ │ ├── ChannelHeader.test.tsx.snap
│ │ │ │ ├── ChannelMessage.test.tsx.snap
│ │ │ │ ├── TeamSelector.test.tsx.snap
│ │ │ │ └── TeamSidebar.test.tsx.snap
│ │ └── tsconfig.json
│ │ └── tsconfig.json
├── 11-api-report
│ ├── api-extractor-base.json
│ ├── packages
│ │ └── PKG
│ │ │ └── api-extractor.json
│ └── scripts
│ │ └── packages
│ │ └── api-report.sh
└── 12-api-docs
│ └── scripts
│ └── workspace
│ └── api-docs.sh
├── LICENSE
├── README.md
├── notes
├── 00-intro.md
├── 01-yarn-workspaces.md
├── 02-composite-project.md
├── 03-tests.md
├── 04-linting.md
├── 05-lerna.md
├── 06-scripty.md
├── 07-changelogs.md
├── 08-publishing-and-versioning.md
├── 09-internal-dependencies.md
├── 10-independent-versioning.md
├── 11-api-report.md
├── 12-api-docs.md
├── README.md
└── img
│ └── dherman-monorepo-1.png
├── package.json
├── tsconfig.json
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | packages/*/tests/*.ts
--------------------------------------------------------------------------------
/.github/crush-pics/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compression_mode": "balanced",
3 | "compression_level": 85,
4 | "strip_tags": false
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: Auto Merge
2 |
3 | on:
4 | push: {} # update PR when base branch is updated
5 | status: {} # try to merge when other checks are completed
6 | pull_request_review: # try to merge after review
7 | types:
8 | - submitted
9 | - edited
10 | - dismissed
11 | pull_request: # try to merge if labels have changed (white/black list)
12 | types:
13 | - labeled
14 | - unlabeled
15 |
16 | jobs:
17 | # thats's all. single step is needed - if PR is mergeable according to
18 | # branch protection rules it will be merged automatically
19 | mergepal:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v1
23 | - uses: maxkomarychev/merge-pal-action@v0.5.1
24 | with:
25 | token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 21 * * 0'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['javascript']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v2
34 | with:
35 | # We must fetch at least the immediate parents so that if this is
36 | # a pull request then we can checkout the head.
37 | fetch-depth: 2
38 |
39 | # If this run was triggered by a pull request event, then checkout
40 | # the head of the pull request instead of the merge commit.
41 | - run: git checkout HEAD^2
42 | if: ${{ github.event_name == 'pull_request' }}
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.github/workflows/node.js-solution.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI (solution)
5 |
6 | on:
7 | schedule:
8 | # * is a special character in YAML so you have to quote this string
9 | - cron: '20 * * * *'
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [10.x, 12.x, 14.x]
19 |
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v2
23 | with:
24 | ref: solution
25 | - name: Cache Setup
26 | uses: actions/cache@v2
27 | with:
28 | path: |
29 | ~/cache
30 | ~/.dts/
31 | !~/cache/exclude
32 | **/node_modules
33 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
34 |
35 | - name: Use (Volta) Node.js ${{ matrix.node-version }}
36 | uses: volta-cli/action@v1
37 | with:
38 | node-version: ${{ matrix.node-version }}
39 | - name: Install dependencies
40 | run: yarn
41 | - name: Build
42 | run: yarn build
43 | - name: Build Docs
44 | run: yarn api-report && yarn api-docs
45 | - name: Run tests
46 | run: yarn test
47 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [10.x, 12.x, 14.x]
20 |
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v2
24 | - name: Cache Setup
25 | uses: actions/cache@v2
26 | with:
27 | path: |
28 | ~/cache
29 | ~/.dts/
30 | !~/cache/exclude
31 | **/node_modules
32 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
33 |
34 | - name: Use (Volta) Node.js ${{ matrix.node-version }}
35 | uses: volta-cli/action@v1
36 | with:
37 | node-version: ${{ matrix.node-version }}
38 | - name: Install dependencies
39 | run: yarn
40 | - name: Build
41 | run: yarn build
42 | - name: Build Docs
43 | run: yarn api-report && yarn api-docs
44 | - name: Run tests
45 | run: yarn test
46 |
--------------------------------------------------------------------------------
/.github/workflows/optimize-images.yml:
--------------------------------------------------------------------------------
1 | name: Crush images
2 | on:
3 | pull_request:
4 | branches:
5 | - master
6 | paths:
7 | - '**.jpg'
8 | - '**.jpeg'
9 | - '**.png'
10 | - '**.gif'
11 | jobs:
12 | crush:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | - name: Crush images
18 | uses: crush-pics/crush-pics-github-action@master
19 | with:
20 | repo-token: ${{ secrets.GITHUB_TOKEN }}
21 | api-key: ${{ secrets.CRUSH_API_KEY }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/ts-nightly-tests.yml:
--------------------------------------------------------------------------------
1 |
2 | name: TypeScript@Next tests (solution)
3 |
4 | on:
5 | schedule:
6 | # * is a special character in YAML so you have to quote this string
7 | - cron: '20 * * * *'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [14.x]
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v2
20 | with:
21 | ref: solution
22 | - name: Cache Setup
23 | uses: actions/cache@v2
24 | with:
25 | path: |
26 | ~/cache
27 | ~/.dts/
28 | !~/cache/exclude
29 | **/node_modules
30 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
31 |
32 | - name: Use (Volta) Node.js ${{ matrix.node-version }}
33 | uses: volta-cli/action@v1
34 | with:
35 | node-version: ${{ matrix.node-version }}
36 | - name: Install dependencies
37 | run: yarn
38 | - name: Install latest TypeScript nightly
39 | run: yarn add -WD typescript@next
40 | - name: Build
41 | run: yarn build
42 | - name: Run tests
43 | run: yarn test
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 | **/temp
118 | **/temp-*
119 | _config.yml
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "api-extractor*.json": "jsonc"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shlack/types",
3 | "version": "0.0.1",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "scripts": {
10 | "build": "tsc -b ."
11 | },
12 | "devDependencies": {
13 | "typescript": "^4.0.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @packageDocumentation
3 | *
4 | *
5 | *
6 | *
Why does this library exist?
7 | *
8 | * `@shlack/types` is a package containing broadly-useful
9 | * types and type guards for our demo slack app. This is part of Mike's
10 | * JS & TS Monorepos Course which you can learn more about by visiting
11 | *
12 | * the repo on GitHub
13 | *
14 | *
15 | * If you want to watch a recorded video of this course, look for it
16 | * on FrontEnd Masters
17 | *
18 | *
19 | * @remarks
20 | * All interfaces are prefixed with `I`
21 | *
22 | * @packageDocumentation
23 | */
24 | export { isChannel, isMessage, isTeam, isTypedArray } from "./type-guards";
25 | export { IChannel, IMessage, ITeam, IUser } from "./types";
26 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/src/type-guards.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
3 | import { IChannel, IMessage, ITeam } from "./types";
4 |
5 | /**
6 | * Check whether a given value is an array where
7 | * each member is of a specified type
8 | *
9 | * @param arr - array to check
10 | * @param check - type guard to use when evaluating each item
11 | * @public
12 | */
13 | export function isTypedArray(
14 | arr: unknown,
15 | check: (x: any) => x is T
16 | ): arr is T[] {
17 | if (!Array.isArray(arr)) return false;
18 | const mismatch = arr.filter((item) => !check(item));
19 | if (mismatch.length > 0) return false;
20 | return true;
21 | }
22 |
23 | /**
24 | * Check whether a given value is an {@link @shlack/types#ITeam}
25 | * @param arg - value to check
26 | * @beta
27 | *
28 | * @example
29 | * Here's an example of how to use this guard
30 | * ```ts
31 | * const team = { id: 'li', name: 'LinkedIn' };
32 | * isTeam(team); // true
33 | * ```
34 | */
35 | export function isTeam(arg: any): arg is ITeam {
36 | return (
37 | typeof arg.name === "string" &&
38 | typeof arg.id === "string" &&
39 | Array.isArray(arg.channels)
40 | );
41 | }
42 |
43 | /**
44 | * Check whether a given value is an {@link @shlack/types#IChannel}
45 | * @param arg - value to check
46 | * @beta
47 | */
48 | export function isChannel(arg: any): arg is IChannel {
49 | return (
50 | typeof arg.id === "string" &&
51 | typeof arg.teamId === "string" &&
52 | typeof arg.description === "string" &&
53 | typeof arg.name === "string"
54 | );
55 | }
56 |
57 | /**
58 | * Check whether a given value is an {@link @shlack/types#IMessage}
59 | * @param arg - value to check
60 | * @beta
61 | */
62 | export function isMessage(arg: any): arg is IMessage {
63 | return (
64 | typeof arg.teamId === "string" &&
65 | typeof arg.channelId === "string" &&
66 | typeof arg.userId === "string" &&
67 | typeof arg.body === "string"
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/src/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A user participating in a chat
3 | * @public
4 | */
5 | export interface IUser {
6 | id: number;
7 | username: string;
8 | name: string;
9 | iconUrl: string;
10 | }
11 |
12 | /**
13 | * A chat message
14 | * @public
15 | */
16 | export interface IMessage {
17 | id: number;
18 | teamId: string;
19 | channelId: string;
20 | userId: string;
21 | createdAt: string;
22 | user: IUser;
23 | body: string;
24 | }
25 |
26 | /**
27 | * A team, containing one or more chat channels
28 | * @public
29 | *
30 | * @remarks
31 | * The {@link @shlack/types#isTeam} type guard may be used to
32 | * detect whether a value conforms to this interface
33 | */
34 | export interface ITeam {
35 | iconUrl: string;
36 | name: string;
37 | id: string;
38 | channels: IChannel[];
39 | }
40 |
41 | /**
42 | * A chat channel, containing many chat messages
43 | * @public
44 | */
45 | export interface IChannel {
46 | teamId: string;
47 | name: string;
48 | description: string;
49 | id: string;
50 | messages: IMessage[];
51 | }
52 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "references": [{ "path": "..", "name": "@shlack/types" }],
3 | "compilerOptions": {
4 | "lib": ["DOM", "ES2018"],
5 | "types": ["jest"],
6 | "baseUrl": "..",
7 | "noEmit": true
8 | },
9 | "include": ["."]
10 | }
11 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/tests/type-guard.test.ts:
--------------------------------------------------------------------------------
1 | import { isChannel, isMessage, isTeam, isTypedArray } from "../src";
2 |
3 | describe("isChannel() tests", function () {
4 | test("valid channel", () => {
5 | expect(
6 | isChannel({
7 | id: "123",
8 | teamId: "12gh",
9 | description: "channel description",
10 | iconUrl: "",
11 | messages: [],
12 | name: "general",
13 | })
14 | ).toEqual(true);
15 | });
16 | test("invalid channel", () => {
17 | expect(
18 | isChannel({
19 | description: "channel description",
20 | messages: [],
21 | name: "general",
22 | })
23 | ).toEqual(false);
24 | });
25 | });
26 |
27 | describe("isMessage() tests", function () {
28 | test("valid message", () => {
29 | expect(
30 | isMessage({
31 | id: 131,
32 | teamId: "12gh",
33 | channelId: "12gh",
34 | userId: "12gh",
35 | body: "hello, world",
36 | })
37 | ).toEqual(true);
38 | });
39 | test("invalid message", () => {
40 | expect(
41 | isMessage({
42 | description: "message description",
43 | messages: [],
44 | name: "general",
45 | })
46 | ).toEqual(false);
47 | });
48 | });
49 |
50 | describe("isTeam() tests", function () {
51 | test("valid message", () => {
52 | expect(
53 | isTeam({
54 | name: "12gh",
55 | id: "12gh",
56 | channels: [],
57 | })
58 | ).toEqual(true);
59 | });
60 | test("invalid message", () => {
61 | expect(
62 | isTeam({
63 | name: "12gh",
64 | id: "12gh",
65 | })
66 | ).toEqual(false);
67 | });
68 | });
69 |
70 | describe("isTypedArray() tests", function () {
71 | test("non-array", () => {
72 | expect(
73 | // @ts-expect-error
74 | isTypedArray(null, () => true)
75 | ).toEqual(false);
76 | });
77 | test("empty array", () => {
78 | expect(isTypedArray([], (x: any): x is any => true)).toEqual(true);
79 | });
80 | test("homogenous array [1, 2, 3]", () => {
81 | expect(
82 | isTypedArray([1, 2, 3], (x): x is number => typeof x === "number")
83 | ).toEqual(true);
84 | });
85 | test("mixed array [1, 'a', 3]", () => {
86 | expect(
87 | isTypedArray(
88 | [1, "a", 3],
89 | (x): x is number => ["number"].indexOf(typeof x) >= 0
90 | )
91 | ).toEqual(false);
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/COURSE_FILES/01-yarn-workspaces/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "types": [],
5 | "sourceMap": true,
6 | "target": "ES2018",
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noImplicitReturns": true,
11 | "declaration": true,
12 | "outDir": "dist",
13 | "rootDir": "src"
14 | },
15 | "include": ["src"]
16 | }
17 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shlack/utils",
3 | "version": "0.0.1",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/api.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import Deferred from "./deferred";
3 |
4 | /**
5 | *
6 | * @param getData
7 | * @param options
8 | */
9 | export function useAsyncDataEffect(
10 | getData: () => Promise,
11 | options: {
12 | stateName: string;
13 | otherStatesToMonitor?: unknown[];
14 | setter: (arg: T) => void;
15 | }
16 | ): void {
17 | let cancelled = false;
18 | const { setter, stateName } = options;
19 | useEffect(() => {
20 | const d = new Deferred();
21 |
22 | getData()
23 | .then((jsonData) => {
24 | if (cancelled) return;
25 | else d.resolve(jsonData);
26 | })
27 | .catch(d.reject);
28 |
29 | d.promise
30 | .then((data) => {
31 | if (!cancelled) {
32 | console.info(
33 | "%c Updating state: " + stateName,
34 | "background: green; color: white; display: block;"
35 | );
36 | setter(data);
37 | }
38 | })
39 | .catch(console.error);
40 | return () => {
41 | cancelled = true;
42 | };
43 | }, [...(options.otherStatesToMonitor || []), stateName]);
44 | }
45 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/date.ts:
--------------------------------------------------------------------------------
1 | import { format } from "date-fns";
2 |
3 | /**
4 | * Format a timestamp as a string
5 | * @param date - the date value to format
6 | * @public
7 | */
8 | export function formatTimestamp(date: Date): string {
9 | return format(date, "MMM dd, yyyy HH:MM:SS a");
10 | }
11 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/deferred.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @public
3 | */
4 | export type ResolveHandler = (value: T | PromiseLike) => void;
5 | /**
6 | * @public
7 | */
8 | export type RejectHandler = (reason: unknown) => void;
9 |
10 | /**
11 | * An "inverted" promise, that can be resolved
12 | * from the outside
13 | *
14 | * @public
15 | */
16 | class Deferred {
17 | #_promise: Promise;
18 | #_resolve!: ResolveHandler;
19 | #_reject!: RejectHandler;
20 | constructor() {
21 | this.#_promise = new Promise((resolve, reject) => {
22 | this.#_resolve = resolve;
23 | this.#_reject = reject;
24 | });
25 | }
26 | get promise(): Promise {
27 | return this.#_promise;
28 | }
29 | get resolve(): ResolveHandler {
30 | return this.#_resolve;
31 | }
32 | get reject(): RejectHandler {
33 | return this.#_reject;
34 | }
35 | }
36 | export default Deferred;
37 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/error.ts:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /**
4 | * Stringify an Error instance
5 | * @param err - The error to stringify
6 | */
7 | function stringifyErrorValue(err: Error): string {
8 | return `${err.name.toUpperCase()}: ${err.message}
9 | ${err.stack || '(no stack trace information)'}`;
10 | }
11 |
12 | /**
13 | * Stringify a thrown value
14 | *
15 | * @param errorDescription
16 | * @param err
17 | *
18 | */
19 | export function stringifyError(errorDescription: string, err: unknown): string {
20 | return `${errorDescription}\n${
21 | err instanceof Error
22 | ? stringifyErrorValue(err)
23 | : err
24 | // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
25 | ? '' + err
26 | : '(missing error information)'
27 | }`;
28 | }
29 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/http-error.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * High-level outcome of an HTTP status code
3 | * @public
4 | */
5 | export enum HTTPErrorKind {
6 | Information = 100,
7 | Success = 200,
8 | Redirect = 300,
9 | Client = 400,
10 | Server = 500,
11 | }
12 |
13 | /**
14 | * Determine the high-level outcome of a HTTP status code
15 | *
16 | * @param status - http status code
17 | * @see HTTPErrorKind
18 | * @public
19 | */
20 | function determineKind(status: number): HTTPErrorKind {
21 | if (status >= 100 && status < 200) return HTTPErrorKind.Information;
22 | else if (status < 300) return HTTPErrorKind.Success;
23 | else if (status < 400) return HTTPErrorKind.Redirect;
24 | else if (status < 500) return HTTPErrorKind.Client;
25 | else if (status < 600) return HTTPErrorKind.Server;
26 | else throw new Error(`Unknown HTTP status code ${status}`);
27 | }
28 |
29 | /**
30 | * An error that's associated with a HTTP response status code
31 | * @public
32 | */
33 | export default class HTTPError extends Error {
34 | kind: HTTPErrorKind;
35 |
36 | constructor(info: { statusText: string; status: number }, message: string) {
37 | super(
38 | `HTTPError [status: ${info.statusText} (${info.status})]\n${message}`
39 | );
40 | this.kind = determineKind(info.status);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./api";
2 | export * from "./date";
3 | export { default as Deferred, RejectHandler, ResolveHandler } from "./deferred";
4 | export * from "./networking";
5 | export * from "./error";
6 | export { default as HTTPError, HTTPErrorKind } from "./http-error";
7 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/src/networking.ts:
--------------------------------------------------------------------------------
1 | import { stringifyError } from "./error";
2 | import HTTPError from "./http-error";
3 |
4 | /**
5 | * Make a GET request, and decode the response body as JSON
6 | *
7 | * @param input - request info
8 | * @param init - request options
9 | * @internal
10 | */
11 | async function getJSON(input: RequestInfo, init?: RequestInit) {
12 | try {
13 | const response = await fetch(input, init);
14 | const responseJSON = await response.json();
15 | return { response, json: responseJSON };
16 | } catch (err) {
17 | throw new Error(
18 | stringifyError(
19 | `Networking/getJSON: An error was encountered while fetching ${JSON.stringify(
20 | input
21 | )}`,
22 | err
23 | )
24 | );
25 | }
26 | }
27 |
28 | /**
29 | * Make a same-origin GET request to the API
30 | *
31 | * @param path - API path
32 | * @param init - fetch options
33 | * @public
34 | */
35 | export async function apiCall(
36 | path: string,
37 | init?: RequestInit
38 | ): Promise {
39 | let response;
40 | let json;
41 | try {
42 | const jsonRespInfo = await getJSON(`/api/${path}`, init);
43 | response = jsonRespInfo.response;
44 | json = jsonRespInfo.json;
45 | } catch (err) {
46 | if (err instanceof HTTPError) throw err;
47 | throw new Error(
48 | stringifyError(
49 | `Networking/apiCall: An error was encountered while making api call to ${path}`,
50 | err
51 | )
52 | );
53 | }
54 | if (!response.ok)
55 | throw new HTTPError(response, "Problem while making API call");
56 | return json;
57 | }
58 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tests/date.test.ts:
--------------------------------------------------------------------------------
1 | import { formatTimestamp } from "@shlack/utils";
2 |
3 | describe("formatTimestamp() tests", function () {
4 | let x = 4;
5 | test("01-01-2020", () => {
6 | expect(formatTimestamp(new Date("01-01-2020"))).toBe(
7 | "Jan 01, 2020 00:01:00 AM"
8 | );
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tests/deferred.test.ts:
--------------------------------------------------------------------------------
1 | import { Deferred } from "@shlack/utils";
2 |
3 | describe("Deferred tests", function () {
4 | test("Constructor does not error", () => {
5 | const d = new Deferred();
6 | expect(d).toBeTruthy();
7 | expect(typeof d.resolve).toBe("function");
8 | expect(typeof d.reject).toBe("function");
9 | expect(d.promise).toBeInstanceOf(Promise);
10 | });
11 | test("promise resolves when Deferred#resolve() is called", async () => {
12 | expect.assertions(2);
13 | const d = new Deferred();
14 | setTimeout(() => {
15 | d.resolve(42);
16 | expect(true).toBe(true);
17 | }, 30);
18 | const val = await d.promise;
19 | expect(val).toBe(42);
20 | });
21 | test("promise rejects when Deferred#reject() is called", async () => {
22 | expect.assertions(3);
23 | const d = new Deferred();
24 | setTimeout(() => {
25 | expect(true).toBe(true);
26 | d.reject(-42);
27 | }, 30);
28 | try {
29 | await d.promise;
30 | expect(true).toBe(true); // should never reach this line
31 | } catch (e) {
32 | expect(e).toBe(-42);
33 | } finally {
34 | expect(true).toBe(true);
35 | }
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tests/error.test.ts:
--------------------------------------------------------------------------------
1 | import { HTTPError, stringifyError } from "@shlack/utils";
2 |
3 | describe("Error tests", function () {
4 | test("stringifyError", () => {
5 | const e = new HTTPError(
6 | { status: 404, statusText: "Not Found" },
7 | "Mock error for testing"
8 | );
9 | const result = stringifyError("mock error for tests", e);
10 | expect(result.split("at").length).toBeGreaterThanOrEqual(5);
11 | expect(result).toContain("mock error for tests");
12 | expect(result).toContain("HTTPError [status: Not Found (404)]");
13 | expect(result).toContain("Mock error for testing");
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tests/http-error.test.ts:
--------------------------------------------------------------------------------
1 | import { HTTPError, HTTPErrorKind } from "@shlack/utils";
2 |
3 | describe("HTTPError tests", function () {
4 | test("creation", () => {
5 | const e = new HTTPError(
6 | { status: 404, statusText: "Not Found" },
7 | "Mock error for testing"
8 | );
9 | expect(e.kind).toBe(HTTPErrorKind.Client);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["DOM", "ES2018"],
4 | "types": ["jest"],
5 | "baseUrl": "..",
6 | "noEmit": true
7 | },
8 | "include": ["."]
9 | }
10 |
--------------------------------------------------------------------------------
/COURSE_FILES/02-second-package/utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "types": [],
5 | "sourceMap": true,
6 | "target": "ES2018",
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noImplicitReturns": true,
11 | "declaration": true
12 | },
13 | "include": ["src"]
14 | }
15 |
--------------------------------------------------------------------------------
/COURSE_FILES/04-linting/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es2021": true
4 | },
5 | "extends": [
6 | "eslint:recommended",
7 | "plugin:@typescript-eslint/recommended",
8 | "plugin:@typescript-eslint/recommended-requiring-type-checking"
9 | ],
10 | "parser": "@typescript-eslint/parser",
11 | "parserOptions": {
12 | "ecmaVersion": 12
13 | },
14 | "plugins": ["@typescript-eslint"],
15 | "rules": {
16 | "prefer-const": "error",
17 | "@typescript-eslint/no-unsafe-member-access": "off",
18 | "@typescript-eslint/no-unsafe-call": "off",
19 | "@typescript-eslint/no-unsafe-assignment": "off"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/COURSE_FILES/05-lerna/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "npmClient": "yarn",
4 | "version": "0.0.1",
5 | "useWorkspaces": true,
6 | "nohoist": ["parcel-bundler"]
7 | }
8 |
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/packages/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 📦 Building $(pwd) ━━━━━━━━━━━━━━━━━━━"
3 | yarn tsc -b
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/packages/lint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🕵️♀️ LINT: eslint src --ext ts,js,tsx,jsx ━━━━━━━"
3 | yarn eslint src --ext ts,js,tsx,jsx
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/packages/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🎯 TEST: $(pwd) ━━━━━━━━━━━━━━━━━━━"
3 | yarn jest
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/workspace/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 📦 Building Workspace ━━━━━━━━━━━━━━━━━━━"
3 | yarn tsc -b packages
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/workspace/clean.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🧹 CLEAN ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
3 | yarn lerna run clean --concurrency 4
4 |
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/workspace/lint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🕵️♀️ LINT: eslint src --ext ts,js,tsx,jsx ━━━━━━━"
3 | yarn lerna run lint --stream --concurrency 1
--------------------------------------------------------------------------------
/COURSE_FILES/06-scripty/scripts/workspace/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🎯 TEST: $(pwd) ━━━━━━━━━━━━━━━━━━━"
3 | yarn lerna run test --stream
--------------------------------------------------------------------------------
/COURSE_FILES/07-commitlint-and-changelogs/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ["@commitlint/config-lerna-scopes"] };
2 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.babelrc"
3 | }
4 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc",
3 | "parserOptions": {
4 | "project": "tsconfig.json"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shlack/data",
3 | "version": "0.0.1",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "scripts": {
10 | "lint": "scripty",
11 | "clean": "rimraf dist *.tsbuildinfo",
12 | "build": "scripty",
13 | "test": "scripty"
14 | },
15 | "devDependencies": {
16 | "@babel/preset-env": "^7.12.0",
17 | "@babel/preset-typescript": "^7.12.0",
18 | "@types/date-fns": "^2.6.0",
19 | "@types/react": "^16.9.52",
20 | "typescript": "^4.0.3"
21 | },
22 | "scripty": {
23 | "path": "../../scripts/packages"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/src/channels.ts:
--------------------------------------------------------------------------------
1 | import { IChannel, isChannel } from "@shlack/types";
2 | import { apiCall } from "@shlack/utils";
3 |
4 | const cachedChannelRecords: Record> = {};
5 |
6 | export async function getChannelById(id: string): Promise {
7 | let cached = cachedChannelRecords[id];
8 | if (typeof cached !== "undefined") return await cached;
9 | cached = cachedChannelRecords[id] = apiCall(`Channels/${id}`).then(
10 | (rawData: unknown) => {
11 | if (isChannel(rawData)) return rawData;
12 | throw new Error(
13 | `Unexpected value for channel\n${JSON.stringify(rawData)}`
14 | );
15 | }
16 | );
17 |
18 | return await cached;
19 | }
20 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./channels";
2 | export * from "./messages";
3 | export * from "./teams";
4 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/src/messages.ts:
--------------------------------------------------------------------------------
1 | import { isMessage, isTypedArray, IMessage } from "@shlack/types";
2 | import { apiCall } from "@shlack/utils";
3 |
4 | const cachedMessageRecordArrays: Record> = {};
5 |
6 | export async function getChannelMessages(
7 | teamId: string,
8 | channelId: string
9 | ): Promise {
10 | let cached = cachedMessageRecordArrays[channelId];
11 | if (typeof cached === "undefined")
12 | cached = cachedMessageRecordArrays[channelId] = apiCall(
13 | `teams/${teamId}/channels/${channelId}/messages`
14 | ).then((rawData) => {
15 | debugger;
16 | if (isTypedArray(rawData, isMessage)) {
17 | return rawData;
18 | } else
19 | throw new Error(
20 | `Unexpected value for message array\n${JSON.stringify(rawData)}`
21 | );
22 | });
23 | return await cached;
24 | }
25 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/src/teams.ts:
--------------------------------------------------------------------------------
1 | import { apiCall } from "@shlack/utils";
2 | import { isTeam, isTypedArray, ITeam } from "@shlack/types";
3 |
4 | let cachedAllTeamsList: Promise;
5 | export async function getAllTeams(): Promise {
6 | if (typeof cachedAllTeamsList === "undefined")
7 | cachedAllTeamsList = apiCall("teams").then((rawData: unknown) => {
8 | if (isTypedArray(rawData, isTeam)) return rawData;
9 | throw new Error(
10 | `Unexpected value for teams array\n${JSON.stringify(rawData)}`
11 | );
12 | });
13 |
14 | return await cachedAllTeamsList;
15 | }
16 |
17 | const cachedTeamRecords: Record> = {};
18 |
19 | export async function getTeamById(id: string): Promise {
20 | let cached = cachedTeamRecords[id];
21 | if (typeof cached === "undefined")
22 | cached = cachedTeamRecords[id] = apiCall(`teams/${id}`).then(
23 | (rawData: unknown) => {
24 | if (isTeam(rawData)) return rawData;
25 | throw new Error(
26 | `Unexpected value for team\n${JSON.stringify(rawData)}`
27 | );
28 | }
29 | );
30 | return await cached;
31 | }
32 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/tests/channel.test.ts:
--------------------------------------------------------------------------------
1 | import * as utils from "@shlack/utils";
2 | import { getChannelById } from "@shlack/data";
3 |
4 | jest.mock("@shlack/utils");
5 | const mockedApiCall = (utils.apiCall as unknown) as jest.MockedFunction<
6 | typeof utils["apiCall"]
7 | >;
8 | mockedApiCall.mockResolvedValue({
9 | id: "recruiting",
10 | name: "recruiting",
11 | description: "The Next Generation Of Recruiting. Find top talents today!",
12 | teamId: "linkedin",
13 | });
14 |
15 | describe("getChannelById() tests", function () {
16 | test("fetching a single team", async (done) => {
17 | expect(mockedApiCall.mock.calls.length).toBe(0);
18 | const pr = getChannelById("14");
19 | let resolvedVal: unknown;
20 | pr.then((val) => {
21 | resolvedVal = val;
22 | });
23 | expect(pr).toBeInstanceOf(Promise);
24 | expect(resolvedVal).toBeUndefined();
25 | expect(mockedApiCall.mock.calls.length).toBe(1);
26 |
27 | await pr;
28 | expect(resolvedVal).toMatchObject({
29 | id: "recruiting",
30 | name: "recruiting",
31 | description: "The Next Generation Of Recruiting. Find top talents today!",
32 | teamId: "linkedin",
33 | });
34 | done();
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/tests/teams.test.ts:
--------------------------------------------------------------------------------
1 | import * as utils from "@shlack/utils";
2 | import { getAllTeams } from "@shlack/data";
3 |
4 | jest.mock("@shlack/utils");
5 | const mockedApiCall = (utils.apiCall as unknown) as jest.MockedFunction<
6 | typeof utils["apiCall"]
7 | >;
8 | mockedApiCall.mockImplementation(async (path, _init) => {
9 | if (path === "teams") {
10 | return Promise.resolve([
11 | {
12 | id: "linkedin",
13 | name: "LinkedIn",
14 | order: 2,
15 | iconUrl: "/assets/img/linkedin.png",
16 | channels: [],
17 | },
18 | {
19 | id: "ms",
20 | name: "Microsoft",
21 | order: 3,
22 | iconUrl: "/assets/img/microsoft.png",
23 | channels: [],
24 | },
25 | {
26 | id: "avengers",
27 | name: "Avengers",
28 | order: 4,
29 | iconUrl: "/assets/img/avengers.jpg",
30 | channels: [],
31 | },
32 | ]);
33 | } else throw new Error("not implemented");
34 | });
35 |
36 | describe("getAllTeams() tests", function () {
37 | test("fetching all teams", async (done) => {
38 | expect(mockedApiCall.mock.calls.length).toBe(0);
39 | const pr = getAllTeams();
40 | let resolvedVal: unknown;
41 | pr.then((val) => {
42 | resolvedVal = val;
43 | });
44 | expect(pr).toBeInstanceOf(Promise);
45 | expect(resolvedVal).toBeUndefined();
46 | expect(mockedApiCall.mock.calls.length).toBe(1);
47 |
48 | await pr;
49 | expect(resolvedVal).toMatchObject([
50 | {
51 | id: "linkedin",
52 | name: "LinkedIn",
53 | order: 2,
54 | iconUrl: "/assets/img/linkedin.png",
55 | channels: [],
56 | },
57 | {
58 | id: "ms",
59 | name: "Microsoft",
60 | order: 3,
61 | iconUrl: "/assets/img/microsoft.png",
62 | channels: [],
63 | },
64 | {
65 | id: "avengers",
66 | name: "Avengers",
67 | order: 4,
68 | iconUrl: "/assets/img/avengers.jpg",
69 | channels: [],
70 | },
71 | ]);
72 | done();
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "references": [{ "path": ".." }],
3 | "compilerOptions": {
4 | "lib": ["ES2018"],
5 | "types": ["jest"],
6 | "baseUrl": "..",
7 | "noEmit": true,
8 | "paths": { "@shlack/data": [".."] }
9 | },
10 | "include": ["."]
11 | }
12 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/data/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 | "references": [{ "path": "../types" }, { "path": "../utils" }],
4 | "compilerOptions": {
5 | "composite": true,
6 | "outDir": "dist",
7 | "rootDir": "src"
8 | },
9 | "include": ["src"]
10 | }
11 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "targets": { "node": "10" } }],
4 | "@babel/preset-react",
5 | "@babel/preset-typescript"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc",
3 | "parserOptions": {
4 | "project": "tsconfig.json"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/API_EXAMPLES.http:
--------------------------------------------------------------------------------
1 |
2 | ### Get all users
3 | GET http://localhost:1234/api/users HTTP/1.1
4 |
5 | ### Get one user
6 | GET http://localhost:1234/api/users/1 HTTP/1.1
7 |
8 | ### Get all teams
9 | GET http://localhost:1234/api/teams HTTP/1.1
10 |
11 | ### Get a team (includes channels)
12 | GET http://localhost:1234/api/teams/li HTTP/1.1
13 |
14 | ### Get team channel messages
15 | GET http://localhost:1234/api/teams/li/channels/general/messages HTTP/1.1
16 |
17 | ### Create a new message in a team channel
18 | POST http://localhost:1234/api/messages HTTP/1.1
19 | Content-Type: application/json
20 |
21 | {
22 | "teamId": "li",
23 | "channelId": "general",
24 | "userId": 1,
25 | "body": "Hi everyone!"
26 | }
27 |
28 | ### Delete a message
29 | DELETE http://localhost:1234/api/messages/19 HTTP/1.1
30 | Content-Type: application/json
31 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/app.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | -webkit-text-decoration: underline dotted;
89 | text-decoration: underline dotted; /* 2 */
90 | }
91 |
92 | /**
93 | * Add the correct font weight in Chrome, Edge, and Safari.
94 | */
95 |
96 | b,
97 | strong {
98 | font-weight: bolder;
99 | }
100 |
101 | /**
102 | * 1. Correct the inheritance and scaling of font size in all browsers.
103 | * 2. Correct the odd `em` font sizing in all browsers.
104 | */
105 |
106 | code,
107 | kbd,
108 | samp {
109 | font-family: monospace, monospace; /* 1 */
110 | font-size: 1em; /* 2 */
111 | }
112 |
113 | /**
114 | * Add the correct font size in all browsers.
115 | */
116 |
117 | small {
118 | font-size: 80%;
119 | }
120 |
121 | /**
122 | * Prevent `sub` and `sup` elements from affecting the line height in
123 | * all browsers.
124 | */
125 |
126 | sub,
127 | sup {
128 | font-size: 75%;
129 | line-height: 0;
130 | position: relative;
131 | vertical-align: baseline;
132 | }
133 |
134 | sub {
135 | bottom: -0.25em;
136 | }
137 |
138 | sup {
139 | top: -0.5em;
140 | }
141 |
142 | /* Embedded content
143 | ========================================================================== */
144 |
145 | /**
146 | * Remove the border on images inside links in IE 10.
147 | */
148 |
149 | img {
150 | border-style: none;
151 | }
152 |
153 | /* Forms
154 | ========================================================================== */
155 |
156 | /**
157 | * 1. Change the font styles in all browsers.
158 | * 2. Remove the margin in Firefox and Safari.
159 | */
160 |
161 | button,
162 | input,
163 | optgroup,
164 | select,
165 | textarea {
166 | font-family: inherit; /* 1 */
167 | font-size: 100%; /* 1 */
168 | line-height: 1.15; /* 1 */
169 | margin: 0; /* 2 */
170 | }
171 |
172 | /**
173 | * Show the overflow in IE.
174 | * 1. Show the overflow in Edge.
175 | */
176 |
177 | button,
178 | input {
179 | /* 1 */
180 | overflow: visible;
181 | }
182 |
183 | /**
184 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
185 | * 1. Remove the inheritance of text transform in Firefox.
186 | */
187 |
188 | button,
189 | select {
190 | /* 1 */
191 | text-transform: none;
192 | }
193 |
194 | /**
195 | * Correct the inability to style clickable types in iOS and Safari.
196 | */
197 |
198 | button,
199 | [type="button"] {
200 | -webkit-appearance: button;
201 | }
202 |
203 | /**
204 | * Remove the inner border and padding in Firefox.
205 | */
206 |
207 | button::-moz-focus-inner,
208 | [type="button"]::-moz-focus-inner {
209 | border-style: none;
210 | padding: 0;
211 | }
212 |
213 | /**
214 | * Restore the focus styles unset by the previous rule.
215 | */
216 |
217 | button:-moz-focusring,
218 | [type="button"]:-moz-focusring {
219 | outline: 1px dotted ButtonText;
220 | }
221 |
222 | /**
223 | * Correct the padding in Firefox.
224 | */
225 |
226 | fieldset {
227 | padding: 0.35em 0.75em 0.625em;
228 | }
229 |
230 | /**
231 | * 1. Correct the text wrapping in Edge and IE.
232 | * 2. Correct the color inheritance from `fieldset` elements in IE.
233 | * 3. Remove the padding so developers are not caught out when they zero out
234 | * `fieldset` elements in all browsers.
235 | */
236 |
237 | legend {
238 | box-sizing: border-box; /* 1 */
239 | color: inherit; /* 2 */
240 | display: table; /* 1 */
241 | max-width: 100%; /* 1 */
242 | padding: 0; /* 3 */
243 | white-space: normal; /* 1 */
244 | }
245 |
246 | /**
247 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
248 | */
249 |
250 | progress {
251 | vertical-align: baseline;
252 | }
253 |
254 | /**
255 | * Remove the default vertical scrollbar in IE 10+.
256 | */
257 |
258 | textarea {
259 | overflow: auto;
260 | }
261 |
262 | /**
263 | * 1. Add the correct box sizing in IE 10.
264 | * 2. Remove the padding in IE 10.
265 | */
266 |
267 | /**
268 | * Correct the cursor style of increment and decrement buttons in Chrome.
269 | */
270 |
271 | /**
272 | * 1. Correct the odd appearance in Chrome and Safari.
273 | * 2. Correct the outline style in Safari.
274 | */
275 |
276 | /**
277 | * Remove the inner padding in Chrome and Safari on macOS.
278 | */
279 |
280 | /**
281 | * 1. Correct the inability to style clickable types in iOS and Safari.
282 | * 2. Change font properties to `inherit` in Safari.
283 | */
284 |
285 | /* Interactive
286 | ========================================================================== */
287 |
288 | /*
289 | * Add the correct display in Edge, IE 10+, and Firefox.
290 | */
291 |
292 | details {
293 | display: block;
294 | }
295 |
296 | /*
297 | * Add the correct display in all browsers.
298 | */
299 |
300 | summary {
301 | display: list-item;
302 | }
303 |
304 | /* Misc
305 | ========================================================================== */
306 |
307 | /**
308 | * Add the correct display in IE 10+.
309 | */
310 |
311 | template {
312 | display: none;
313 | }
314 |
315 | /**
316 | * Add the correct display in IE 10.
317 | */
318 |
319 | [hidden] {
320 | display: none;
321 | }
322 |
323 | /**
324 | * Manually forked from SUIT CSS Base: https://github.com/suitcss/base
325 | * A thin layer on top of normalize.css that provides a starting point more
326 | * suitable for web applications.
327 | */
328 |
329 | /**
330 | * Removes the default spacing and border for appropriate elements.
331 | */
332 |
333 | blockquote,
334 | dl,
335 | dd,
336 | h1,
337 | h2,
338 | h3,
339 | h4,
340 | h5,
341 | h6,
342 | hr,
343 | figure,
344 | p,
345 | pre {
346 | margin: 0;
347 | }
348 |
349 | button {
350 | background-color: transparent;
351 | background-image: none;
352 | }
353 |
354 | /**
355 | * Work around a Firefox/IE bug where the transparent `button` background
356 | * results in a loss of the default `button` focus styles.
357 | */
358 |
359 | button:focus {
360 | outline: 1px dotted;
361 | outline: 5px auto -webkit-focus-ring-color;
362 | }
363 |
364 | fieldset {
365 | margin: 0;
366 | padding: 0;
367 | }
368 |
369 | ol,
370 | ul {
371 | list-style: none;
372 | margin: 0;
373 | padding: 0;
374 | }
375 |
376 | /**
377 | * Tailwind custom reset styles
378 | */
379 |
380 | /**
381 | * 1. Use the user's configured `sans` font-family (with Tailwind's default
382 | * sans-serif font stack as a fallback) as a sane default.
383 | * 2. Use Tailwind's default "normal" line-height so the user isn't forced
384 | * to override it to ensure consistency even when using the default theme.
385 | */
386 |
387 | html {
388 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
389 | "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
390 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */
391 | line-height: 1.5; /* 2 */
392 | }
393 |
394 | /**
395 | * 1. Prevent padding and border from affecting element width.
396 | *
397 | * We used to set this in the html element and inherit from
398 | * the parent element for everything else. This caused issues
399 | * in shadow-dom-enhanced elements like where the content
400 | * is wrapped by a div with box-sizing set to `content-box`.
401 | *
402 | * https://github.com/mozdevs/cssremedy/issues/4
403 | *
404 | *
405 | * 2. Allow adding a border to an element by just adding a border-width.
406 | *
407 | * By default, the way the browser specifies that an element should have no
408 | * border is by setting it's border-style to `none` in the user-agent
409 | * stylesheet.
410 | *
411 | * In order to easily add borders to elements by just setting the `border-width`
412 | * property, we change the default border-style for all elements to `solid`, and
413 | * use border-width to hide them instead. This way our `border` utilities only
414 | * need to set the `border-width` property instead of the entire `border`
415 | * shorthand, making our border utilities much more straightforward to compose.
416 | *
417 | * https://github.com/tailwindcss/tailwindcss/pull/116
418 | */
419 |
420 | *,
421 | ::before,
422 | ::after {
423 | box-sizing: border-box; /* 1 */
424 | border-width: 0; /* 2 */
425 | border-style: solid; /* 2 */
426 | border-color: #e2e8f0; /* 2 */
427 | }
428 |
429 | /*
430 | * Ensure horizontal rules are visible by default
431 | */
432 |
433 | hr {
434 | border-top-width: 1px;
435 | }
436 |
437 | /**
438 | * Undo the `border-style: none` reset that Normalize applies to images so that
439 | * our `border-{width}` utilities have the expected effect.
440 | *
441 | * The Normalize reset is unnecessary for us since we default the border-width
442 | * to 0 on all elements.
443 | *
444 | * https://github.com/tailwindcss/tailwindcss/issues/362
445 | */
446 |
447 | img {
448 | border-style: solid;
449 | }
450 |
451 | textarea {
452 | resize: vertical;
453 | }
454 |
455 | input::-moz-placeholder,
456 | textarea::-moz-placeholder {
457 | color: #a0aec0;
458 | }
459 |
460 | input:-ms-input-placeholder,
461 | textarea:-ms-input-placeholder {
462 | color: #a0aec0;
463 | }
464 |
465 | input::placeholder,
466 | textarea::placeholder {
467 | color: #a0aec0;
468 | }
469 |
470 | button,
471 | [role="button"] {
472 | cursor: pointer;
473 | }
474 |
475 | table {
476 | border-collapse: collapse;
477 | }
478 |
479 | h1,
480 | h2,
481 | h3,
482 | h4,
483 | h5,
484 | h6 {
485 | font-size: inherit;
486 | font-weight: inherit;
487 | }
488 |
489 | /**
490 | * Reset links to optimize for opt-in styling instead of
491 | * opt-out.
492 | */
493 |
494 | a {
495 | color: inherit;
496 | text-decoration: inherit;
497 | }
498 |
499 | /**
500 | * Reset form element properties that are easy to forget to
501 | * style explicitly so you don't inadvertently introduce
502 | * styles that deviate from your design system. These styles
503 | * supplement a partial reset that is already applied by
504 | * normalize.css.
505 | */
506 |
507 | button,
508 | input,
509 | optgroup,
510 | select,
511 | textarea {
512 | padding: 0;
513 | line-height: inherit;
514 | color: inherit;
515 | }
516 |
517 | /**
518 | * Use the configured 'mono' font family for elements that
519 | * are expected to be rendered with a monospace font, falling
520 | * back to the system monospace stack if there is no configured
521 | * 'mono' font family.
522 | */
523 |
524 | pre,
525 | code,
526 | kbd,
527 | samp {
528 | font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
529 | monospace;
530 | }
531 |
532 | /**
533 | * Make replaced elements `display: block` by default as that's
534 | * the behavior you want almost all of the time. Inspired by
535 | * CSS Remedy, with `svg` added as well.
536 | *
537 | * https://github.com/mozdevs/cssremedy/issues/14
538 | */
539 |
540 | img,
541 | svg,
542 | video,
543 | canvas,
544 | audio,
545 | iframe,
546 | embed,
547 | object {
548 | display: block;
549 | vertical-align: middle;
550 | }
551 |
552 | /**
553 | * Constrain images and videos to the parent width and preserve
554 | * their instrinsic aspect ratio.
555 | *
556 | * https://github.com/mozdevs/cssremedy/issues/14
557 | */
558 |
559 | img,
560 | video {
561 | max-width: 100%;
562 | height: auto;
563 | }
564 |
565 | .sr-only {
566 | position: absolute;
567 | width: 1px;
568 | height: 1px;
569 | padding: 0;
570 | margin: -1px;
571 | overflow: hidden;
572 | clip: rect(0, 0, 0, 0);
573 | white-space: nowrap;
574 | border-width: 0;
575 | }
576 |
577 | .bg-white {
578 | --bg-opacity: 1;
579 | background-color: #fff;
580 | background-color: rgba(255, 255, 255, var(--bg-opacity));
581 | }
582 |
583 | .bg-gray-500 {
584 | --bg-opacity: 1;
585 | background-color: #a0aec0;
586 | background-color: rgba(160, 174, 192, var(--bg-opacity));
587 | }
588 |
589 | .bg-gray-600 {
590 | --bg-opacity: 1;
591 | background-color: #718096;
592 | background-color: rgba(113, 128, 150, var(--bg-opacity));
593 | }
594 |
595 | .bg-teal-700 {
596 | --bg-opacity: 1;
597 | background-color: #2c7a7b;
598 | background-color: rgba(44, 122, 123, var(--bg-opacity));
599 | }
600 |
601 | .bg-indigo-800 {
602 | --bg-opacity: 1;
603 | background-color: #2f365f;
604 | background-color: rgba(47, 54, 95, var(--bg-opacity));
605 | }
606 |
607 | .bg-indigo-900 {
608 | --bg-opacity: 1;
609 | background-color: #191e38;
610 | background-color: rgba(25, 30, 56, var(--bg-opacity));
611 | }
612 |
613 | .hover\:bg-gray-100:hover {
614 | --bg-opacity: 1;
615 | background-color: #f7fafc;
616 | background-color: rgba(247, 250, 252, var(--bg-opacity));
617 | }
618 |
619 | .hover\:bg-red-100:hover {
620 | --bg-opacity: 1;
621 | background-color: #fcebea;
622 | background-color: rgba(252, 235, 234, var(--bg-opacity));
623 | }
624 |
625 | .hover\:bg-red-800:hover {
626 | --bg-opacity: 1;
627 | background-color: #9b2c2c;
628 | background-color: rgba(155, 44, 44, var(--bg-opacity));
629 | }
630 |
631 | .border-transparent {
632 | border-color: transparent;
633 | }
634 |
635 | .border-teal-600 {
636 | --border-opacity: 1;
637 | border-color: #319795;
638 | border-color: rgba(49, 151, 149, var(--border-opacity));
639 | }
640 |
641 | .border-indigo-900 {
642 | --border-opacity: 1;
643 | border-color: #191e38;
644 | border-color: rgba(25, 30, 56, var(--border-opacity));
645 | }
646 |
647 | .hover\:border-red-400:hover {
648 | --border-opacity: 1;
649 | border-color: #ef5753;
650 | border-color: rgba(239, 87, 83, var(--border-opacity));
651 | }
652 |
653 | .rounded {
654 | border-radius: 0.25rem;
655 | }
656 |
657 | .rounded-lg {
658 | border-radius: 0.5rem;
659 | }
660 |
661 | .border-2 {
662 | border-width: 2px;
663 | }
664 |
665 | .border-r-2 {
666 | border-right-width: 2px;
667 | }
668 |
669 | .border-b {
670 | border-bottom-width: 1px;
671 | }
672 |
673 | .cursor-pointer {
674 | cursor: pointer;
675 | }
676 |
677 | .block {
678 | display: block;
679 | }
680 |
681 | .inline-block {
682 | display: inline-block;
683 | }
684 |
685 | .flex {
686 | display: flex;
687 | }
688 |
689 | .table {
690 | display: table;
691 | }
692 |
693 | .hidden {
694 | display: none;
695 | }
696 |
697 | .flex-row {
698 | flex-direction: row;
699 | }
700 |
701 | .flex-col {
702 | flex-direction: column;
703 | }
704 |
705 | .items-start {
706 | align-items: flex-start;
707 | }
708 |
709 | .items-center {
710 | align-items: center;
711 | }
712 |
713 | .justify-center {
714 | justify-content: center;
715 | }
716 |
717 | .justify-between {
718 | justify-content: space-between;
719 | }
720 |
721 | .flex-1 {
722 | flex: 1 1 0%;
723 | }
724 |
725 | .flex-auto {
726 | flex: 1 1 auto;
727 | }
728 |
729 | .flex-none {
730 | flex: none;
731 | }
732 |
733 | .font-sans {
734 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
735 | "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
736 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
737 | }
738 |
739 | .font-normal {
740 | font-weight: 400;
741 | }
742 |
743 | .font-semibold {
744 | font-weight: 600;
745 | }
746 |
747 | .font-bold {
748 | font-weight: 700;
749 | }
750 |
751 | .font-extrabold {
752 | font-weight: 800;
753 | }
754 |
755 | .h-2 {
756 | height: 0.5rem;
757 | }
758 |
759 | .h-4 {
760 | height: 1rem;
761 | }
762 |
763 | .h-6 {
764 | height: 1.5rem;
765 | }
766 |
767 | .h-10 {
768 | height: 2.5rem;
769 | }
770 |
771 | .h-12 {
772 | height: 3rem;
773 | }
774 |
775 | .h-full {
776 | height: 100%;
777 | }
778 |
779 | .h-screen {
780 | height: 100vh;
781 | }
782 |
783 | .text-xs {
784 | font-size: 0.75rem;
785 | }
786 |
787 | .text-sm {
788 | font-size: 0.875rem;
789 | }
790 |
791 | .text-lg {
792 | font-size: 1.125rem;
793 | }
794 |
795 | .text-xl {
796 | font-size: 1.25rem;
797 | }
798 |
799 | .text-2xl {
800 | font-size: 1.5rem;
801 | }
802 |
803 | .text-3xl {
804 | font-size: 1.875rem;
805 | }
806 |
807 | .leading-tight {
808 | line-height: 1.25;
809 | }
810 |
811 | .leading-normal {
812 | line-height: 1.5;
813 | }
814 |
815 | .m-12 {
816 | margin: 3rem;
817 | }
818 |
819 | .mx-4 {
820 | margin-left: 1rem;
821 | margin-right: 1rem;
822 | }
823 |
824 | .mb-1 {
825 | margin-bottom: 0.25rem;
826 | }
827 |
828 | .ml-1 {
829 | margin-left: 0.25rem;
830 | }
831 |
832 | .mt-2 {
833 | margin-top: 0.5rem;
834 | }
835 |
836 | .mr-2 {
837 | margin-right: 0.5rem;
838 | }
839 |
840 | .mb-2 {
841 | margin-bottom: 0.5rem;
842 | }
843 |
844 | .mt-3 {
845 | margin-top: 0.75rem;
846 | }
847 |
848 | .mr-3 {
849 | margin-right: 0.75rem;
850 | }
851 |
852 | .mb-4 {
853 | margin-bottom: 1rem;
854 | }
855 |
856 | .mb-6 {
857 | margin-bottom: 1.5rem;
858 | }
859 |
860 | .opacity-25 {
861 | opacity: 0.25;
862 | }
863 |
864 | .opacity-50 {
865 | opacity: 0.5;
866 | }
867 |
868 | .opacity-75 {
869 | opacity: 0.75;
870 | }
871 |
872 | .opacity-100 {
873 | opacity: 1;
874 | }
875 |
876 | .overflow-hidden {
877 | overflow: hidden;
878 | }
879 |
880 | .overflow-y-scroll {
881 | overflow-y: scroll;
882 | }
883 |
884 | .p-2 {
885 | padding: 0.5rem;
886 | }
887 |
888 | .p-4 {
889 | padding: 1rem;
890 | }
891 |
892 | .py-1 {
893 | padding-top: 0.25rem;
894 | padding-bottom: 0.25rem;
895 | }
896 |
897 | .py-2 {
898 | padding-top: 0.5rem;
899 | padding-bottom: 0.5rem;
900 | }
901 |
902 | .py-4 {
903 | padding-top: 1rem;
904 | padding-bottom: 1rem;
905 | }
906 |
907 | .px-4 {
908 | padding-left: 1rem;
909 | padding-right: 1rem;
910 | }
911 |
912 | .px-6 {
913 | padding-left: 1.5rem;
914 | padding-right: 1.5rem;
915 | }
916 |
917 | .pt-2 {
918 | padding-top: 0.5rem;
919 | }
920 |
921 | .pr-2 {
922 | padding-right: 0.5rem;
923 | }
924 |
925 | .pl-3 {
926 | padding-left: 0.75rem;
927 | }
928 |
929 | .pl-4 {
930 | padding-left: 1rem;
931 | }
932 |
933 | .pb-6 {
934 | padding-bottom: 1.5rem;
935 | }
936 |
937 | .fill-current {
938 | fill: currentColor;
939 | }
940 |
941 | .text-black {
942 | --text-opacity: 1;
943 | color: #000;
944 | color: rgba(0, 0, 0, var(--text-opacity));
945 | }
946 |
947 | .text-white {
948 | --text-opacity: 1;
949 | color: #fff;
950 | color: rgba(255, 255, 255, var(--text-opacity));
951 | }
952 |
953 | .text-gray-200 {
954 | --text-opacity: 1;
955 | color: #edf2f7;
956 | color: rgba(237, 242, 247, var(--text-opacity));
957 | }
958 |
959 | .text-gray-500 {
960 | --text-opacity: 1;
961 | color: #a0aec0;
962 | color: rgba(160, 174, 192, var(--text-opacity));
963 | }
964 |
965 | .text-gray-600 {
966 | --text-opacity: 1;
967 | color: #718096;
968 | color: rgba(113, 128, 150, var(--text-opacity));
969 | }
970 |
971 | .text-gray-800 {
972 | --text-opacity: 1;
973 | color: #2d3748;
974 | color: rgba(45, 55, 72, var(--text-opacity));
975 | }
976 |
977 | .text-green-500 {
978 | --text-opacity: 1;
979 | color: #48bb78;
980 | color: rgba(72, 187, 120, var(--text-opacity));
981 | }
982 |
983 | .text-purple-300 {
984 | --text-opacity: 1;
985 | color: #d6bbfc;
986 | color: rgba(214, 187, 252, var(--text-opacity));
987 | }
988 |
989 | .uppercase {
990 | text-transform: uppercase;
991 | }
992 |
993 | .no-underline {
994 | text-decoration: none;
995 | }
996 |
997 | .hover\:underline:hover {
998 | text-decoration: underline;
999 | }
1000 |
1001 | .antialiased {
1002 | -webkit-font-smoothing: antialiased;
1003 | -moz-osx-font-smoothing: grayscale;
1004 | }
1005 |
1006 | .truncate {
1007 | overflow: hidden;
1008 | text-overflow: ellipsis;
1009 | white-space: nowrap;
1010 | }
1011 |
1012 | .w-2 {
1013 | width: 0.5rem;
1014 | }
1015 |
1016 | .w-4 {
1017 | width: 1rem;
1018 | }
1019 |
1020 | .w-6 {
1021 | width: 1.5rem;
1022 | }
1023 |
1024 | .w-10 {
1025 | width: 2.5rem;
1026 | }
1027 |
1028 | .w-12 {
1029 | width: 3rem;
1030 | }
1031 |
1032 | .w-full {
1033 | width: 100%;
1034 | }
1035 |
1036 | @-webkit-keyframes spin {
1037 | to {
1038 | transform: rotate(360deg);
1039 | }
1040 | }
1041 |
1042 | @keyframes spin {
1043 | to {
1044 | transform: rotate(360deg);
1045 | }
1046 | }
1047 |
1048 | @-webkit-keyframes ping {
1049 | 75%,
1050 | 100% {
1051 | transform: scale(2);
1052 | opacity: 0;
1053 | }
1054 | }
1055 |
1056 | @keyframes ping {
1057 | 75%,
1058 | 100% {
1059 | transform: scale(2);
1060 | opacity: 0;
1061 | }
1062 | }
1063 |
1064 | @-webkit-keyframes pulse {
1065 | 50% {
1066 | opacity: 0.5;
1067 | }
1068 | }
1069 |
1070 | @keyframes pulse {
1071 | 50% {
1072 | opacity: 0.5;
1073 | }
1074 | }
1075 |
1076 | @-webkit-keyframes bounce {
1077 | 0%,
1078 | 100% {
1079 | transform: translateY(-25%);
1080 | -webkit-animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
1081 | animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
1082 | }
1083 |
1084 | 50% {
1085 | transform: none;
1086 | -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
1087 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
1088 | }
1089 | }
1090 |
1091 | @keyframes bounce {
1092 | 0%,
1093 | 100% {
1094 | transform: translateY(-25%);
1095 | -webkit-animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
1096 | animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
1097 | }
1098 |
1099 | 50% {
1100 | transform: none;
1101 | -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
1102 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
1103 | }
1104 | }
1105 |
1106 | .hover-target .show-on-hover {
1107 | opacity: 0;
1108 | filter: alpha(opacity=0);
1109 | }
1110 |
1111 | .hover-target:hover .show-on-hover,
1112 | .hover-target .show-on-hover:focus,
1113 | .hover-target .show-on-hover:active {
1114 | opacity: 1;
1115 | filter: alpha(opacity=1);
1116 | }
1117 |
1118 | .sr-only {
1119 | -webkit-clip-path: inset(50%);
1120 | clip-path: inset(50%);
1121 | clip: rect(1px, 1px, 1px, 1px);
1122 | height: 1px;
1123 | margin: -1px;
1124 | overflow: hidden;
1125 | padding: 0;
1126 | position: absolute;
1127 | width: 1px;
1128 | }
1129 |
1130 | @media (min-width: 640px) {
1131 | .sm\:block {
1132 | display: block;
1133 | }
1134 |
1135 | .sm\:flex-row {
1136 | flex-direction: row;
1137 | }
1138 |
1139 | .sm\:w-48 {
1140 | width: 12rem;
1141 | }
1142 | }
1143 |
1144 | @media (min-width: 768px) {
1145 | .md\:w-64 {
1146 | width: 16rem;
1147 | }
1148 | }
1149 |
1150 | /*# sourceMappingURL=/app.32da848e.css.map */
1151 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/angry-cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/angry-cat.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/avengers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/avengers.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/boss.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/boss.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/cat.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/clippy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/clippy.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/colonel-meow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/colonel-meow.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/desk_flip.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/desk_flip.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/dilbert.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/dilbert.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/drstrange.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/drstrange.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/ironman.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/ironman.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/jquery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/jquery.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/js.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/linkedin.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/lisa.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/lisa.jpeg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/maru.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/maru.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/microsoft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/microsoft.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/mike.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/mike.jpeg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/node.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/office97.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/office97.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/thor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/thor.jpg
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/assets/img/ts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/COURSE_FILES/09-internal-dependents/ui/assets/img/ts.png
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "teams": [
3 | {
4 | "id": "linkedin",
5 | "name": "LinkedIn",
6 | "order": 2,
7 | "iconUrl": "/assets/img/linkedin.png"
8 | },
9 | {
10 | "id": "ms",
11 | "name": "Microsoft",
12 | "order": 3,
13 | "iconUrl": "/assets/img/microsoft.png"
14 | },
15 | {
16 | "id": "avengers",
17 | "name": "Avengers",
18 | "order": 4,
19 | "iconUrl": "/assets/img/avengers.jpg"
20 | },
21 | {
22 | "id": "angrycat",
23 | "name": "Angry Cat",
24 | "order": 5,
25 | "iconUrl": "/assets/img/angry-cat.jpg"
26 | },
27 | {
28 | "id": "javascript",
29 | "name": "Javascript",
30 | "order": 6,
31 | "iconUrl": "/assets/img/js.png"
32 | }
33 | ],
34 | "users": [
35 | {
36 | "id": 0,
37 | "name": "Dilbert",
38 | "username": "dilbert",
39 | "iconUrl": "/assets/img/dilbert.jpg"
40 | },
41 | {
42 | "id": 1,
43 | "name": "Mike North",
44 | "username": "mike",
45 | "iconUrl": "/assets/img/mike.jpeg"
46 | },
47 | {
48 | "id": 2,
49 | "name": "Lisa Huang-North",
50 | "username": "lisa",
51 | "iconUrl": "/assets/img/lisa.jpeg"
52 | },
53 | {
54 | "id": 3,
55 | "name": "Clippy",
56 | "username": "clippy",
57 | "iconUrl": "/assets/img/clippy.png"
58 | },
59 | {
60 | "id": 4,
61 | "name": "office97",
62 | "username": "office",
63 | "iconUrl": "/assets/img/office97.png"
64 | },
65 | {
66 | "id": 5,
67 | "name": "Tony Stark",
68 | "username": "ironman",
69 | "iconUrl": "/assets/img/ironman.jpg"
70 | },
71 | {
72 | "id": 6,
73 | "name": "Thor",
74 | "username": "thor",
75 | "iconUrl": "/assets/img/thor.jpg"
76 | },
77 | {
78 | "id": 7,
79 | "name": "Dr Stephen Strange",
80 | "username": "strange",
81 | "iconUrl": "/assets/img/drstrange.jpg"
82 | },
83 | {
84 | "id": 8,
85 | "name": "Lil Bub",
86 | "username": "bub",
87 | "iconUrl": "/assets/img/cat.jpg"
88 | },
89 | {
90 | "id": 9,
91 | "name": "Colonel Meow",
92 | "username": "meow",
93 | "iconUrl": "/assets/img/colonel-meow.jpg"
94 | },
95 | {
96 | "id": 10,
97 | "name": "Maru",
98 | "username": "maru",
99 | "iconUrl": "/assets/img/maru.jpg"
100 | },
101 | {
102 | "id": 11,
103 | "name": "NodeJS",
104 | "username": "nodejs",
105 | "iconUrl": "/assets/img/node.png"
106 | },
107 | {
108 | "id": 12,
109 | "name": "Engineer Anon",
110 | "username": "anonymous",
111 | "iconUrl": "/assets/img/desk_flip.jpg"
112 | },
113 | {
114 | "id": 13,
115 | "name": "Typescript",
116 | "username": "typescript",
117 | "iconUrl": "/assets/img/ts.png"
118 | },
119 | {
120 | "id": 14,
121 | "name": "jQuery",
122 | "username": "jquery",
123 | "iconUrl": "/assets/img/jquery.png"
124 | },
125 | {
126 | "id": 15,
127 | "name": "boss man",
128 | "username": "boss",
129 | "iconUrl": "/assets/img/boss.jpg"
130 | }
131 | ],
132 | "channels": [
133 | {
134 | "id": "recruiting",
135 | "name": "recruiting",
136 | "description": "The Next Generation Of Recruiting. Find top talents today!",
137 | "teamId": "linkedin"
138 | },
139 | {
140 | "id": "general",
141 | "name": "general",
142 | "description": "LinkedIn general chat",
143 | "teamId": "linkedin"
144 | },
145 | {
146 | "id": "jobs",
147 | "name": "Job hunting",
148 | "description": "Discover companies that fit you.",
149 | "teamId": "linkedin"
150 | },
151 | {
152 | "id": "tbt",
153 | "name": "throw back thursday",
154 | "description": "Remember the good old days? yay, us too.",
155 | "teamId": "ms"
156 | },
157 | {
158 | "id": "endgame",
159 | "name": "top secret",
160 | "description": "for your eyes only",
161 | "teamId": "avengers"
162 | },
163 | {
164 | "id": "dominate",
165 | "name": "catnip",
166 | "description": "exchange tips and best practicse on world domination",
167 | "teamId": "angrycat",
168 | "isDisabled": true
169 | },
170 | {
171 | "id": "funny",
172 | "name": "funny",
173 | "description": "think you got what it takes? Share your best memes / jokes here!",
174 | "teamId": "javascript"
175 | }
176 | ],
177 | "messages": [
178 | {
179 | "teamId": "linkedin",
180 | "channelId": "compliments",
181 | "userId": 2,
182 | "createdAt": "2019-04-21T17:48:33.421Z",
183 | "body": "Your penmanship is excellent!",
184 | "id": 1
185 | },
186 | {
187 | "teamId": "linkedin",
188 | "channelId": "compliments",
189 | "userId": 1,
190 | "createdAt": "2019-04-21T17:54:38.556Z",
191 | "body": "I admire your punctuality",
192 | "id": 2
193 | },
194 | {
195 | "teamId": "linkedin",
196 | "channelId": "general",
197 | "userId": 2,
198 | "createdAt": "2019-04-21T17:55:08.713Z",
199 | "body": "Hello shlack!",
200 | "id": 3
201 | },
202 | {
203 | "teamId": "linkedin",
204 | "channelId": "general",
205 | "userId": 1,
206 | "createdAt": "2019-04-21T18:36:30.995Z",
207 | "body": "awda",
208 | "id": 11
209 | },
210 | {
211 | "teamId": "linkedin",
212 | "channelId": "general",
213 | "userId": 1,
214 | "createdAt": "2019-04-21T18:40:34.648Z",
215 | "body": "wad",
216 | "id": 12
217 | },
218 | {
219 | "teamId": "linkedin",
220 | "channelId": "general",
221 | "userId": 1,
222 | "body": "wdw",
223 | "createdAt": 1555872178322,
224 | "id": 13
225 | },
226 | {
227 | "teamId": "linkedin",
228 | "channelId": "general",
229 | "userId": 1,
230 | "body": "awqwdqwqwdq",
231 | "enrichedBody": ["awqwdqwqwdq"],
232 | "createdAt": 1555872270175,
233 | "id": 14
234 | },
235 | {
236 | "teamId": "linkedin",
237 | "channelId": "general",
238 | "userId": 1,
239 | "body": "qdq",
240 | "createdAt": 1555872790592,
241 | "id": 15
242 | },
243 | {
244 | "teamId": "linkedin",
245 | "channelId": "general",
246 | "userId": 1,
247 | "body": "w",
248 | "createdAt": 1555872792437,
249 | "id": 16
250 | },
251 | {
252 | "teamId": "linkedin",
253 | "channelId": "general",
254 | "userId": 2,
255 | "body": "Would you like to join my professional network?",
256 | "createdAt": 1555874498634,
257 | "id": 17
258 | },
259 | {
260 | "teamId": "linkedin",
261 | "channelId": "general",
262 | "userId": 1,
263 | "body": "Hello developer, I looked at your profile and am impressed by your 14 years of COBOL experience. Are you happy in your current role?",
264 | "createdAt": 1555874584752,
265 | "id": 18
266 | },
267 | {
268 | "channelId": "bofny",
269 | "teamId": "avengers",
270 | "body": "Hey dudes",
271 | "userId": "3",
272 | "createdAt": 1556676377508,
273 | "id": 19
274 | },
275 | {
276 | "channelId": "funny",
277 | "teamId": "javascript",
278 | "body": "\"How do you comfort a JavaScript bug?\" ",
279 | "userId": "14",
280 | "createdAt": 1556679721022,
281 | "id": 20
282 | },
283 | {
284 | "channelId": "funny",
285 | "teamId": "javascript",
286 | "body": "I dunno.. you de-bug it?",
287 | "userId": "12",
288 | "createdAt": 1556679740793,
289 | "id": 21
290 | },
291 | {
292 | "channelId": "funny",
293 | "teamId": "javascript",
294 | "body": "No man, You console it",
295 | "userId": "14",
296 | "createdAt": 1556679745885,
297 | "id": 22
298 | },
299 | {
300 | "channelId": "funny",
301 | "teamId": "javascript",
302 | "body": " Why was the JavaScript developer sad?",
303 | "userId": "14",
304 | "createdAt": 1556679754017,
305 | "id": 23
306 | },
307 | {
308 | "channelId": "funny",
309 | "teamId": "javascript",
310 | "body": "Because there are too many JS frameworks!",
311 | "userId": "11",
312 | "createdAt": 1556679782382,
313 | "id": 24
314 | },
315 | {
316 | "channelId": "funny",
317 | "teamId": "javascript",
318 | "body": "Wrong! It's because he didn’t Node how to Express himself",
319 | "userId": "14",
320 | "createdAt": 1556679797050,
321 | "id": 25
322 | },
323 | {
324 | "channelId": "funny",
325 | "teamId": "javascript",
326 | "body": "ha-ha",
327 | "userId": "11",
328 | "createdAt": 1556679800867,
329 | "id": 26
330 | },
331 | {
332 | "channelId": "funny",
333 | "teamId": "javascript",
334 | "body": "Ok here's one: Why do C# and Java developers keep breaking their keyboards?",
335 | "userId": "12",
336 | "createdAt": 1556679820803,
337 | "id": 27
338 | },
339 | {
340 | "channelId": "funny",
341 | "teamId": "javascript",
342 | "body": "Mmm... because one of them tried to write a Hello World application in Java EE?",
343 | "userId": "14",
344 | "createdAt": 1556679939014,
345 | "id": 28
346 | },
347 | {
348 | "channelId": "funny",
349 | "teamId": "javascript",
350 | "body": "Nah, it's because they both use strongly typed language",
351 | "userId": "12",
352 | "createdAt": 1556680157584,
353 | "id": 29
354 | },
355 | {
356 | "channelId": "funny",
357 | "teamId": "javascript",
358 | "body": "Seriously.",
359 | "userId": "13",
360 | "createdAt": 1556680172998,
361 | "id": 30
362 | },
363 | {
364 | "channelId": "endgame",
365 | "teamId": "avengers",
366 | "body": "Hey... has someone seen my hammer? I can't find it and I need a can opener...",
367 | "userId": "6",
368 | "createdAt": 1556680603106,
369 | "id": 31
370 | },
371 | {
372 | "channelId": "endgame",
373 | "teamId": "avengers",
374 | "body": "No, if you remember the exact date and time you last used it, I can open a time portal for you",
375 | "userId": "7",
376 | "createdAt": 1556680649251,
377 | "id": 32
378 | },
379 | {
380 | "channelId": "endgame",
381 | "teamId": "avengers",
382 | "body": "I'm pretty sure I saw whats-his-name take it. You might want to work out more or the hammer might choose *him* instead",
383 | "userId": "5",
384 | "createdAt": 1556680753815,
385 | "id": 33
386 | },
387 | {
388 | "channelId": "endgame",
389 | "teamId": "avengers",
390 | "body": "NO! I SHALL BRING THUNDER DOWN ON HIM!",
391 | "userId": "6",
392 | "createdAt": 1556680812491,
393 | "id": 34
394 | },
395 | {
396 | "channelId": "endgame",
397 | "teamId": "avengers",
398 | "body": "With which hand? Are you giving up the beer or Fortnite?",
399 | "userId": "5",
400 | "createdAt": 1556680973259,
401 | "id": 35
402 | },
403 | {
404 | "channelId": "dominate",
405 | "teamId": "angrycat",
406 | "body": "Meowhahaha! I crossed the 800K followers mark today! One step closer to world domination 😈",
407 | "userId": "8",
408 | "createdAt": 1556681392393,
409 | "id": 36
410 | },
411 | {
412 | "channelId": "dominate",
413 | "teamId": "angrycat",
414 | "body": "What did you cats get up to today?",
415 | "userId": "8",
416 | "createdAt": 1556681406684,
417 | "id": 37
418 | },
419 | {
420 | "channelId": "dominate",
421 | "teamId": "angrycat",
422 | "body": "Oh and Penguin approached me for another book deal today. Apparently my last book has been hailed \"a literary classic\"...",
423 | "userId": "8",
424 | "createdAt": 1556681504008,
425 | "id": 38
426 | },
427 | {
428 | "channelId": "dominate",
429 | "teamId": "angrycat",
430 | "body": "Can you believe these human?",
431 | "userId": "8",
432 | "createdAt": 1556681514248,
433 | "id": 39
434 | },
435 | {
436 | "channelId": "dominate",
437 | "teamId": "angrycat",
438 | "body": "Pfff, 800K on what network? I have 400K fans on Facebook and they hail me as their \"fearsome dictator\", a \"prodigious Scotch drinker\" and \"the angriest cat in the world\"",
439 | "userId": "9",
440 | "createdAt": 1556681660966,
441 | "id": 40
442 | },
443 | {
444 | "channelId": "dominate",
445 | "teamId": "angrycat",
446 | "body": "AND I hold a Guinness world record 🏆, beat that Bub 🐈",
447 | "userId": "9",
448 | "createdAt": 1556681701911,
449 | "id": 41
450 | },
451 | {
452 | "channelId": "dominate",
453 | "teamId": "angrycat",
454 | "body": "*stretches* not that anyone's counting, but my videos have been been viewed over 325 million times on Youtube. ",
455 | "userId": "10",
456 | "createdAt": 1556681886829,
457 | "id": 42
458 | },
459 | {
460 | "channelId": "dominate",
461 | "teamId": "angrycat",
462 | "body": "Holding a Guinness World Record doesn't get us closer to world domination, meow. We need to think outside the box",
463 | "userId": "10",
464 | "createdAt": 1556681916083,
465 | "id": 43
466 | },
467 | {
468 | "channelId": "dominate",
469 | "teamId": "angrycat",
470 | "body": "But I looooooove cupboard boxes! ",
471 | "userId": "8",
472 | "createdAt": 1556681928716,
473 | "id": 44
474 | },
475 | {
476 | "channelId": "tbt",
477 | "teamId": "ms",
478 | "body": "Sigh, I miss having a job...",
479 | "userId": "3",
480 | "createdAt": 1556682112281,
481 | "id": 45
482 | },
483 | {
484 | "channelId": "tbt",
485 | "teamId": "ms",
486 | "body": "It was so nice to \"talk\" and help people around the world with their spelling mistakes!",
487 | "userId": "3",
488 | "createdAt": 1556682143644,
489 | "id": 46
490 | },
491 | {
492 | "channelId": "tbt",
493 | "teamId": "ms",
494 | "body": "Don't you miss it?",
495 | "userId": "3",
496 | "createdAt": 1556682148707,
497 | "id": 47
498 | },
499 | {
500 | "channelId": "tbt",
501 | "teamId": "ms",
502 | "body": "Tell me about it, they were so happy when they found the pinball game in Word 97",
503 | "userId": "4",
504 | "createdAt": 1556682251128,
505 | "id": 48
506 | },
507 | {
508 | "channelId": "tbt",
509 | "teamId": "ms",
510 | "body": "OHH and that flight simulator game in Excel! People always tried to shoot me for some reason",
511 | "userId": "3",
512 | "createdAt": 1556682314145,
513 | "id": 49
514 | },
515 | {
516 | "channelId": "tbt",
517 | "teamId": "ms",
518 | "body": "At least people remembers you and try to bring you back. I don't think anyone even use floppy disks installation any more...",
519 | "userId": "4",
520 | "createdAt": 1556682392864,
521 | "id": 50
522 | },
523 | {
524 | "channelId": "tbt",
525 | "teamId": "ms",
526 | "body": "I don't miss Windows 95 though. Dang he was slow!",
527 | "userId": "4",
528 | "createdAt": 1556682501011,
529 | "id": 51
530 | },
531 | {
532 | "channelId": "recruiting",
533 | "teamId": "linkedin",
534 | "body": "Hey guys, looking for a ninja rockstar software engineer. Must have 15 years of Rust experience and willing to take 80% compensation in equity. We have free lunches and table tennis in the office.",
535 | "userId": "15",
536 | "createdAt": 1556682816711,
537 | "id": 52
538 | },
539 | {
540 | "channelId": "recruiting",
541 | "teamId": "linkedin",
542 | "body": "Oh and the position is in Antarctica. Low income tax rate, free snow cones and the skiing is great!",
543 | "userId": "15",
544 | "createdAt": 1556682870302,
545 | "id": 53
546 | },
547 | {
548 | "channelId": "jobs",
549 | "teamId": "linkedin",
550 | "body": "LF new position. Our tech lead just decided to go with a COBOL/CoffeeScript stack and I'm getting out of here!!",
551 | "userId": "2",
552 | "createdAt": 1556682931336,
553 | "id": 54
554 | },
555 | {
556 | "channelId": "recruiting",
557 | "teamId": "linkedin",
558 | "body": "#ask Does anyone know the difference between Java and Javascript? ",
559 | "userId": "0",
560 | "createdAt": 1556682976168,
561 | "id": 55
562 | },
563 | {
564 | "channelId": "recruiting",
565 | "teamId": "linkedin",
566 | "body": "Ah nvm, probably doesn't matter. I'll just spray and pray",
567 | "userId": "0",
568 | "createdAt": 1556682987555,
569 | "id": 56
570 | },
571 | {
572 | "channelId": "jobs",
573 | "teamId": "linkedin",
574 | "body": "Somebody just gave me a LinkedIn endorsement for \"Copying & Pasting from Stackoverflow\" and \"Private API usage.\" Are they trying to tell me something?",
575 | "userId": "1",
576 | "createdAt": 1556683077121,
577 | "id": 57
578 | }
579 | ]
580 | }
581 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shlack/ui",
3 | "version": "0.0.1",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "scripts": {
10 | "dev": "node server/server.js",
11 | "lint": "scripty",
12 | "clean": "rimraf dist *.tsbuildinfo",
13 | "build": "parcel build index.html",
14 | "test": "scripty"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.12.0",
18 | "@babel/preset-env": "^7.12.0",
19 | "@babel/preset-react": "^7.10.4",
20 | "@babel/preset-typescript": "^7.12.0",
21 | "@types/date-fns": "^2.6.0",
22 | "@types/express": "^4.17.8",
23 | "@types/json-server": "^0.14.2",
24 | "@types/node": "^14.11.8",
25 | "@types/react": "^16.9.52",
26 | "@types/react-dom": "^16.9.8",
27 | "@types/react-router": "^5.1.8",
28 | "@types/react-router-dom": "^5.1.6",
29 | "react-router": "^5.2.0",
30 | "react-router-dom": "^5.2.0",
31 | "react-test-renderer": "^16.14.0",
32 | "typescript": "^4.0.3"
33 | },
34 | "dependencies": {
35 | "express": "^4.17.1",
36 | "json-server": "^0.16.2",
37 | "parcel-bundler": "^1.12.4",
38 | "react": "^16.14.0",
39 | "react-dom": "^16.14.0"
40 | },
41 | "scripty": {
42 | "path": "../../scripts/packages"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/server/api-server.js:
--------------------------------------------------------------------------------
1 | const jsonServer = require("json-server");
2 |
3 | // const router = jsonServer.router("db.json");
4 | const middlewares = jsonServer.defaults();
5 |
6 | /**
7 | *
8 | * @param {import('express').Request} req
9 | * @param {import('express').Response} res
10 | * @param {import('express').NextFunction} next
11 | */
12 | function SINGULAR_MIDDLEWARE(req, res, next) {
13 | const send = res.send;
14 | res.send = function (body) {
15 | if (req.url.indexOf("singular") >= 0) {
16 | try {
17 | const json = JSON.parse(body);
18 | if (Array.isArray(json)) {
19 | if (json.length === 1) {
20 | return send.call(this, JSON.stringify(json[0]));
21 | } else if (json.length === 0) {
22 | return send(JSON.stringify({ error: "NOT FOUND" })).status(404);
23 | }
24 | }
25 | } catch (e) {
26 | throw new Error("Problem unwrapping array");
27 | }
28 | }
29 | return send.call(this, body);
30 | };
31 | next();
32 | }
33 |
34 | /**
35 | *
36 | * @param {import('express').Request} req
37 | * @param {import('express').Response} _res
38 | * @param {import('express').NextFunction} next
39 | */
40 | function addDateToPost(req, _res, next) {
41 | if (req.method === "POST") {
42 | req.body.createdAt = Date.now();
43 | }
44 | // Continue to JSON Server router
45 | next();
46 | }
47 |
48 | /**
49 | *
50 | * @param {import('express').Router} router
51 | * @param {import('express').Router} server
52 | *
53 | * @returns {import('express').Router}
54 | */
55 | function setupAPI(router, server) {
56 | server.use("/api", SINGULAR_MIDDLEWARE, ...middlewares);
57 | server.use(jsonServer.bodyParser);
58 |
59 | server.use(addDateToPost);
60 | server.use(
61 | jsonServer.rewriter({
62 | "/api/teams": "/api/teams?_embed=channels",
63 | "/api/teams/:id": "/api/teams/:id?_embed=channels",
64 | "/api/teams/:id/channels": "/api/channels?teamId=:id",
65 | "/api/teams/:id/channels/:channelId":
66 | "/api/channels?id=:channelId&teamId=:id&singular=1",
67 | "/api/teams/:id/channels/:channelId/messages":
68 | "/api/messages?_expand=user&teamId=:id&channelId=:channelId",
69 | })
70 | );
71 |
72 | server.use("/api", router);
73 | return server;
74 | }
75 |
76 | module.exports = { setupAPI };
77 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/server/server.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const Bundler = require("parcel-bundler");
3 | const e = require("express");
4 | const jsonServer = require("json-server");
5 | const server = jsonServer.create();
6 | const { setupAPI } = require("./api-server");
7 | const { join } = require("path");
8 |
9 | /**
10 | * Initialize an API server for shlack
11 | *
12 | * @public
13 | * @param {e.Application} a express application
14 | * @param {string} dbFilePath Path to json-server database file
15 | */
16 | function initializeApiServer(a, dbFilePath) {
17 | const server = jsonServer.create();
18 | const jsonApiServer = jsonServer.router(dbFilePath);
19 | setupAPI(jsonApiServer, server);
20 | a.use(server);
21 | }
22 |
23 | /**
24 | * Initialize a UI middleware for shlack
25 | *
26 | * @beta
27 | * @param {e.Application} a express application
28 | * @param {string} uiPkgPath path to the UI package
29 | */
30 | function initializeUiMiddleware(a, uiPkgPath) {
31 | const file = join(uiPkgPath, "index.html"); // Pass an absolute path to the entrypoint here
32 | const options = {}; // See options section of api docs, for the possibilities
33 | // Initialize a new bundler using a file and options
34 | const bundler = new Bundler(file, options);
35 | a.use("/assets", e.static(join(uiPkgPath, "assets")));
36 | a.use(bundler.middleware());
37 | }
38 |
39 | const app = e();
40 |
41 | const PORT = process.env.PORT || 1234;
42 | const DB_FILE_PATH =
43 | process.env.DB_FILE_PATH || join(__dirname, "..", "db.json");
44 | const UI_PKG_PATH = join(__dirname, "..");
45 |
46 | initializeApiServer(app, DB_FILE_PATH);
47 | initializeUiMiddleware(app, UI_PKG_PATH);
48 |
49 | // Listen on port 1234
50 | app.listen(PORT, () => {
51 | console.log(`Listening on http://localhost:${PORT}`);
52 | });
53 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | BrowserRouter as Router,
4 | match,
5 | Route,
6 | Switch,
7 | } from "react-router-dom";
8 | import { getAllTeams } from "@shlack/data";
9 | import { ITeam } from "@shlack/types";
10 | import { useAsyncDataEffect } from "@shlack/utils";
11 | import Loading from "./components/Loading";
12 | import SelectedTeam from "./components/SelectedTeam";
13 | import TeamSelector from "./components/TeamSelector";
14 |
15 | const { useState } = React;
16 |
17 | const App: React.FunctionComponent = () => {
18 | const [teams, setTeams] = useState();
19 |
20 | useAsyncDataEffect(() => getAllTeams(), {
21 | setter: setTeams,
22 | stateName: "teams",
23 | });
24 | if (!teams) return ;
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 | Please select a team
33 |
34 |
35 |
36 |
37 | Please select a team
38 |
39 |
40 | }) => (
43 |
44 | )}
45 | />
46 |
47 |
48 |
49 | );
50 | };
51 | export default App;
52 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Channel.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { getChannelMessages } from "@shlack/data";
3 | import { IChannel, IMessage } from "@shlack/types";
4 | import { useAsyncDataEffect } from "@shlack/utils";
5 | import ChannelFooter from "./Channel/Footer";
6 | import ChannelHeader from "./Channel/Header";
7 | import ChannelMessage from "./Channel/Message";
8 | import Loading from "./Loading";
9 |
10 | const Channel: React.FunctionComponent<{ channel: IChannel }> = ({
11 | channel,
12 | }) => {
13 | const [messages, setMessages] = React.useState();
14 | useAsyncDataEffect(() => getChannelMessages(channel.teamId, channel.id), {
15 | setter: setMessages,
16 | stateName: "messages",
17 | otherStatesToMonitor: [channel],
18 | });
19 | if (!messages) return ;
20 | if (messages.length === 0) return ;
21 | console.log(
22 | `%c CHANNEL render: ${channel.name}`,
23 | "background-color: purple; color: white"
24 | );
25 | return (
26 |
27 |
28 |
32 | {messages.map((m: IMessage) => (
33 |
39 | ))}
40 |
41 |
42 |
43 |
44 | );
45 | };
46 | export default Channel;
47 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Channel/Footer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { IChannel } from "@shlack/types";
3 |
4 | const Footer: React.FunctionComponent<{ channel: IChannel }> = ({
5 | channel: { name: channelName },
6 | }) => (
7 |
49 | );
50 |
51 | export default Footer;
52 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Channel/Header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Header: React.FunctionComponent<{
4 | title: string;
5 | description: string;
6 | }> = ({ title, description }) => (
7 |
18 | );
19 |
20 | export default Header;
21 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Channel/Message.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { IUser } from "@shlack/types";
3 | import { formatTimestamp } from "@shlack/utils";
4 |
5 | const Message: React.FunctionComponent<{
6 | user: IUser;
7 | date: Date;
8 | body: string;
9 | }> = ({ user, date, body }) => (
10 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
28 | {user.name}
29 |
30 | at
31 |
32 | {formatTimestamp(date)}
33 |
34 |
35 |
36 |
{body}
37 |
38 |
39 |
43 | 🗑
44 |
45 |
46 | );
47 |
48 | export default Message;
49 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Loading: React.FunctionComponent<{ message: string }> = ({
4 | message = "Loading...",
5 | children,
6 | }) => (
7 |
8 | {message}...
9 | {children}
10 |
11 | );
12 | export default Loading;
13 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/SelectedChannel.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { match } from "react-router";
3 | import { IChannel } from "@shlack/types";
4 | import Channel from "./Channel";
5 |
6 | const SelectedChannel: React.FunctionComponent<{
7 | match: match<{ channelId: string }>;
8 | channels: IChannel[];
9 | }> = ({ match, channels }) => {
10 | if (!channels) throw new Error("no channels");
11 | if (!match) throw new Error("no match");
12 |
13 | const { params } = match;
14 | if (!match) return No match params
;
15 | const { channelId: selectedChannelId } = params;
16 | if (!selectedChannelId) return Invalid channelId
;
17 | const selectedChannel = channels.find(
18 | (c: IChannel) => c.id === selectedChannelId
19 | );
20 | if (!selectedChannel)
21 | return (
22 |
23 |
Could not find channel with id {selectedChannelId}
24 |
{JSON.stringify(channels, null, " ")}
25 |
26 | );
27 |
28 | return ;
29 | };
30 |
31 | export default SelectedChannel;
32 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/SelectedTeam.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { match } from "react-router";
3 | import type { ITeam } from "@shlack/types";
4 | import Team from "./Team";
5 |
6 | const SelectedTeam: React.FunctionComponent<{
7 | match: match<{ teamId: string }>;
8 | teams: ITeam[];
9 | }> = ({ match, teams }) => {
10 | if (!match) throw new Error("no match");
11 |
12 | const { params } = match;
13 | if (!params) throw new Error("no match params");
14 |
15 | const { teamId: selectedTeamId } = params;
16 | if (!selectedTeamId) throw new Error(`undefined teamId`);
17 |
18 | const selectedTeam = teams.find((t: ITeam) => t.id === selectedTeamId);
19 | if (!selectedTeam)
20 | throw new Error(`Invalid could not find team with id {selectedTeamId}`);
21 |
22 | return ;
23 | };
24 |
25 | export default SelectedTeam;
26 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/Team.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { match, Route, Switch } from "react-router-dom";
3 | import type { ITeam } from "@shlack/types";
4 | import SelectedChannel from "./SelectedChannel";
5 | import TeamSidebar from "./TeamSidebar";
6 |
7 | const Team: React.FunctionComponent<{ team: ITeam }> = ({ team }) => {
8 | console.log(
9 | `%c TEAM render: ${team.name}`,
10 | "background-color: blue; color: white"
11 | );
12 | const { channels } = team;
13 | return (
14 |
15 |
16 |
17 |
18 | Please select a channel
19 |
20 | }) => (
24 |
25 | )}
26 | />
27 |
28 |
29 | );
30 | };
31 | export default Team;
32 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/TeamSelector.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { ITeam } from "@shlack/types";
3 | import TeamLink from "./TeamSelector/TeamLink";
4 |
5 | const TeamSelector: React.FunctionComponent<{ teams: ITeam[] }> = ({
6 | teams,
7 | }) => (
8 |
9 | {teams.map((team: ITeam) => {
10 | const { id, ...rest } = team;
11 | return ;
12 | })}
13 |
14 |
26 |
27 | );
28 |
29 | export default TeamSelector;
30 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/TeamSelector/TeamLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link, useRouteMatch } from "react-router-dom";
3 | import { ITeam } from "@shlack/types";
4 |
5 | const TeamLink: React.FunctionComponent<{ team: ITeam }> = ({ team }) => {
6 | const match = useRouteMatch({
7 | path: `/team/${team.id}`,
8 | exact: false,
9 | });
10 |
11 | return (
12 |
19 |
20 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default TeamLink;
31 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/TeamSidebar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { IChannel, ITeam } from "@shlack/types";
3 | import ChannelLink from "./TeamSidebar/ChannelLink";
4 |
5 | /**
6 | * Team Sidebar
7 | *
8 | * @public
9 | * @param param0 - props
10 | */
11 | const TeamSidebar: React.FunctionComponent<{ team: ITeam }> = ({ team }) => {
12 | return (
13 |
14 |
47 |
48 |
49 |
50 |
Channels
51 |
52 |
57 |
63 |
64 |
65 |
66 |
67 | {team.channels.map((ch: IChannel) => (
68 |
73 | ))}
74 |
75 |
76 |
77 | Logout
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default TeamSidebar;
85 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/components/TeamSidebar/ChannelLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link, useRouteMatch } from "react-router-dom";
3 | import { IChannel } from "@shlack/types";
4 |
5 | const ChannelLink: React.FunctionComponent<{
6 | to: string;
7 | channel: IChannel;
8 | }> = ({ to, channel }) => {
9 | const match = useRouteMatch(to);
10 | return (
11 |
18 | #
19 | {channel.name}
20 |
21 | );
22 | };
23 |
24 | export default ChannelLink;
25 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/src/index.ts:
--------------------------------------------------------------------------------
1 | import "regenerator-runtime/runtime";
2 | import * as React from "react";
3 | import { render } from "react-dom";
4 | import App from "./App";
5 |
6 | function initializeReactApp() {
7 | const appContainer = document.getElementById("appContainer");
8 | if (!appContainer) throw new Error("No #appContainer found in DOM");
9 | render(React.createElement(App), appContainer);
10 | }
11 |
12 | initializeReactApp();
13 |
14 | export { default as Channel } from "./components/Channel";
15 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/Channel.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as renderer from "react-test-renderer";
3 | import Channel from "../../src/components/Channel";
4 |
5 | test("Link changes the class when hovered", () => {
6 | const component = renderer.create(
7 |
15 | );
16 | const tree = component.toJSON();
17 | expect(tree).toMatchSnapshot();
18 | });
19 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/ChannelFooter.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as renderer from "react-test-renderer";
3 | import Footer from "../../src/components/Channel/Footer";
4 |
5 | test("Link changes the class when hovered", () => {
6 | const component = renderer.create(
7 |
10 | );
11 | const tree = component.toJSON();
12 | expect(tree).toMatchSnapshot();
13 | });
14 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/ChannelHeader.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as renderer from "react-test-renderer";
3 | import Header from "../../src/components/Channel/Header";
4 |
5 | test("Link changes the class when hovered", () => {
6 | const component = renderer.create(
7 |
8 | );
9 | const tree = component.toJSON();
10 | expect(tree).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/ChannelMessage.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as renderer from "react-test-renderer";
3 | import Message from "../../src/components/Channel/Message";
4 |
5 | test("Link changes the class when hovered", () => {
6 | const component = renderer.create(
7 |
12 | );
13 | const tree = component.toJSON();
14 | expect(tree).toMatchSnapshot();
15 | });
16 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/TeamSelector.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BrowserRouter as Router } from "react-router-dom";
3 | import * as renderer from "react-test-renderer";
4 | import TeamSelector from "../../src/components/TeamSelector";
5 |
6 | test("Link changes the class when hovered", () => {
7 | const component = renderer.create(
8 |
9 |
26 |
27 | );
28 | const tree = component.toJSON();
29 | expect(tree).toMatchSnapshot();
30 | });
31 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/TeamSidebar.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BrowserRouter as Router } from "react-router-dom";
3 | import * as renderer from "react-test-renderer";
4 | import TeamSidebar from "../../src/components/TeamSidebar";
5 |
6 | test("Link changes the class when hovered", () => {
7 | const component = renderer.create(
8 |
9 |
24 |
25 | );
26 | const tree = component.toJSON();
27 | expect(tree).toMatchSnapshot();
28 | });
29 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/Channel.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
5 | Loading messages
6 | ...
7 |
8 | `;
9 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/ChannelFooter.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
52 | `;
53 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/ChannelHeader.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
27 | `;
28 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/ChannelMessage.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
8 |
11 |
16 |
17 |
20 |
23 |
27 | Mike
28 |
29 |
32 | at
33 |
34 |
37 | Jan 01, 2001 00:01:00 AM
38 |
39 |
40 |
43 | Hello world!
44 |
45 |
46 |
50 | 🗑
51 |
52 |
53 | `;
54 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/TeamSelector.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
7 |
12 |
15 |
20 |
21 |
22 |
40 |
41 | `;
42 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/components/__snapshots__/TeamSidebar.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link changes the class when hovered 1`] = `
4 |
105 | `;
106 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "references": [{ "path": ".." }],
3 | "compilerOptions": {
4 | "types": ["jest"],
5 | "baseUrl": "..",
6 | "jsx": "react",
7 | "noEmit": true
8 | },
9 | "include": ["."]
10 | }
11 |
--------------------------------------------------------------------------------
/COURSE_FILES/09-internal-dependents/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 | "references": [
4 | { "path": "../types" },
5 | { "path": "../utils" },
6 | { "path": "../data" }
7 | ],
8 | "compilerOptions": {
9 | "composite": true,
10 | "outDir": "dist",
11 | "rootDir": "src",
12 | "jsx": "react"
13 | },
14 | "include": ["src"]
15 | }
16 |
--------------------------------------------------------------------------------
/COURSE_FILES/11-api-report/api-extractor-base.json:
--------------------------------------------------------------------------------
1 | /**
2 | * Config file for API Extractor. For more info, please visit: https://api-extractor.com
3 | */
4 | {
5 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
6 |
7 | /**
8 | * Optionally specifies another JSON config file that this file extends from. This provides a way for
9 | * standard settings to be shared across multiple projects.
10 | *
11 | * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
12 | * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
13 | * resolved using NodeJS require().
14 | *
15 | * SUPPORTED TOKENS: none
16 | * DEFAULT VALUE: ""
17 | */
18 | // "extends": "./shared/api-extractor-base.json"
19 | // "extends": "my-package/include/api-extractor-base.json"
20 |
21 | /**
22 | * Determines the "" token that can be used with other config file settings. The project folder
23 | * typically contains the tsconfig.json and package.json config files, but the path is user-defined.
24 | *
25 | * The path is resolved relative to the folder of the config file that contains the setting.
26 | *
27 | * The default value for "projectFolder" is the token "", which means the folder is determined by traversing
28 | * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
29 | * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
30 | * will be reported.
31 | *
32 | * SUPPORTED TOKENS:
33 | * DEFAULT VALUE: ""
34 | */
35 | // "projectFolder": "..",
36 |
37 | /**
38 | * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
39 | * analyzes the symbols exported by this module.
40 | *
41 | * The file extension must be ".d.ts" and not ".ts".
42 | *
43 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
44 | * prepend a folder token such as "".
45 | *
46 | * SUPPORTED TOKENS: , ,
47 | */
48 | "mainEntryPointFilePath": "/lib/index.d.ts",
49 |
50 | /**
51 | * A list of NPM package names whose exports should be treated as part of this package.
52 | *
53 | * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
54 | * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
55 | * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
56 | * imports library2. To avoid this, we can specify:
57 | *
58 | * "bundledPackages": [ "library2" ],
59 | *
60 | * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
61 | * local files for library1.
62 | */
63 | "bundledPackages": [],
64 |
65 | /**
66 | * Determines how the TypeScript compiler engine will be invoked by API Extractor.
67 | */
68 | "compiler": {
69 | /**
70 | * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
71 | *
72 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
73 | * prepend a folder token such as "".
74 | *
75 | * Note: This setting will be ignored if "overrideTsconfig" is used.
76 | *
77 | * SUPPORTED TOKENS: , ,
78 | * DEFAULT VALUE: "/tsconfig.json"
79 | */
80 | // "tsconfigFilePath": "/tsconfig.json",
81 | /**
82 | * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
83 | * The object must conform to the TypeScript tsconfig schema:
84 | *
85 | * http://json.schemastore.org/tsconfig
86 | *
87 | * If omitted, then the tsconfig.json file will be read from the "projectFolder".
88 | *
89 | * DEFAULT VALUE: no overrideTsconfig section
90 | */
91 | // "overrideTsconfig": {
92 | // . . .
93 | // }
94 | /**
95 | * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
96 | * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
97 | * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
98 | * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
99 | *
100 | * DEFAULT VALUE: false
101 | */
102 | // "skipLibCheck": true,
103 | },
104 |
105 | /**
106 | * Configures how the API report file (*.api.md) will be generated.
107 | */
108 | "apiReport": {
109 | /**
110 | * (REQUIRED) Whether to generate an API report.
111 | */
112 | "enabled": true
113 |
114 | /**
115 | * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
116 | * a full file path.
117 | *
118 | * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
119 | *
120 | * SUPPORTED TOKENS: ,
121 | * DEFAULT VALUE: ".api.md"
122 | */
123 | // "reportFileName": ".api.md",
124 |
125 | /**
126 | * Specifies the folder where the API report file is written. The file name portion is determined by
127 | * the "reportFileName" setting.
128 | *
129 | * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
130 | * e.g. for an API review.
131 | *
132 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
133 | * prepend a folder token such as "".
134 | *
135 | * SUPPORTED TOKENS: , ,
136 | * DEFAULT VALUE: "/etc/"
137 | */
138 | // "reportFolder": "/etc/",
139 |
140 | /**
141 | * Specifies the folder where the temporary report file is written. The file name portion is determined by
142 | * the "reportFileName" setting.
143 | *
144 | * After the temporary file is written to disk, it is compared with the file in the "reportFolder".
145 | * If they are different, a production build will fail.
146 | *
147 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
148 | * prepend a folder token such as "".
149 | *
150 | * SUPPORTED TOKENS: , ,
151 | * DEFAULT VALUE: "/temp/"
152 | */
153 | // "reportTempFolder": "/temp/"
154 | },
155 |
156 | /**
157 | * Configures how the doc model file (*.api.json) will be generated.
158 | */
159 | "docModel": {
160 | /**
161 | * (REQUIRED) Whether to generate a doc model file.
162 | */
163 | "enabled": true,
164 |
165 | /**
166 | * The output path for the doc model file. The file extension should be ".api.json".
167 | *
168 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
169 | * prepend a folder token such as "".
170 | *
171 | * SUPPORTED TOKENS: , ,
172 | * DEFAULT VALUE: "/temp/.api.json"
173 | */
174 | "apiJsonFilePath": "/../../temp/.api.json"
175 | },
176 |
177 | /**
178 | * Configures how the .d.ts rollup file will be generated.
179 | */
180 | "dtsRollup": {
181 | /**
182 | * (REQUIRED) Whether to generate the .d.ts rollup file.
183 | */
184 | "enabled": true,
185 |
186 | /**
187 | * Specifies the output path for a .d.ts rollup file to be generated without any trimming.
188 | * This file will include all declarations that are exported by the main entry point.
189 | *
190 | * If the path is an empty string, then this file will not be written.
191 | *
192 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
193 | * prepend a folder token such as "".
194 | *
195 | * SUPPORTED TOKENS: , ,
196 | * DEFAULT VALUE: "/dist/.d.ts"
197 | */
198 | "untrimmedFilePath": "/dist/-private.d.ts",
199 |
200 | /**
201 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
202 | * This file will include only declarations that are marked as "@public" or "@beta".
203 | *
204 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
205 | * prepend a folder token such as "".
206 | *
207 | * SUPPORTED TOKENS: , ,
208 | * DEFAULT VALUE: ""
209 | */
210 | "betaTrimmedFilePath": "/dist/-beta.d.ts",
211 |
212 | /**
213 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
214 | * This file will include only declarations that are marked as "@public".
215 | *
216 | * If the path is an empty string, then this file will not be written.
217 | *
218 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
219 | * prepend a folder token such as "".
220 | *
221 | * SUPPORTED TOKENS: , ,
222 | * DEFAULT VALUE: ""
223 | */
224 | "publicTrimmedFilePath": "/dist/.d.ts"
225 |
226 | /**
227 | * When a declaration is trimmed, by default it will be replaced by a code comment such as
228 | * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
229 | * declaration completely.
230 | *
231 | * DEFAULT VALUE: false
232 | */
233 | // "omitTrimmingComments": true
234 | },
235 |
236 | /**
237 | * Configures how the tsdoc-metadata.json file will be generated.
238 | */
239 | "tsdocMetadata": {
240 | /**
241 | * Whether to generate the tsdoc-metadata.json file.
242 | *
243 | * DEFAULT VALUE: true
244 | */
245 | // "enabled": true,
246 | /**
247 | * Specifies where the TSDoc metadata file should be written.
248 | *
249 | * The path is resolved relative to the folder of the config file that contains the setting; to change this,
250 | * prepend a folder token such as "".
251 | *
252 | * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata",
253 | * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
254 | * falls back to "tsdoc-metadata.json" in the package folder.
255 | *
256 | * SUPPORTED TOKENS: , ,
257 | * DEFAULT VALUE: ""
258 | */
259 | // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json"
260 | },
261 |
262 | /**
263 | * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
264 | * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
265 | * To use the OS's default newline kind, specify "os".
266 | *
267 | * DEFAULT VALUE: "crlf"
268 | */
269 | // "newlineKind": "crlf",
270 |
271 | /**
272 | * Configures how API Extractor reports error and warning messages produced during analysis.
273 | *
274 | * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
275 | */
276 | "messages": {
277 | /**
278 | * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
279 | * the input .d.ts files.
280 | *
281 | * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
282 | *
283 | * DEFAULT VALUE: A single "default" entry with logLevel=warning.
284 | */
285 | "compilerMessageReporting": {
286 | /**
287 | * Configures the default routing for messages that don't match an explicit rule in this table.
288 | */
289 | "default": {
290 | /**
291 | * Specifies whether the message should be written to the the tool's output log. Note that
292 | * the "addToApiReportFile" property may supersede this option.
293 | *
294 | * Possible values: "error", "warning", "none"
295 | *
296 | * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
297 | * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
298 | * the "--local" option), the warning is displayed but the build will not fail.
299 | *
300 | * DEFAULT VALUE: "warning"
301 | */
302 | "logLevel": "warning"
303 |
304 | /**
305 | * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
306 | * then the message will be written inside that file; otherwise, the message is instead logged according to
307 | * the "logLevel" option.
308 | *
309 | * DEFAULT VALUE: false
310 | */
311 | // "addToApiReportFile": false
312 | }
313 |
314 | // "TS2551": {
315 | // "logLevel": "warning",
316 | // "addToApiReportFile": true
317 | // },
318 | //
319 | // . . .
320 | },
321 |
322 | /**
323 | * Configures handling of messages reported by API Extractor during its analysis.
324 | *
325 | * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
326 | *
327 | * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
328 | */
329 | "extractorMessageReporting": {
330 | "default": {
331 | "logLevel": "warning"
332 | // "addToApiReportFile": false
333 | }
334 |
335 | // "ae-extra-release-tag": {
336 | // "logLevel": "warning",
337 | // "addToApiReportFile": true
338 | // },
339 | //
340 | // . . .
341 | },
342 |
343 | /**
344 | * Configures handling of messages reported by the TSDoc parser when analyzing code comments.
345 | *
346 | * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
347 | *
348 | * DEFAULT VALUE: A single "default" entry with logLevel=warning.
349 | */
350 | "tsdocMessageReporting": {
351 | "default": {
352 | "logLevel": "warning"
353 | // "addToApiReportFile": false
354 | }
355 |
356 | // "tsdoc-link-tag-unescaped-text": {
357 | // "logLevel": "warning",
358 | // "addToApiReportFile": true
359 | // },
360 | //
361 | // . . .
362 | }
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/COURSE_FILES/11-api-report/packages/PKG/api-extractor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3 | "mainEntryPointFilePath": "/dist/index.d.ts",
4 | "extends": "../../api-extractor-base.json"
5 | }
6 |
--------------------------------------------------------------------------------
/COURSE_FILES/11-api-report/scripts/packages/api-report.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 🧩 API REPORT: $(pwd) ━━━━━━━━━━━━━━━━━━━━━"
3 | yarn api-extractor run --local
4 |
--------------------------------------------------------------------------------
/COURSE_FILES/12-api-docs/scripts/workspace/api-docs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "┏━━━ 📚 API DOCS: Extracting API surface ━━━━━━━━━━━━━━"
3 | yarn clean
4 | yarn tsc -b packages
5 | yarn lerna run api-report;
6 | echo "┏━━━ 📝 API DOCS: Generating Markdown Docs ━━━━━━━━━━━━"
7 | GH_PAGES_CFG_EXISTS=$(test -f docs/_config.yml)
8 | if [ $GH_PAGES_CFG_EXISTS ]
9 | then
10 | echo "GitHub pages config file DETECTED"
11 | cp docs/_config.yml .
12 | fi
13 |
14 | yarn api-documenter markdown -i temp -o docs
15 |
16 | if [ $GH_PAGES_CFG_EXISTS ]
17 | then
18 | cp _config.yml docs/_config.yml
19 | fi
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Mike North. All rights reserved.
2 |
3 | Redistribution and use in
4 | source and binary forms, with or without modification, are permitted provided
5 | that the following conditions are met:
6 |
7 | 1. Redistributions of source code must
8 | retain the above copyright notice, this list of conditions and the following
9 | disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above
12 | copyright notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 |
15 | THIS
16 | SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
25 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JS/TS Monorepos
2 |
3 | [/badge.svg)](https://github.com/mike-north/js-ts-monorepos/actions?query=workflow%3A%22Node.js+CI+%28solution%29%22)
4 | [/badge.svg)](https://github.com/mike-north/js-ts-monorepos/actions?query=workflow%3A%22TypeScript%40Next+tests+%28solution%29%22)
5 |
6 | ## What's this course about?
7 |
8 | This course is intended teach those already somewhat familiar with modern
9 | JavaScript and TypeScript about monorepos, their use cases and related tools.
10 | Mike shares some of the experience he's had as LinkedIn's TypeScript infrastructure lead, so you don't have to learn things "the hard way" like he did.
11 |
12 | ## Project setup
13 |
14 | First, you should ensure you have [your ssh keys working with GitHub](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). You can verify this by running
15 |
16 | ```sh
17 | ssh git@github.com
18 | ```
19 |
20 | and getting a response like
21 |
22 | ```sh
23 | Hi mike-north! You've successfully authenticated, but GitHub does not provide shell access.
24 | Connection to github.com closed.
25 | ```
26 |
27 | ### Tools
28 |
29 | Next, make sure you have installed [volta](http://volta.sh/) which ensures you have the right version of node and yarn for this project. You can run:
30 | ```
31 | volta install node
32 | ```
33 | and then:
34 | ```
35 | volta install yarn
36 | ```
37 | To get the right versions for this workshop.
38 |
39 | We also strongly recommend the use of [Visual Studio Code](https://code.visualstudio.com/) as an authoring tool. If you use something else, you're on your own.
40 |
41 | ### Clone
42 |
43 | Next, checkout a working copy of this project
44 |
45 | ```sh
46 | git clone git@github.com:mike-north/js-ts-monorepos
47 | ```
48 |
49 | enter the directory you just created
50 |
51 | ```sh
52 | cd js-ts-monorepos
53 | ```
54 |
55 | ### Install dependencies
56 |
57 | [`yarn`](https://yarnpkg.com/) is the recommended package manager to use with this project. Please use it instead of npm.
58 |
59 | Install dependencies with yarn by running
60 |
61 | ```sh
62 | yarn
63 | ```
64 |
65 | ### Starting the project
66 |
67 | Start up the project in development mode by running
68 |
69 | ```sh
70 | yarn dev
71 | ```
72 |
73 | Changing any files in the `src` folder will result in an incremental rebuild, and a refresh of the screen.
74 |
75 | By default, the app is served on https://localhost:1234.
76 |
77 | # Legal
78 |
79 | © 2020 LinkedIn, All Rights Reserved
80 |
81 | ## Licensing
82 |
83 | The code in this project is licensed as [BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) license, and the written content in the ./notes folder is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
84 |
--------------------------------------------------------------------------------
/notes/00-intro.md:
--------------------------------------------------------------------------------
1 | # Welcome to JS/TS Monorepos
2 |
3 | A "monorepo" is a term used to describe packages (usually multiple)
4 | within a single repository. In the words of @dherman..
5 |
6 |
7 |
8 |
9 |
10 | Thankfully, in the JavaScript and TypeScript ecosystems, this
11 | term is usually used in reference to the idea of
12 |
13 | > _many packages, related by a shared purpose and (usually) deep entanglement_ located in a single git repo.
14 |
15 | For this course, we'll take this as the canonical definition.
16 |
17 | ---
18 |
19 |
20 | Next: Yarn Workspaces ▶
21 |
22 |
--------------------------------------------------------------------------------
/notes/01-yarn-workspaces.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Intro
3 |
4 |
5 | ---
6 |
7 | # Yarn Workspaces
8 |
9 | Yarn provides us with the lowest level of monorepo tooling, through Workspaces.
10 |
11 | ## Setting up workspaces
12 |
13 | To go from our starting point code to a "yarn workspaces" enabled project, we need only add a `workspaces` field to our [`package.json`](../package.json)
14 |
15 | ```diff
16 | "repository": "git@github.com:mike-north/js-ts-monorepos.git",
17 | "author": "Mike North ",
18 | "license": "BSD-2-Clause",
19 | + "private": true,
20 | + "workspaces": [
21 | + "packages/*"
22 | + ],
23 | ```
24 |
25 | You should also run
26 |
27 | ```sh
28 | volta pin node yarn
29 | ```
30 |
31 | which should result in another change being made to your [`package.json`](../package.json)
32 |
33 | ```diff
34 | "repository": "git@github.com:mike-north/js-ts-monorepos.git",
35 | "author": "Mike North ",
36 | "license": "BSD-2-Clause",
37 | "private": true,
38 | "workspaces": [
39 | "packages/*"
40 | + ],
41 | + "volta": {
42 | + "node": "12.19.0",
43 | + "yarn": "1.22.10"
44 | + },
45 | ```
46 |
47 | ## Copying in our first two packages
48 |
49 | In the [`COURSE_FILES/01-yarn-workspaces/`](../COURSE_FILES/01-yarn-workspaces/) folder, you should find a folder called `types`
50 |
51 | Make a new folder [`/packages`](../packages)
52 |
53 | ```sh
54 | mkdir packages
55 | ```
56 |
57 | and copy the `types` folder into it, so you have something that looks kind of like
58 |
59 | ```
60 | packages/
61 | types/
62 | src/
63 | tests/
64 | ```
65 |
66 | [`types`](../packages/types) will become your first package: `@shlack/types`.
67 |
68 | It needs a `package.json`, so create one
69 |
70 | ### [`types/package.json`](../packages/types/package.json)
71 |
72 | ```json
73 | {
74 | "name": "@shlack/types",
75 | "version": "0.0.1",
76 | "main": "dist/index.js",
77 | "types": "dist/index.d.ts",
78 | "publishConfig": {
79 | "access": "public"
80 | },
81 | "scripts": {
82 | "build": "tsc -b ."
83 | },
84 | "devDependencies": {
85 | "typescript": "^4.0.3"
86 | }
87 | }
88 | ```
89 |
90 | This is a TypeScript project, so you'll need a `tsconfig.json`
91 |
92 | ### [`types/tsconfig.json`](../packages/types/tsconfig.json)
93 |
94 | ```json
95 | {
96 | "compilerOptions": {
97 | "module": "CommonJS",
98 | "types": [],
99 | "sourceMap": true,
100 | "target": "ES2018",
101 | "strict": true,
102 | "noUnusedLocals": true,
103 | "noUnusedParameters": true,
104 | "noImplicitReturns": true,
105 | "declaration": true,
106 | "outDir": "dist",
107 | "rootDir": "src"
108 | },
109 | "include": ["src"]
110 | }
111 | ```
112 |
113 | Finally run `yarn` to install and link dependencies, and then try to build the `@shlack/types` package from within its directory.
114 |
115 | ```sh
116 | cd packages/types
117 | yarn build
118 | ```
119 |
120 | you should that a [`packages/types/dist`](../packages/types/dist) is created for you, and there are a few `.js` and `.d.ts` files within it (your build output)
121 |
122 | ---
123 |
124 |
125 | Next: Composite Projects ▶
126 |
127 |
--------------------------------------------------------------------------------
/notes/02-composite-project.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Yarn Workspaces
3 |
4 |
5 | ---
6 |
7 | # Composite Project
8 |
9 | TypeScript supports the concept of several sub-projects that _refer_ to eachother's build output. This will make the most frequently-performed tasks much faster (i.e., an incremental build) so it's in our interest to set this up.
10 |
11 | ## A second package
12 |
13 | Before we do this, we need another package, so that we have a few things that can refer to each other.
14 |
15 | in your [`COURSE_FILES/02-second-package/`](../COURSE_FILES/02-second-package/) folder, you should find a `utils/` subfolder. Copy this into the project's [`packages/`](../packages/) folder so you have something like
16 |
17 | ```
18 | packages/
19 | types/
20 | src/
21 | tests/
22 | utils/
23 | src/
24 | tests/
25 | ```
26 |
27 | This new `@shlack/utils` package will need a `package.json`, so let's create one
28 |
29 | ### [`packages/utils/package.json`](../packages/utils/package.json)
30 |
31 | ```json
32 | {
33 | "name": "@shlack/utils",
34 | "version": "0.0.1",
35 | "main": "dist/index.js",
36 | "types": "dist/index.d.ts",
37 | "publishConfig": {
38 | "access": "public"
39 | },
40 | "scripts": {
41 | "build": "tsc -b ."
42 | },
43 | "devDependencies": {
44 | "@types/date-fns": "^2.6.0",
45 | "@types/react": "^16.9.52",
46 | "typescript": "^4.0.3"
47 | },
48 | "dependencies": {
49 | "date-fns": "^2.16.1",
50 | "react": "^16.14.0"
51 | }
52 | }
53 | ```
54 |
55 | we'll need a `tsconfig.json` as well, and it'll be almost _exactly_ the same as the one for our `@shlack/types` package from the last step. Repetition is annoying (and an "out of sync" bug waiting to happen), so let's set up one _meaningful_ tsconfig, and extend from it in multiple places.
56 |
57 | ### [`packages/tsconfig.settings.json`](../packages/tsconfig.settings.json)
58 |
59 | ```json
60 | {
61 | "compilerOptions": {
62 | "module": "CommonJS",
63 | "types": [],
64 | "sourceMap": true,
65 | "target": "ES2018",
66 | "strict": true,
67 | "noUnusedLocals": true,
68 | "noUnusedParameters": true,
69 | "noImplicitReturns": true,
70 | "declaration": true
71 | }
72 | }
73 | ```
74 |
75 | and then create a `utils/tsconfig.json`
76 |
77 | ```json
78 | {
79 | "extends": "../tsconfig.settings.json",
80 | "compilerOptions": {
81 | "composite": true,
82 | "outDir": "dist",
83 | "rootDir": "src"
84 | },
85 | "include": ["src"]
86 | }
87 | ```
88 |
89 | and then update your `types/tsconfig.json` so that it exactly matches
90 |
91 | ```diff
92 | @@ -1,14 +1,7 @@
93 | {
94 | + "extends": "../tsconfig.settings.json",
95 | "compilerOptions": {
96 | - "module": "CommonJS",
97 | - "types": [],
98 | - "sourceMap": true,
99 | - "target": "ES2018",
100 | - "strict": true,
101 | - "noUnusedLocals": true,
102 | - "noUnusedParameters": true,
103 | - "noImplicitReturns": true,
104 | - "declaration": true,
105 | + "composite": true,
106 | "outDir": "dist",
107 | "rootDir": "src"
108 | ```
109 |
110 | Finally create a [`packages/tsconfig.json`](../packages/tsconfig.json) that _refers_ to each package
111 |
112 | ```json
113 | {
114 | "files": [],
115 | "references": [{ "path": "utils" }, { "path": "types" }]
116 | }
117 | ```
118 |
119 | try running this from the root of your project now
120 |
121 | ```sh
122 | yarn tsc -b packages
123 | ```
124 |
125 | both `types` and `utils` should build!
126 |
127 | ## Squeaky Clean
128 |
129 | Each package we create will put its build output in its own folder, so we should set ourselves up for an easy way to destroy it and "build again from scratch".
130 |
131 | We can install a _workspace_ dependency (at the root of the project, not a dependency of any package) to handle this in a platform-independent way:
132 |
133 | ```sh
134 | yarn add -WD rimraf
135 | ```
136 |
137 | Then, go to `types/package.json` and `utils/package.json` and make this small change
138 |
139 | ```diff
140 | @@ -7,6 +7,7 @@
141 | "access": "public"
142 | },
143 | "scripts": {
144 | + "clean": "rimraf dist *.tsbuildinfo",
145 | "build": "tsc -b ."
146 | },
147 | ```
148 |
149 | Now, we can go to either of these projects and run `yarn clean` to delete build output, and `yarn build` to make a fresh build
150 |
151 | ---
152 |
153 |
154 | Next: Tests ▶
155 |
156 |
--------------------------------------------------------------------------------
/notes/03-tests.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Composite Projects
3 |
4 |
5 | ---
6 |
7 | # Tests
8 |
9 | Jest is a great testing tool, and it's really simple to set up for use with JavaScript, TypeScript, React -- pretty much whatever you like. It's nice to have a test runner that can handle a wide range of applications, because it means we're less likely to outgrow it when we run into a new use case
10 |
11 | Go to each of the subfolders of `packages/` and run
12 |
13 | ```sh
14 | yarn add -D @babel/preset-env @babel/preset-typescript @types/jest jest
15 | ```
16 |
17 | Jest uses babel to convert TS to JS, so we'll need to create a few babel config files.
18 |
19 | ### [`packages/.babelrc`](../packages/.babelrc)
20 |
21 | ```json
22 | {
23 | "presets": [
24 | ["@babel/preset-env", { "targets": { "node": "10" } }],
25 | "@babel/preset-typescript"
26 | ]
27 | }
28 | ```
29 |
30 | ### [`packages/types/.babelrc`](../packages/types/.babelrc)
31 |
32 | ```json
33 | {
34 | "extends": "../.babelrc"
35 | }
36 | ```
37 |
38 | ### [`packages/utils/.babelrc`](../packages/utils/.babelrc)
39 |
40 | ```json
41 | {
42 | "extends": "../.babelrc"
43 | }
44 | ```
45 |
46 | Once again we're applying the pattern of "thin config files" that all extend from a more meaningful source of truth.
47 |
48 | run `yarn` to install those new dependencies, and then from within your `packages/types` and `packages/utils` folder you should be able to run
49 |
50 | ```sh
51 | yarn jest
52 | ```
53 |
54 | and see your tests run.
55 |
56 | Add a new "scripts" entry to each package's `package.json`, so that we can just run `yarn test` on any package and it'll do _whatever testing means for that package_.
57 |
58 | ````diff
59 | ```diff
60 | @@ -7,6 +7,7 @@
61 | "access": "public"
62 | },
63 | "scripts": {
64 | "clean": "rimraf dist *.tsbuildinfo",
65 | "build": "tsc -b .",
66 | + "test": "jest",
67 | },
68 | ````
69 |
70 | ---
71 |
72 |
73 | Next: Linting ▶
74 |
75 |
--------------------------------------------------------------------------------
/notes/04-linting.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Tests
3 |
4 |
5 | ---
6 |
7 | # Linting
8 |
9 | We'll use [ESLint](https://eslint.org/) for linting, because it's able
10 | to understand JS, TS, TSX and JSX equally well.
11 |
12 | ## Setting Up ESLint
13 |
14 | In your [`COURSE_FILES/04-linting`](../COURSE_FILES/04-linting) folder, you should find an `.eslintrc` file. Copy it to the root of the project.
15 |
16 | We'll have to install `eslint` and a few plugins now. We install these at the _workspace_ level.
17 |
18 | ```
19 | yarn add -WD eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
20 | ```
21 |
22 | and then each _package root (`packages/*`)_ gets its own "thin" `.eslintrc`
23 |
24 | ```json
25 | {
26 | "extends": "../../.eslintrc",
27 | "parserOptions": {
28 | "project": "tsconfig.json"
29 | }
30 | }
31 | ```
32 |
33 | and a small tweak to _each package's `package.json`_
34 |
35 | ```diff
36 | "access": "public"
37 | },
38 | "scripts": {
39 | + "lint": "eslint src --ext js,ts",
40 | "clean": "rimraf dist",
41 | ```
42 |
43 | you should now be able to go to each package root and run `yarn lint`
44 |
45 | ```sh
46 | cd packages/types
47 | yarn lint # run ESLint
48 | ```
49 |
50 | If you know how to fix TypeScript errors, there are a few in the starter code for you to find and fix on your own.
51 |
52 | ---
53 |
54 |
55 | Next: Lerna ▶
56 |
57 |
--------------------------------------------------------------------------------
/notes/05-lerna.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Linting
3 |
4 |
5 | ---
6 |
7 | # Lerna
8 |
9 | It may feel like we're doing a _lot_ of per-package work, even with just two packages. Imagine how much worse this gets when you have 10 or 100 packages!
10 |
11 | Lerna is designed to make this much easier.
12 |
13 | Let's begin by adding it as a workspace dependency
14 |
15 | ```sh
16 | yarn add -WD lerna
17 | ```
18 |
19 | and creating a `lerna.json` config file at the root of our project
20 |
21 | ```json
22 | {
23 | "packages": ["packages/*"],
24 | "npmClient": "yarn",
25 | "version": "0.0.1",
26 | "useWorkspaces": true,
27 | "nohoist": ["parcel-bundler"]
28 | }
29 | ```
30 |
31 | Finally, run
32 |
33 | ```sh
34 | lerna bootstrap
35 | ```
36 |
37 | If we had dependencies between our existing packages (we do not, yet), this would take care of "linking" everything up.
38 |
39 | Look in `packages/types/node_modules`. See anything interesting?
40 |
41 | Lerna can do a lot for us, but we'll start with one of the most useful aspects of the tool: running a command _in each_ package.
42 |
43 | ```sh
44 | # Go to each package and run `yarn test`
45 | lerna run test
46 | # Go to each package and run `yarn lint`
47 | lerna run lint
48 | ```
49 |
50 | Check out `lerna --help` to get a preview of what else Lerna can do.
51 |
52 | ---
53 |
54 |
55 | Next: Scripty ▶
56 |
57 |
--------------------------------------------------------------------------------
/notes/06-scripty.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Lerna
3 |
4 |
5 | ---
6 |
7 | # Scripty
8 |
9 | Building on our theme of eliminating duplication, we need to do something about the `"scripts": {}` part of each `package/*/package.json`. Scripty allows us to implement these npm-scripts as files.
10 |
11 | Let's install scripty as a workspace dependency
12 |
13 | ```sh
14 | yarn add -WD scripty
15 | ```
16 |
17 | There's a `scripts` folder in [`COURSE_FILES/06-scripty`](../COURSE_FILES/06-scripty) copy it into the project root.
18 |
19 | > **These are `sh` scripts that can be run in any POSIX environment, so if you use windows, you'll need [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (recommended) or [Cigwyn](https://www.cygwin.com/) (if you're desperate).**
20 |
21 | You'll need to make these scripts _executable_
22 |
23 | ```
24 | chmod -R +x scripts
25 | ```
26 |
27 | Update your _workspace_ [`package.json`](../package.json) with...
28 |
29 | ```diff
30 | + "scripts": {
31 | + "build": "scripty",
32 | + "test": "scripty",
33 | + "lint": "scripty",
34 | + "clean": "scripty"
35 | + },
36 | + "scripty": {
37 | + "path": "./scripts/workspace"
38 | + },
39 | ```
40 |
41 | and each package's `package.json` with
42 |
43 | ```diff
44 | },
45 | "scripts": {
46 | - "lint": "eslint src --ext js,ts",
47 | - "clean": "rimraf dist",
48 | - "build": "tsc -b ."
49 | + "lint": "scripty",
50 | + "clean": "rimraf dist *.tsbuildinfo",
51 | + "build": "scripty",
52 | + "test": "scripty"
53 | },
54 | "devDependencies": {
55 | "@babel/preset-env": "^7.12.0",
56 | "@babel/preset-typescript": "^7.12.0",
57 | "@types/jest": "^26.0.14",
58 | "jest": "^26.5.3",
59 | "typescript": "^4.0.3"
60 | },
61 | + "scripty": {
62 | + "path": "../../scripts/packages"
63 | ```
64 |
65 | Look through everything in the `scripts/` folder -- nothing should be all that surprising in there.
66 |
67 | At this point, you should be able to run things like
68 |
69 | ```sh
70 | yarn build # Build *all* packages
71 | yarn test # Run *all* tests
72 | yarn lint # Lint *all* packages
73 | yarn clean # Clean *everything*
74 | ```
75 |
76 | All with centrally defined scripts that are applied to _each package_.
77 |
78 | ---
79 |
80 |
81 | Next: Changelogs ▶
82 |
83 |
--------------------------------------------------------------------------------
/notes/07-changelogs.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Scripty
3 |
4 |
5 | ---
6 |
7 | # Changelogs
8 |
9 | ---
10 |
11 |
12 | Next: Publishing and Versioning ▶
13 |
14 |
--------------------------------------------------------------------------------
/notes/08-publishing-and-versioning.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Changelogs
3 |
4 |
5 | ---
6 |
7 | # Publishing and Versioning
8 |
9 | ---
10 |
11 |
12 | Next: Internal Dependencies ▶
13 |
14 |
--------------------------------------------------------------------------------
/notes/09-internal-dependencies.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Publishing and Versioning
3 |
4 |
5 | ---
6 |
7 | # Internal Dependencies
8 |
9 | ---
10 |
11 |
12 | Next: Independent Versioning ▶
13 |
14 |
--------------------------------------------------------------------------------
/notes/10-independent-versioning.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Internal Dependencies
3 |
4 |
5 | ---
6 |
7 | # Independent Versioning
8 |
9 | ---
10 |
11 |
12 | Next: API Report ▶
13 |
14 |
--------------------------------------------------------------------------------
/notes/11-api-report.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: Independent Versioning
3 |
4 |
5 | ---
6 |
7 | # API Report
8 |
9 | ---
10 |
11 |
12 | Next: API Docs ▶
13 |
14 |
--------------------------------------------------------------------------------
/notes/12-api-docs.md:
--------------------------------------------------------------------------------
1 |
2 | ◀ Back: API Report
3 |
4 |
5 | ---
6 |
7 | # API Documentation
8 |
--------------------------------------------------------------------------------
/notes/README.md:
--------------------------------------------------------------------------------
1 | # JS/TS Monorepos: Course Notes
2 |
3 | Please begin reading at [`00-intro.md`](./00-intro.md)
4 |
--------------------------------------------------------------------------------
/notes/img/dherman-monorepo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-north/js-ts-monorepos/79ee63390ce5138216ea0469035ec7868ae91318/notes/img/dherman-monorepo-1.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-ts-monotrepos",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "repository": "git@github.com:mike-north/js-ts-monorepos.git",
6 | "author": "Mike North ",
7 | "license": "BSD-2-Clause",
8 | "private": true,
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 0"
11 | },
12 | "volta": {
13 | "node": "16.14.2",
14 | "yarn": "1.22.18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "composite": true,
5 | "target": "ES2018",
6 | "strict": true,
7 | "noUnusedLocals": true,
8 | "noUnusedParameters": true,
9 | "noImplicitReturns": true,
10 | "declaration": true
11 | },
12 | "exclude": ["**/node_modules/**", "COURSE_FILES/**"]
13 | }
14 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------