├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── build.yml
├── .gitignore
├── .prettierrc.js
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── action.yml
├── dist
├── README.md
├── index.js
└── licenses.txt
├── jest.config.js
├── media
├── Logo-600.png
├── Logo.png
├── github_readme_learn_section.png
├── notion_full_page_db.png
├── notion_full_page_db_id.png
└── notion_table_schema_options.png
├── package-lock.json
├── package.json
├── scripts
└── codecov.sh
├── src
├── action.ts
├── index.ts
├── types.ts
└── utils
│ ├── checkForSections.ts
│ ├── commitFile.ts
│ ├── constructCategoriesMap.ts
│ ├── constructNewContents.ts
│ ├── fetchData.ts
│ ├── getSchemaEntries.ts
│ ├── index.ts
│ ├── modifyRows.ts
│ └── populateCategoriesMapItems.ts
├── tests
├── action.test.ts
└── utils
│ ├── checkForSections.test.ts
│ ├── constructCategoriesMap.test.ts
│ ├── constructNewContents.test.ts
│ ├── fetchData.test.ts
│ ├── getSchemaEntries.test.ts
│ ├── modifyRows.test.ts
│ └── populateCategoriesMapItems.test.ts
└── tsconfig.json
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '🐛 Bug report'
3 | about: Report a reproducible bug or regression.
4 | ---
5 |
6 | **Describe the bug**
7 | A clear and concise description of what the bug is.
8 | **To Reproduce**
9 | Steps to reproduce the behavior:
10 |
11 | 1. Go to '...'
12 | 2. Click on '....'
13 | 3. Scroll down to '....'
14 | 4. See error
15 | **Expected behavior**
16 | A clear and concise description of what you expected to happen.
17 | **Screenshots**
18 | If applicable, add screenshots to help explain your problem.
19 | **Desktop (please complete the following information):**
20 |
21 | - OS: [e.g. iOS]
22 | - Browser [e.g. chrome, safari]
23 | - Version [e.g. 22]
24 | **Smartphone (please complete the following information):**
25 | - Device: [e.g. iPhone6]
26 | - OS: [e.g. iOS8.1]
27 | - Browser [e.g. stock browser, safari]
28 | - Version [e.g. 22]
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 | **Describe the solution you'd like**
12 | A clear and concise description of what you want to happen.
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 | **Additional context**
16 | Add any other context or screenshots about the feature request here.
17 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Generate build and check code formatting
8 |
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Use Node.js 12.x
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: 12
17 | - run: npm ci
18 | - run: npm run test
19 | - run: npm run build
20 | - name: Uploading test coverage reports
21 | run: |
22 | chmod +x "${GITHUB_WORKSPACE}/scripts/codecov.sh"
23 | "${GITHUB_WORKSPACE}/scripts/codecov.sh"
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | test.js
3 | test.md
4 | build
5 | coverage
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | trailingComma: "none",
3 | tabWidth: 2,
4 | semi: true,
5 | singleQuote: true,
6 | };
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing and Maintaining
2 |
3 | First, thank you for taking the time to contribute!
4 | The following is a set of guidelines for contributors as well as information and instructions around our maintenance process. The two are closely tied together in terms of how we all work together and set expectations, so while you may not need to know everything in here to submit an issue or pull request, it's best to keep them in the same document.
5 |
6 | ## Ways to contribute
7 |
8 | Contributing isn't just writing code - it's anything that improves the project. All contributions are managed right here on GitHub. Here are some ways you can help:
9 |
10 | ### Reporting bugs
11 |
12 | If you're running into an issue, please take a look through [existing issues](/issues) and [open a new one](/issues/new) if needed. If you're able, include steps to reproduce, environment information, and screenshots/screencasts as relevant.
13 |
14 | ### Suggesting enhancements
15 |
16 | New features and enhancements are also managed via [issues](/issues).
17 |
18 | ### Pull requests
19 |
20 | Pull requests represent a proposed solution to a specified problem. They should always reference an issue that describes the problem and contains discussion about the problem itself. Discussion on pull requests should be limited to the pull request itself, i.e. code review.
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Varun Sridharan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | Github Readme Learn Section - Github Action
4 | Automatically update your github README with data fetched from a notion database
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Configuration
14 |
15 | | Option | Description | Required | Default |
16 | | :-----------: | :----------------------------------------------------------------------: | :------: | :-----: |
17 | | `database_id` | Set this to the id of your remote notion database | true | - |
18 | | `token_v2` | Set this to your notion `token_v2` (Required only for private databases) | false | - |
19 |
20 | ## Usage
21 |
22 | ### In Repository File
23 |
24 | #### 1. Add the following content to your `README.md`
25 |
26 | ```markdown
27 | ## What I know so far
28 |
29 |
30 |
31 | ```
32 |
33 | #### 2. Configure the workflow
34 |
35 | ```yaml
36 | name: 'Github Readme Updater'
37 | on:
38 | workflow_dispatch:
39 | schedule:
40 | - cron: '0 0 * * *' # Runs Every Day
41 | jobs:
42 | update_learn:
43 | name: 'Update learn section'
44 | runs-on: ubuntu-latest
45 | steps:
46 | - name: 'Fetching Repository Contents'
47 | uses: actions/checkout@main
48 | - name: 'Learn Section Updater'
49 | uses: 'devorein/github-readme-learn-section-notion@master'
50 | with:
51 | database_id: '6626c1ebc5a44db78e3f2fe285171ab7'
52 | token_v2: ${{ secrets.NOTION_TOKEN_V2 }} # Required only if your database is private
53 | ```
54 |
55 | **TIP**: You can test out using [this template](https://www.notion.so/devorein/6c46c1ebc5a44db78e3f5fe285071ab6?v=0bc36e7c59e54f34b0838956e35b4490) that I've created, or [this repo](https://github.com/Devorein/test-github-action).
56 |
57 | ### In your notion account
58 |
59 | #### 1. Create a full page database
60 |
61 | 
62 |
63 | **NOTE**: Your database must maintain the following structure/schema
64 |
65 | | Name | Type | Required | Default | Description | Value | Example |
66 | | :------: | :----: | :------: | :-----: | :-------------------------------------: | :-----------------------------------------------------------------------------: | :--------------------------------------: |
67 | | Name | title | true | - | The name of the item you've learnt | Must be a valid icon from `https://simple-icons.github.io/simple-icons-website` | React, Typescript |
68 | | Category | select | true | - | The category under which the item falls | Any string | Language, Library |
69 | | Color | text | false | black | Background Color of the badge | Any keyword color or hex value without alpha and # | red,00ff00 |
70 | | Base64 | text | false | "" | Custom base64 of the svg logo | Any base64 encoded svg | data:image/svg%2bxml;base64,PHN2ZyB4b... |
71 |
72 | #### 2. Get the id of the database
73 |
74 | 
75 |
76 | #### 3. Add it in workflow file
77 |
78 | ```yaml
79 | with:
80 | database_id: '6626c1ebc5a44db78e3f2fe285171ab7'
81 | ```
82 |
83 | Follow the rest of the steps only if your database is not public, if its public you don't need to set the token_v2
84 |
85 | #### To make your database public
86 |
87 | 1. Navigate to the database in your notion account
88 | 2. Click on Share at the top right corner
89 | 3. Click on Share to Web button.
90 |
91 | #### 1. Get your notion `token_v2`
92 |
93 | **NOTE**: By no means should you share or expose your notion `token_v2`. If you feel like you've done so accidentally, immediately log out from that account in all of your devices.
94 |
95 | Follow the steps below to obtain your `token_v2`:
96 |
97 | 1. Open up the devtools of your preferred browser.
98 | 2. Go to the Application > Cookies section.
99 | 3. There you'll find a `token_v2` cookie.
100 |
101 | **NOTE**: Its highly recommended to store your `token_v2` as a github secret rather than pasting it in your workflow file. And if you want to embed it in your workflow file make sure unauthorized sources can't access/view it.
102 |
103 | #### 2. Create a github secret to store `token_v2`
104 |
105 | 1. navigate to the url `https://github.com///settings/secrets/actions`
106 | 2. Click on `New repository secret`
107 | 3. You can name your secret as anything you want
108 | 4. Paste the `token_v2` value in the `Value` textarea
109 | 5. Use the secret in your workflow file
110 |
111 | ```yaml
112 | with:
113 | token_v2: ${{ secrets.NOTION_TOKEN_V2 }} # The secret was named NOTION_TOKEN_V2
114 | ```
115 |
116 | ### Outcome
117 |
118 | If you follow all the steps properly your readme should look something like this.
119 |
120 | 
121 |
122 | Feel free to submit a pull request or open a new issue, contributions are more than welcome !!!
123 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: "Github Readme Learn Section Notion"
2 | description: "Populates the learning section in your github readme with data from notion"
3 | author: devorein
4 |
5 | inputs:
6 | database_id:
7 | description: "The id of the database"
8 | required: true
9 | token_v2:
10 | description: "Your notion token_v2 string, required only for private databases"
11 | required: false
12 |
13 | branding:
14 | icon: "box"
15 | color: "gray-dark"
16 |
17 | runs:
18 | using: "node12"
19 | main: "dist/index.js"
20 |
--------------------------------------------------------------------------------
/dist/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/dist/README.md
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | /******/ (() => { // webpackBootstrap
2 | /******/ var __webpack_modules__ = ({
3 |
4 | /***/ 351:
5 | /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
6 |
7 | "use strict";
8 |
9 | var __importStar = (this && this.__importStar) || function (mod) {
10 | if (mod && mod.__esModule) return mod;
11 | var result = {};
12 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
13 | result["default"] = mod;
14 | return result;
15 | };
16 | Object.defineProperty(exports, "__esModule", ({ value: true }));
17 | const os = __importStar(__nccwpck_require__(87));
18 | const utils_1 = __nccwpck_require__(278);
19 | /**
20 | * Commands
21 | *
22 | * Command Format:
23 | * ::name key=value,key=value::message
24 | *
25 | * Examples:
26 | * ::warning::This is the message
27 | * ::set-env name=MY_VAR::some value
28 | */
29 | function issueCommand(command, properties, message) {
30 | const cmd = new Command(command, properties, message);
31 | process.stdout.write(cmd.toString() + os.EOL);
32 | }
33 | exports.issueCommand = issueCommand;
34 | function issue(name, message = '') {
35 | issueCommand(name, {}, message);
36 | }
37 | exports.issue = issue;
38 | const CMD_STRING = '::';
39 | class Command {
40 | constructor(command, properties, message) {
41 | if (!command) {
42 | command = 'missing.command';
43 | }
44 | this.command = command;
45 | this.properties = properties;
46 | this.message = message;
47 | }
48 | toString() {
49 | let cmdStr = CMD_STRING + this.command;
50 | if (this.properties && Object.keys(this.properties).length > 0) {
51 | cmdStr += ' ';
52 | let first = true;
53 | for (const key in this.properties) {
54 | if (this.properties.hasOwnProperty(key)) {
55 | const val = this.properties[key];
56 | if (val) {
57 | if (first) {
58 | first = false;
59 | }
60 | else {
61 | cmdStr += ',';
62 | }
63 | cmdStr += `${key}=${escapeProperty(val)}`;
64 | }
65 | }
66 | }
67 | }
68 | cmdStr += `${CMD_STRING}${escapeData(this.message)}`;
69 | return cmdStr;
70 | }
71 | }
72 | function escapeData(s) {
73 | return utils_1.toCommandValue(s)
74 | .replace(/%/g, '%25')
75 | .replace(/\r/g, '%0D')
76 | .replace(/\n/g, '%0A');
77 | }
78 | function escapeProperty(s) {
79 | return utils_1.toCommandValue(s)
80 | .replace(/%/g, '%25')
81 | .replace(/\r/g, '%0D')
82 | .replace(/\n/g, '%0A')
83 | .replace(/:/g, '%3A')
84 | .replace(/,/g, '%2C');
85 | }
86 | //# sourceMappingURL=command.js.map
87 |
88 | /***/ }),
89 |
90 | /***/ 186:
91 | /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
92 |
93 | "use strict";
94 |
95 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
96 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
97 | return new (P || (P = Promise))(function (resolve, reject) {
98 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
99 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
100 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
101 | step((generator = generator.apply(thisArg, _arguments || [])).next());
102 | });
103 | };
104 | var __importStar = (this && this.__importStar) || function (mod) {
105 | if (mod && mod.__esModule) return mod;
106 | var result = {};
107 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
108 | result["default"] = mod;
109 | return result;
110 | };
111 | Object.defineProperty(exports, "__esModule", ({ value: true }));
112 | const command_1 = __nccwpck_require__(351);
113 | const file_command_1 = __nccwpck_require__(717);
114 | const utils_1 = __nccwpck_require__(278);
115 | const os = __importStar(__nccwpck_require__(87));
116 | const path = __importStar(__nccwpck_require__(622));
117 | /**
118 | * The code to exit an action
119 | */
120 | var ExitCode;
121 | (function (ExitCode) {
122 | /**
123 | * A code indicating that the action was successful
124 | */
125 | ExitCode[ExitCode["Success"] = 0] = "Success";
126 | /**
127 | * A code indicating that the action was a failure
128 | */
129 | ExitCode[ExitCode["Failure"] = 1] = "Failure";
130 | })(ExitCode = exports.ExitCode || (exports.ExitCode = {}));
131 | //-----------------------------------------------------------------------
132 | // Variables
133 | //-----------------------------------------------------------------------
134 | /**
135 | * Sets env variable for this action and future actions in the job
136 | * @param name the name of the variable to set
137 | * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
138 | */
139 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
140 | function exportVariable(name, val) {
141 | const convertedVal = utils_1.toCommandValue(val);
142 | process.env[name] = convertedVal;
143 | const filePath = process.env['GITHUB_ENV'] || '';
144 | if (filePath) {
145 | const delimiter = '_GitHubActionsFileCommandDelimeter_';
146 | const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
147 | file_command_1.issueCommand('ENV', commandValue);
148 | }
149 | else {
150 | command_1.issueCommand('set-env', { name }, convertedVal);
151 | }
152 | }
153 | exports.exportVariable = exportVariable;
154 | /**
155 | * Registers a secret which will get masked from logs
156 | * @param secret value of the secret
157 | */
158 | function setSecret(secret) {
159 | command_1.issueCommand('add-mask', {}, secret);
160 | }
161 | exports.setSecret = setSecret;
162 | /**
163 | * Prepends inputPath to the PATH (for this action and future actions)
164 | * @param inputPath
165 | */
166 | function addPath(inputPath) {
167 | const filePath = process.env['GITHUB_PATH'] || '';
168 | if (filePath) {
169 | file_command_1.issueCommand('PATH', inputPath);
170 | }
171 | else {
172 | command_1.issueCommand('add-path', {}, inputPath);
173 | }
174 | process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
175 | }
176 | exports.addPath = addPath;
177 | /**
178 | * Gets the value of an input. The value is also trimmed.
179 | *
180 | * @param name name of the input to get
181 | * @param options optional. See InputOptions.
182 | * @returns string
183 | */
184 | function getInput(name, options) {
185 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
186 | if (options && options.required && !val) {
187 | throw new Error(`Input required and not supplied: ${name}`);
188 | }
189 | return val.trim();
190 | }
191 | exports.getInput = getInput;
192 | /**
193 | * Sets the value of an output.
194 | *
195 | * @param name name of the output to set
196 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify
197 | */
198 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
199 | function setOutput(name, value) {
200 | process.stdout.write(os.EOL);
201 | command_1.issueCommand('set-output', { name }, value);
202 | }
203 | exports.setOutput = setOutput;
204 | /**
205 | * Enables or disables the echoing of commands into stdout for the rest of the step.
206 | * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
207 | *
208 | */
209 | function setCommandEcho(enabled) {
210 | command_1.issue('echo', enabled ? 'on' : 'off');
211 | }
212 | exports.setCommandEcho = setCommandEcho;
213 | //-----------------------------------------------------------------------
214 | // Results
215 | //-----------------------------------------------------------------------
216 | /**
217 | * Sets the action status to failed.
218 | * When the action exits it will be with an exit code of 1
219 | * @param message add error issue message
220 | */
221 | function setFailed(message) {
222 | process.exitCode = ExitCode.Failure;
223 | error(message);
224 | }
225 | exports.setFailed = setFailed;
226 | //-----------------------------------------------------------------------
227 | // Logging Commands
228 | //-----------------------------------------------------------------------
229 | /**
230 | * Gets whether Actions Step Debug is on or not
231 | */
232 | function isDebug() {
233 | return process.env['RUNNER_DEBUG'] === '1';
234 | }
235 | exports.isDebug = isDebug;
236 | /**
237 | * Writes debug message to user log
238 | * @param message debug message
239 | */
240 | function debug(message) {
241 | command_1.issueCommand('debug', {}, message);
242 | }
243 | exports.debug = debug;
244 | /**
245 | * Adds an error issue
246 | * @param message error issue message. Errors will be converted to string via toString()
247 | */
248 | function error(message) {
249 | command_1.issue('error', message instanceof Error ? message.toString() : message);
250 | }
251 | exports.error = error;
252 | /**
253 | * Adds an warning issue
254 | * @param message warning issue message. Errors will be converted to string via toString()
255 | */
256 | function warning(message) {
257 | command_1.issue('warning', message instanceof Error ? message.toString() : message);
258 | }
259 | exports.warning = warning;
260 | /**
261 | * Writes info to log with console.log.
262 | * @param message info message
263 | */
264 | function info(message) {
265 | process.stdout.write(message + os.EOL);
266 | }
267 | exports.info = info;
268 | /**
269 | * Begin an output group.
270 | *
271 | * Output until the next `groupEnd` will be foldable in this group
272 | *
273 | * @param name The name of the output group
274 | */
275 | function startGroup(name) {
276 | command_1.issue('group', name);
277 | }
278 | exports.startGroup = startGroup;
279 | /**
280 | * End an output group.
281 | */
282 | function endGroup() {
283 | command_1.issue('endgroup');
284 | }
285 | exports.endGroup = endGroup;
286 | /**
287 | * Wrap an asynchronous function call in a group.
288 | *
289 | * Returns the same type as the function itself.
290 | *
291 | * @param name The name of the group
292 | * @param fn The function to wrap in the group
293 | */
294 | function group(name, fn) {
295 | return __awaiter(this, void 0, void 0, function* () {
296 | startGroup(name);
297 | let result;
298 | try {
299 | result = yield fn();
300 | }
301 | finally {
302 | endGroup();
303 | }
304 | return result;
305 | });
306 | }
307 | exports.group = group;
308 | //-----------------------------------------------------------------------
309 | // Wrapper action state
310 | //-----------------------------------------------------------------------
311 | /**
312 | * Saves state for current action, the state can only be retrieved by this action's post job execution.
313 | *
314 | * @param name name of the state to store
315 | * @param value value to store. Non-string values will be converted to a string via JSON.stringify
316 | */
317 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
318 | function saveState(name, value) {
319 | command_1.issueCommand('save-state', { name }, value);
320 | }
321 | exports.saveState = saveState;
322 | /**
323 | * Gets the value of an state set by this action's main execution.
324 | *
325 | * @param name name of the state to get
326 | * @returns string
327 | */
328 | function getState(name) {
329 | return process.env[`STATE_${name}`] || '';
330 | }
331 | exports.getState = getState;
332 | //# sourceMappingURL=core.js.map
333 |
334 | /***/ }),
335 |
336 | /***/ 717:
337 | /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
338 |
339 | "use strict";
340 |
341 | // For internal use, subject to change.
342 | var __importStar = (this && this.__importStar) || function (mod) {
343 | if (mod && mod.__esModule) return mod;
344 | var result = {};
345 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
346 | result["default"] = mod;
347 | return result;
348 | };
349 | Object.defineProperty(exports, "__esModule", ({ value: true }));
350 | // We use any as a valid input type
351 | /* eslint-disable @typescript-eslint/no-explicit-any */
352 | const fs = __importStar(__nccwpck_require__(747));
353 | const os = __importStar(__nccwpck_require__(87));
354 | const utils_1 = __nccwpck_require__(278);
355 | function issueCommand(command, message) {
356 | const filePath = process.env[`GITHUB_${command}`];
357 | if (!filePath) {
358 | throw new Error(`Unable to find environment variable for file command ${command}`);
359 | }
360 | if (!fs.existsSync(filePath)) {
361 | throw new Error(`Missing file at path: ${filePath}`);
362 | }
363 | fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, {
364 | encoding: 'utf8'
365 | });
366 | }
367 | exports.issueCommand = issueCommand;
368 | //# sourceMappingURL=file-command.js.map
369 |
370 | /***/ }),
371 |
372 | /***/ 278:
373 | /***/ ((__unused_webpack_module, exports) => {
374 |
375 | "use strict";
376 |
377 | // We use any as a valid input type
378 | /* eslint-disable @typescript-eslint/no-explicit-any */
379 | Object.defineProperty(exports, "__esModule", ({ value: true }));
380 | /**
381 | * Sanitizes an input into a string so it can be passed into issueCommand safely
382 | * @param input input to sanitize into a string
383 | */
384 | function toCommandValue(input) {
385 | if (input === null || input === undefined) {
386 | return '';
387 | }
388 | else if (typeof input === 'string' || input instanceof String) {
389 | return input;
390 | }
391 | return JSON.stringify(input);
392 | }
393 | exports.toCommandValue = toCommandValue;
394 | //# sourceMappingURL=utils.js.map
395 |
396 | /***/ }),
397 |
398 | /***/ 925:
399 | /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
400 |
401 | "use strict";
402 | var __webpack_unused_export__;
403 |
404 | __webpack_unused_export__ = ({ value: true });
405 | const http = __nccwpck_require__(605);
406 | const https = __nccwpck_require__(211);
407 | const pm = __nccwpck_require__(443);
408 | let tunnel;
409 | var HttpCodes;
410 | (function (HttpCodes) {
411 | HttpCodes[HttpCodes["OK"] = 200] = "OK";
412 | HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices";
413 | HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently";
414 | HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved";
415 | HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther";
416 | HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified";
417 | HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy";
418 | HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy";
419 | HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect";
420 | HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect";
421 | HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest";
422 | HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized";
423 | HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired";
424 | HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden";
425 | HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound";
426 | HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed";
427 | HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable";
428 | HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
429 | HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout";
430 | HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict";
431 | HttpCodes[HttpCodes["Gone"] = 410] = "Gone";
432 | HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests";
433 | HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError";
434 | HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented";
435 | HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway";
436 | HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable";
437 | HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout";
438 | })(HttpCodes = exports.o8 || (exports.o8 = {}));
439 | var Headers;
440 | (function (Headers) {
441 | Headers["Accept"] = "accept";
442 | Headers["ContentType"] = "content-type";
443 | })(Headers = exports.PM || (exports.PM = {}));
444 | var MediaTypes;
445 | (function (MediaTypes) {
446 | MediaTypes["ApplicationJson"] = "application/json";
447 | })(MediaTypes = exports.Tr || (exports.Tr = {}));
448 | /**
449 | * Returns the proxy URL, depending upon the supplied url and proxy environment variables.
450 | * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
451 | */
452 | function getProxyUrl(serverUrl) {
453 | let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
454 | return proxyUrl ? proxyUrl.href : '';
455 | }
456 | __webpack_unused_export__ = getProxyUrl;
457 | const HttpRedirectCodes = [
458 | HttpCodes.MovedPermanently,
459 | HttpCodes.ResourceMoved,
460 | HttpCodes.SeeOther,
461 | HttpCodes.TemporaryRedirect,
462 | HttpCodes.PermanentRedirect
463 | ];
464 | const HttpResponseRetryCodes = [
465 | HttpCodes.BadGateway,
466 | HttpCodes.ServiceUnavailable,
467 | HttpCodes.GatewayTimeout
468 | ];
469 | const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
470 | const ExponentialBackoffCeiling = 10;
471 | const ExponentialBackoffTimeSlice = 5;
472 | class HttpClientError extends Error {
473 | constructor(message, statusCode) {
474 | super(message);
475 | this.name = 'HttpClientError';
476 | this.statusCode = statusCode;
477 | Object.setPrototypeOf(this, HttpClientError.prototype);
478 | }
479 | }
480 | __webpack_unused_export__ = HttpClientError;
481 | class HttpClientResponse {
482 | constructor(message) {
483 | this.message = message;
484 | }
485 | readBody() {
486 | return new Promise(async (resolve, reject) => {
487 | let output = Buffer.alloc(0);
488 | this.message.on('data', (chunk) => {
489 | output = Buffer.concat([output, chunk]);
490 | });
491 | this.message.on('end', () => {
492 | resolve(output.toString());
493 | });
494 | });
495 | }
496 | }
497 | __webpack_unused_export__ = HttpClientResponse;
498 | function isHttps(requestUrl) {
499 | let parsedUrl = new URL(requestUrl);
500 | return parsedUrl.protocol === 'https:';
501 | }
502 | __webpack_unused_export__ = isHttps;
503 | class HttpClient {
504 | constructor(userAgent, handlers, requestOptions) {
505 | this._ignoreSslError = false;
506 | this._allowRedirects = true;
507 | this._allowRedirectDowngrade = false;
508 | this._maxRedirects = 50;
509 | this._allowRetries = false;
510 | this._maxRetries = 1;
511 | this._keepAlive = false;
512 | this._disposed = false;
513 | this.userAgent = userAgent;
514 | this.handlers = handlers || [];
515 | this.requestOptions = requestOptions;
516 | if (requestOptions) {
517 | if (requestOptions.ignoreSslError != null) {
518 | this._ignoreSslError = requestOptions.ignoreSslError;
519 | }
520 | this._socketTimeout = requestOptions.socketTimeout;
521 | if (requestOptions.allowRedirects != null) {
522 | this._allowRedirects = requestOptions.allowRedirects;
523 | }
524 | if (requestOptions.allowRedirectDowngrade != null) {
525 | this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade;
526 | }
527 | if (requestOptions.maxRedirects != null) {
528 | this._maxRedirects = Math.max(requestOptions.maxRedirects, 0);
529 | }
530 | if (requestOptions.keepAlive != null) {
531 | this._keepAlive = requestOptions.keepAlive;
532 | }
533 | if (requestOptions.allowRetries != null) {
534 | this._allowRetries = requestOptions.allowRetries;
535 | }
536 | if (requestOptions.maxRetries != null) {
537 | this._maxRetries = requestOptions.maxRetries;
538 | }
539 | }
540 | }
541 | options(requestUrl, additionalHeaders) {
542 | return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
543 | }
544 | get(requestUrl, additionalHeaders) {
545 | return this.request('GET', requestUrl, null, additionalHeaders || {});
546 | }
547 | del(requestUrl, additionalHeaders) {
548 | return this.request('DELETE', requestUrl, null, additionalHeaders || {});
549 | }
550 | post(requestUrl, data, additionalHeaders) {
551 | return this.request('POST', requestUrl, data, additionalHeaders || {});
552 | }
553 | patch(requestUrl, data, additionalHeaders) {
554 | return this.request('PATCH', requestUrl, data, additionalHeaders || {});
555 | }
556 | put(requestUrl, data, additionalHeaders) {
557 | return this.request('PUT', requestUrl, data, additionalHeaders || {});
558 | }
559 | head(requestUrl, additionalHeaders) {
560 | return this.request('HEAD', requestUrl, null, additionalHeaders || {});
561 | }
562 | sendStream(verb, requestUrl, stream, additionalHeaders) {
563 | return this.request(verb, requestUrl, stream, additionalHeaders);
564 | }
565 | /**
566 | * Gets a typed object from an endpoint
567 | * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
568 | */
569 | async getJson(requestUrl, additionalHeaders = {}) {
570 | additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
571 | let res = await this.get(requestUrl, additionalHeaders);
572 | return this._processResponse(res, this.requestOptions);
573 | }
574 | async postJson(requestUrl, obj, additionalHeaders = {}) {
575 | let data = JSON.stringify(obj, null, 2);
576 | additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
577 | additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
578 | let res = await this.post(requestUrl, data, additionalHeaders);
579 | return this._processResponse(res, this.requestOptions);
580 | }
581 | async putJson(requestUrl, obj, additionalHeaders = {}) {
582 | let data = JSON.stringify(obj, null, 2);
583 | additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
584 | additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
585 | let res = await this.put(requestUrl, data, additionalHeaders);
586 | return this._processResponse(res, this.requestOptions);
587 | }
588 | async patchJson(requestUrl, obj, additionalHeaders = {}) {
589 | let data = JSON.stringify(obj, null, 2);
590 | additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
591 | additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
592 | let res = await this.patch(requestUrl, data, additionalHeaders);
593 | return this._processResponse(res, this.requestOptions);
594 | }
595 | /**
596 | * Makes a raw http request.
597 | * All other methods such as get, post, patch, and request ultimately call this.
598 | * Prefer get, del, post and patch
599 | */
600 | async request(verb, requestUrl, data, headers) {
601 | if (this._disposed) {
602 | throw new Error('Client has already been disposed.');
603 | }
604 | let parsedUrl = new URL(requestUrl);
605 | let info = this._prepareRequest(verb, parsedUrl, headers);
606 | // Only perform retries on reads since writes may not be idempotent.
607 | let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
608 | ? this._maxRetries + 1
609 | : 1;
610 | let numTries = 0;
611 | let response;
612 | while (numTries < maxTries) {
613 | response = await this.requestRaw(info, data);
614 | // Check if it's an authentication challenge
615 | if (response &&
616 | response.message &&
617 | response.message.statusCode === HttpCodes.Unauthorized) {
618 | let authenticationHandler;
619 | for (let i = 0; i < this.handlers.length; i++) {
620 | if (this.handlers[i].canHandleAuthentication(response)) {
621 | authenticationHandler = this.handlers[i];
622 | break;
623 | }
624 | }
625 | if (authenticationHandler) {
626 | return authenticationHandler.handleAuthentication(this, info, data);
627 | }
628 | else {
629 | // We have received an unauthorized response but have no handlers to handle it.
630 | // Let the response return to the caller.
631 | return response;
632 | }
633 | }
634 | let redirectsRemaining = this._maxRedirects;
635 | while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
636 | this._allowRedirects &&
637 | redirectsRemaining > 0) {
638 | const redirectUrl = response.message.headers['location'];
639 | if (!redirectUrl) {
640 | // if there's no location to redirect to, we won't
641 | break;
642 | }
643 | let parsedRedirectUrl = new URL(redirectUrl);
644 | if (parsedUrl.protocol == 'https:' &&
645 | parsedUrl.protocol != parsedRedirectUrl.protocol &&
646 | !this._allowRedirectDowngrade) {
647 | throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
648 | }
649 | // we need to finish reading the response before reassigning response
650 | // which will leak the open socket.
651 | await response.readBody();
652 | // strip authorization header if redirected to a different hostname
653 | if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
654 | for (let header in headers) {
655 | // header names are case insensitive
656 | if (header.toLowerCase() === 'authorization') {
657 | delete headers[header];
658 | }
659 | }
660 | }
661 | // let's make the request with the new redirectUrl
662 | info = this._prepareRequest(verb, parsedRedirectUrl, headers);
663 | response = await this.requestRaw(info, data);
664 | redirectsRemaining--;
665 | }
666 | if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
667 | // If not a retry code, return immediately instead of retrying
668 | return response;
669 | }
670 | numTries += 1;
671 | if (numTries < maxTries) {
672 | await response.readBody();
673 | await this._performExponentialBackoff(numTries);
674 | }
675 | }
676 | return response;
677 | }
678 | /**
679 | * Needs to be called if keepAlive is set to true in request options.
680 | */
681 | dispose() {
682 | if (this._agent) {
683 | this._agent.destroy();
684 | }
685 | this._disposed = true;
686 | }
687 | /**
688 | * Raw request.
689 | * @param info
690 | * @param data
691 | */
692 | requestRaw(info, data) {
693 | return new Promise((resolve, reject) => {
694 | let callbackForResult = function (err, res) {
695 | if (err) {
696 | reject(err);
697 | }
698 | resolve(res);
699 | };
700 | this.requestRawWithCallback(info, data, callbackForResult);
701 | });
702 | }
703 | /**
704 | * Raw request with callback.
705 | * @param info
706 | * @param data
707 | * @param onResult
708 | */
709 | requestRawWithCallback(info, data, onResult) {
710 | let socket;
711 | if (typeof data === 'string') {
712 | info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
713 | }
714 | let callbackCalled = false;
715 | let handleResult = (err, res) => {
716 | if (!callbackCalled) {
717 | callbackCalled = true;
718 | onResult(err, res);
719 | }
720 | };
721 | let req = info.httpModule.request(info.options, (msg) => {
722 | let res = new HttpClientResponse(msg);
723 | handleResult(null, res);
724 | });
725 | req.on('socket', sock => {
726 | socket = sock;
727 | });
728 | // If we ever get disconnected, we want the socket to timeout eventually
729 | req.setTimeout(this._socketTimeout || 3 * 60000, () => {
730 | if (socket) {
731 | socket.end();
732 | }
733 | handleResult(new Error('Request timeout: ' + info.options.path), null);
734 | });
735 | req.on('error', function (err) {
736 | // err has statusCode property
737 | // res should have headers
738 | handleResult(err, null);
739 | });
740 | if (data && typeof data === 'string') {
741 | req.write(data, 'utf8');
742 | }
743 | if (data && typeof data !== 'string') {
744 | data.on('close', function () {
745 | req.end();
746 | });
747 | data.pipe(req);
748 | }
749 | else {
750 | req.end();
751 | }
752 | }
753 | /**
754 | * Gets an http agent. This function is useful when you need an http agent that handles
755 | * routing through a proxy server - depending upon the url and proxy environment variables.
756 | * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
757 | */
758 | getAgent(serverUrl) {
759 | let parsedUrl = new URL(serverUrl);
760 | return this._getAgent(parsedUrl);
761 | }
762 | _prepareRequest(method, requestUrl, headers) {
763 | const info = {};
764 | info.parsedUrl = requestUrl;
765 | const usingSsl = info.parsedUrl.protocol === 'https:';
766 | info.httpModule = usingSsl ? https : http;
767 | const defaultPort = usingSsl ? 443 : 80;
768 | info.options = {};
769 | info.options.host = info.parsedUrl.hostname;
770 | info.options.port = info.parsedUrl.port
771 | ? parseInt(info.parsedUrl.port)
772 | : defaultPort;
773 | info.options.path =
774 | (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
775 | info.options.method = method;
776 | info.options.headers = this._mergeHeaders(headers);
777 | if (this.userAgent != null) {
778 | info.options.headers['user-agent'] = this.userAgent;
779 | }
780 | info.options.agent = this._getAgent(info.parsedUrl);
781 | // gives handlers an opportunity to participate
782 | if (this.handlers) {
783 | this.handlers.forEach(handler => {
784 | handler.prepareRequest(info.options);
785 | });
786 | }
787 | return info;
788 | }
789 | _mergeHeaders(headers) {
790 | const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
791 | if (this.requestOptions && this.requestOptions.headers) {
792 | return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
793 | }
794 | return lowercaseKeys(headers || {});
795 | }
796 | _getExistingOrDefaultHeader(additionalHeaders, header, _default) {
797 | const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
798 | let clientHeader;
799 | if (this.requestOptions && this.requestOptions.headers) {
800 | clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
801 | }
802 | return additionalHeaders[header] || clientHeader || _default;
803 | }
804 | _getAgent(parsedUrl) {
805 | let agent;
806 | let proxyUrl = pm.getProxyUrl(parsedUrl);
807 | let useProxy = proxyUrl && proxyUrl.hostname;
808 | if (this._keepAlive && useProxy) {
809 | agent = this._proxyAgent;
810 | }
811 | if (this._keepAlive && !useProxy) {
812 | agent = this._agent;
813 | }
814 | // if agent is already assigned use that agent.
815 | if (!!agent) {
816 | return agent;
817 | }
818 | const usingSsl = parsedUrl.protocol === 'https:';
819 | let maxSockets = 100;
820 | if (!!this.requestOptions) {
821 | maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
822 | }
823 | if (useProxy) {
824 | // If using proxy, need tunnel
825 | if (!tunnel) {
826 | tunnel = __nccwpck_require__(294);
827 | }
828 | const agentOptions = {
829 | maxSockets: maxSockets,
830 | keepAlive: this._keepAlive,
831 | proxy: {
832 | ...((proxyUrl.username || proxyUrl.password) && {
833 | proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
834 | }),
835 | host: proxyUrl.hostname,
836 | port: proxyUrl.port
837 | }
838 | };
839 | let tunnelAgent;
840 | const overHttps = proxyUrl.protocol === 'https:';
841 | if (usingSsl) {
842 | tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp;
843 | }
844 | else {
845 | tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp;
846 | }
847 | agent = tunnelAgent(agentOptions);
848 | this._proxyAgent = agent;
849 | }
850 | // if reusing agent across request and tunneling agent isn't assigned create a new agent
851 | if (this._keepAlive && !agent) {
852 | const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
853 | agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
854 | this._agent = agent;
855 | }
856 | // if not using private agent and tunnel agent isn't setup then use global agent
857 | if (!agent) {
858 | agent = usingSsl ? https.globalAgent : http.globalAgent;
859 | }
860 | if (usingSsl && this._ignoreSslError) {
861 | // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
862 | // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
863 | // we have to cast it to any and change it directly
864 | agent.options = Object.assign(agent.options || {}, {
865 | rejectUnauthorized: false
866 | });
867 | }
868 | return agent;
869 | }
870 | _performExponentialBackoff(retryNumber) {
871 | retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
872 | const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
873 | return new Promise(resolve => setTimeout(() => resolve(), ms));
874 | }
875 | static dateTimeDeserializer(key, value) {
876 | if (typeof value === 'string') {
877 | let a = new Date(value);
878 | if (!isNaN(a.valueOf())) {
879 | return a;
880 | }
881 | }
882 | return value;
883 | }
884 | async _processResponse(res, options) {
885 | return new Promise(async (resolve, reject) => {
886 | const statusCode = res.message.statusCode;
887 | const response = {
888 | statusCode: statusCode,
889 | result: null,
890 | headers: {}
891 | };
892 | // not found leads to null obj returned
893 | if (statusCode == HttpCodes.NotFound) {
894 | resolve(response);
895 | }
896 | let obj;
897 | let contents;
898 | // get the result from the body
899 | try {
900 | contents = await res.readBody();
901 | if (contents && contents.length > 0) {
902 | if (options && options.deserializeDates) {
903 | obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
904 | }
905 | else {
906 | obj = JSON.parse(contents);
907 | }
908 | response.result = obj;
909 | }
910 | response.headers = res.message.headers;
911 | }
912 | catch (err) {
913 | // Invalid resource (contents not json); leaving result obj null
914 | }
915 | // note that 3xx redirects are handled by the http layer.
916 | if (statusCode > 299) {
917 | let msg;
918 | // if exception/error in body, attempt to get better error
919 | if (obj && obj.message) {
920 | msg = obj.message;
921 | }
922 | else if (contents && contents.length > 0) {
923 | // it may be the case that the exception is in the body message as string
924 | msg = contents;
925 | }
926 | else {
927 | msg = 'Failed request: (' + statusCode + ')';
928 | }
929 | let err = new HttpClientError(msg, statusCode);
930 | err.result = response.result;
931 | reject(err);
932 | }
933 | else {
934 | resolve(response);
935 | }
936 | });
937 | }
938 | }
939 | exports.eN = HttpClient;
940 |
941 |
942 | /***/ }),
943 |
944 | /***/ 443:
945 | /***/ ((__unused_webpack_module, exports) => {
946 |
947 | "use strict";
948 |
949 | Object.defineProperty(exports, "__esModule", ({ value: true }));
950 | function getProxyUrl(reqUrl) {
951 | let usingSsl = reqUrl.protocol === 'https:';
952 | let proxyUrl;
953 | if (checkBypass(reqUrl)) {
954 | return proxyUrl;
955 | }
956 | let proxyVar;
957 | if (usingSsl) {
958 | proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
959 | }
960 | else {
961 | proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
962 | }
963 | if (proxyVar) {
964 | proxyUrl = new URL(proxyVar);
965 | }
966 | return proxyUrl;
967 | }
968 | exports.getProxyUrl = getProxyUrl;
969 | function checkBypass(reqUrl) {
970 | if (!reqUrl.hostname) {
971 | return false;
972 | }
973 | let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
974 | if (!noProxy) {
975 | return false;
976 | }
977 | // Determine the request port
978 | let reqPort;
979 | if (reqUrl.port) {
980 | reqPort = Number(reqUrl.port);
981 | }
982 | else if (reqUrl.protocol === 'http:') {
983 | reqPort = 80;
984 | }
985 | else if (reqUrl.protocol === 'https:') {
986 | reqPort = 443;
987 | }
988 | // Format the request hostname and hostname with port
989 | let upperReqHosts = [reqUrl.hostname.toUpperCase()];
990 | if (typeof reqPort === 'number') {
991 | upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
992 | }
993 | // Compare request host against noproxy
994 | for (let upperNoProxyItem of noProxy
995 | .split(',')
996 | .map(x => x.trim().toUpperCase())
997 | .filter(x => x)) {
998 | if (upperReqHosts.some(x => x === upperNoProxyItem)) {
999 | return true;
1000 | }
1001 | }
1002 | return false;
1003 | }
1004 | exports.checkBypass = checkBypass;
1005 |
1006 |
1007 | /***/ }),
1008 |
1009 | /***/ 294:
1010 | /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
1011 |
1012 | module.exports = __nccwpck_require__(219);
1013 |
1014 |
1015 | /***/ }),
1016 |
1017 | /***/ 219:
1018 | /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1019 |
1020 | "use strict";
1021 |
1022 |
1023 | var net = __nccwpck_require__(631);
1024 | var tls = __nccwpck_require__(16);
1025 | var http = __nccwpck_require__(605);
1026 | var https = __nccwpck_require__(211);
1027 | var events = __nccwpck_require__(614);
1028 | var assert = __nccwpck_require__(357);
1029 | var util = __nccwpck_require__(669);
1030 |
1031 |
1032 | exports.httpOverHttp = httpOverHttp;
1033 | exports.httpsOverHttp = httpsOverHttp;
1034 | exports.httpOverHttps = httpOverHttps;
1035 | exports.httpsOverHttps = httpsOverHttps;
1036 |
1037 |
1038 | function httpOverHttp(options) {
1039 | var agent = new TunnelingAgent(options);
1040 | agent.request = http.request;
1041 | return agent;
1042 | }
1043 |
1044 | function httpsOverHttp(options) {
1045 | var agent = new TunnelingAgent(options);
1046 | agent.request = http.request;
1047 | agent.createSocket = createSecureSocket;
1048 | agent.defaultPort = 443;
1049 | return agent;
1050 | }
1051 |
1052 | function httpOverHttps(options) {
1053 | var agent = new TunnelingAgent(options);
1054 | agent.request = https.request;
1055 | return agent;
1056 | }
1057 |
1058 | function httpsOverHttps(options) {
1059 | var agent = new TunnelingAgent(options);
1060 | agent.request = https.request;
1061 | agent.createSocket = createSecureSocket;
1062 | agent.defaultPort = 443;
1063 | return agent;
1064 | }
1065 |
1066 |
1067 | function TunnelingAgent(options) {
1068 | var self = this;
1069 | self.options = options || {};
1070 | self.proxyOptions = self.options.proxy || {};
1071 | self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets;
1072 | self.requests = [];
1073 | self.sockets = [];
1074 |
1075 | self.on('free', function onFree(socket, host, port, localAddress) {
1076 | var options = toOptions(host, port, localAddress);
1077 | for (var i = 0, len = self.requests.length; i < len; ++i) {
1078 | var pending = self.requests[i];
1079 | if (pending.host === options.host && pending.port === options.port) {
1080 | // Detect the request to connect same origin server,
1081 | // reuse the connection.
1082 | self.requests.splice(i, 1);
1083 | pending.request.onSocket(socket);
1084 | return;
1085 | }
1086 | }
1087 | socket.destroy();
1088 | self.removeSocket(socket);
1089 | });
1090 | }
1091 | util.inherits(TunnelingAgent, events.EventEmitter);
1092 |
1093 | TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) {
1094 | var self = this;
1095 | var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress));
1096 |
1097 | if (self.sockets.length >= this.maxSockets) {
1098 | // We are over limit so we'll add it to the queue.
1099 | self.requests.push(options);
1100 | return;
1101 | }
1102 |
1103 | // If we are under maxSockets create a new one.
1104 | self.createSocket(options, function(socket) {
1105 | socket.on('free', onFree);
1106 | socket.on('close', onCloseOrRemove);
1107 | socket.on('agentRemove', onCloseOrRemove);
1108 | req.onSocket(socket);
1109 |
1110 | function onFree() {
1111 | self.emit('free', socket, options);
1112 | }
1113 |
1114 | function onCloseOrRemove(err) {
1115 | self.removeSocket(socket);
1116 | socket.removeListener('free', onFree);
1117 | socket.removeListener('close', onCloseOrRemove);
1118 | socket.removeListener('agentRemove', onCloseOrRemove);
1119 | }
1120 | });
1121 | };
1122 |
1123 | TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
1124 | var self = this;
1125 | var placeholder = {};
1126 | self.sockets.push(placeholder);
1127 |
1128 | var connectOptions = mergeOptions({}, self.proxyOptions, {
1129 | method: 'CONNECT',
1130 | path: options.host + ':' + options.port,
1131 | agent: false,
1132 | headers: {
1133 | host: options.host + ':' + options.port
1134 | }
1135 | });
1136 | if (options.localAddress) {
1137 | connectOptions.localAddress = options.localAddress;
1138 | }
1139 | if (connectOptions.proxyAuth) {
1140 | connectOptions.headers = connectOptions.headers || {};
1141 | connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
1142 | new Buffer(connectOptions.proxyAuth).toString('base64');
1143 | }
1144 |
1145 | debug('making CONNECT request');
1146 | var connectReq = self.request(connectOptions);
1147 | connectReq.useChunkedEncodingByDefault = false; // for v0.6
1148 | connectReq.once('response', onResponse); // for v0.6
1149 | connectReq.once('upgrade', onUpgrade); // for v0.6
1150 | connectReq.once('connect', onConnect); // for v0.7 or later
1151 | connectReq.once('error', onError);
1152 | connectReq.end();
1153 |
1154 | function onResponse(res) {
1155 | // Very hacky. This is necessary to avoid http-parser leaks.
1156 | res.upgrade = true;
1157 | }
1158 |
1159 | function onUpgrade(res, socket, head) {
1160 | // Hacky.
1161 | process.nextTick(function() {
1162 | onConnect(res, socket, head);
1163 | });
1164 | }
1165 |
1166 | function onConnect(res, socket, head) {
1167 | connectReq.removeAllListeners();
1168 | socket.removeAllListeners();
1169 |
1170 | if (res.statusCode !== 200) {
1171 | debug('tunneling socket could not be established, statusCode=%d',
1172 | res.statusCode);
1173 | socket.destroy();
1174 | var error = new Error('tunneling socket could not be established, ' +
1175 | 'statusCode=' + res.statusCode);
1176 | error.code = 'ECONNRESET';
1177 | options.request.emit('error', error);
1178 | self.removeSocket(placeholder);
1179 | return;
1180 | }
1181 | if (head.length > 0) {
1182 | debug('got illegal response body from proxy');
1183 | socket.destroy();
1184 | var error = new Error('got illegal response body from proxy');
1185 | error.code = 'ECONNRESET';
1186 | options.request.emit('error', error);
1187 | self.removeSocket(placeholder);
1188 | return;
1189 | }
1190 | debug('tunneling connection has established');
1191 | self.sockets[self.sockets.indexOf(placeholder)] = socket;
1192 | return cb(socket);
1193 | }
1194 |
1195 | function onError(cause) {
1196 | connectReq.removeAllListeners();
1197 |
1198 | debug('tunneling socket could not be established, cause=%s\n',
1199 | cause.message, cause.stack);
1200 | var error = new Error('tunneling socket could not be established, ' +
1201 | 'cause=' + cause.message);
1202 | error.code = 'ECONNRESET';
1203 | options.request.emit('error', error);
1204 | self.removeSocket(placeholder);
1205 | }
1206 | };
1207 |
1208 | TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
1209 | var pos = this.sockets.indexOf(socket)
1210 | if (pos === -1) {
1211 | return;
1212 | }
1213 | this.sockets.splice(pos, 1);
1214 |
1215 | var pending = this.requests.shift();
1216 | if (pending) {
1217 | // If we have pending requests and a socket gets closed a new one
1218 | // needs to be created to take over in the pool for the one that closed.
1219 | this.createSocket(pending, function(socket) {
1220 | pending.request.onSocket(socket);
1221 | });
1222 | }
1223 | };
1224 |
1225 | function createSecureSocket(options, cb) {
1226 | var self = this;
1227 | TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
1228 | var hostHeader = options.request.getHeader('host');
1229 | var tlsOptions = mergeOptions({}, self.options, {
1230 | socket: socket,
1231 | servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host
1232 | });
1233 |
1234 | // 0 is dummy port for v0.6
1235 | var secureSocket = tls.connect(0, tlsOptions);
1236 | self.sockets[self.sockets.indexOf(socket)] = secureSocket;
1237 | cb(secureSocket);
1238 | });
1239 | }
1240 |
1241 |
1242 | function toOptions(host, port, localAddress) {
1243 | if (typeof host === 'string') { // since v0.10
1244 | return {
1245 | host: host,
1246 | port: port,
1247 | localAddress: localAddress
1248 | };
1249 | }
1250 | return host; // for v0.11 or later
1251 | }
1252 |
1253 | function mergeOptions(target) {
1254 | for (var i = 1, len = arguments.length; i < len; ++i) {
1255 | var overrides = arguments[i];
1256 | if (typeof overrides === 'object') {
1257 | var keys = Object.keys(overrides);
1258 | for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
1259 | var k = keys[j];
1260 | if (overrides[k] !== undefined) {
1261 | target[k] = overrides[k];
1262 | }
1263 | }
1264 | }
1265 | }
1266 | return target;
1267 | }
1268 |
1269 |
1270 | var debug;
1271 | if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
1272 | debug = function() {
1273 | var args = Array.prototype.slice.call(arguments);
1274 | if (typeof args[0] === 'string') {
1275 | args[0] = 'TUNNEL: ' + args[0];
1276 | } else {
1277 | args.unshift('TUNNEL:');
1278 | }
1279 | console.error.apply(console, args);
1280 | }
1281 | } else {
1282 | debug = function() {};
1283 | }
1284 | exports.debug = debug; // for test
1285 |
1286 |
1287 | /***/ }),
1288 |
1289 | /***/ 357:
1290 | /***/ ((module) => {
1291 |
1292 | "use strict";
1293 | module.exports = require("assert");;
1294 |
1295 | /***/ }),
1296 |
1297 | /***/ 614:
1298 | /***/ ((module) => {
1299 |
1300 | "use strict";
1301 | module.exports = require("events");;
1302 |
1303 | /***/ }),
1304 |
1305 | /***/ 747:
1306 | /***/ ((module) => {
1307 |
1308 | "use strict";
1309 | module.exports = require("fs");;
1310 |
1311 | /***/ }),
1312 |
1313 | /***/ 605:
1314 | /***/ ((module) => {
1315 |
1316 | "use strict";
1317 | module.exports = require("http");;
1318 |
1319 | /***/ }),
1320 |
1321 | /***/ 211:
1322 | /***/ ((module) => {
1323 |
1324 | "use strict";
1325 | module.exports = require("https");;
1326 |
1327 | /***/ }),
1328 |
1329 | /***/ 631:
1330 | /***/ ((module) => {
1331 |
1332 | "use strict";
1333 | module.exports = require("net");;
1334 |
1335 | /***/ }),
1336 |
1337 | /***/ 87:
1338 | /***/ ((module) => {
1339 |
1340 | "use strict";
1341 | module.exports = require("os");;
1342 |
1343 | /***/ }),
1344 |
1345 | /***/ 622:
1346 | /***/ ((module) => {
1347 |
1348 | "use strict";
1349 | module.exports = require("path");;
1350 |
1351 | /***/ }),
1352 |
1353 | /***/ 16:
1354 | /***/ ((module) => {
1355 |
1356 | "use strict";
1357 | module.exports = require("tls");;
1358 |
1359 | /***/ }),
1360 |
1361 | /***/ 669:
1362 | /***/ ((module) => {
1363 |
1364 | "use strict";
1365 | module.exports = require("util");;
1366 |
1367 | /***/ })
1368 |
1369 | /******/ });
1370 | /************************************************************************/
1371 | /******/ // The module cache
1372 | /******/ var __webpack_module_cache__ = {};
1373 | /******/
1374 | /******/ // The require function
1375 | /******/ function __nccwpck_require__(moduleId) {
1376 | /******/ // Check if module is in cache
1377 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
1378 | /******/ if (cachedModule !== undefined) {
1379 | /******/ return cachedModule.exports;
1380 | /******/ }
1381 | /******/ // Create a new module (and put it into the cache)
1382 | /******/ var module = __webpack_module_cache__[moduleId] = {
1383 | /******/ // no module.id needed
1384 | /******/ // no module.loaded needed
1385 | /******/ exports: {}
1386 | /******/ };
1387 | /******/
1388 | /******/ // Execute the module function
1389 | /******/ var threw = true;
1390 | /******/ try {
1391 | /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __nccwpck_require__);
1392 | /******/ threw = false;
1393 | /******/ } finally {
1394 | /******/ if(threw) delete __webpack_module_cache__[moduleId];
1395 | /******/ }
1396 | /******/
1397 | /******/ // Return the exports of the module
1398 | /******/ return module.exports;
1399 | /******/ }
1400 | /******/
1401 | /************************************************************************/
1402 | /******/ /* webpack/runtime/compat get default export */
1403 | /******/ (() => {
1404 | /******/ // getDefaultExport function for compatibility with non-harmony modules
1405 | /******/ __nccwpck_require__.n = (module) => {
1406 | /******/ var getter = module && module.__esModule ?
1407 | /******/ () => (module['default']) :
1408 | /******/ () => (module);
1409 | /******/ __nccwpck_require__.d(getter, { a: getter });
1410 | /******/ return getter;
1411 | /******/ };
1412 | /******/ })();
1413 | /******/
1414 | /******/ /* webpack/runtime/define property getters */
1415 | /******/ (() => {
1416 | /******/ // define getter functions for harmony exports
1417 | /******/ __nccwpck_require__.d = (exports, definition) => {
1418 | /******/ for(var key in definition) {
1419 | /******/ if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) {
1420 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
1421 | /******/ }
1422 | /******/ }
1423 | /******/ };
1424 | /******/ })();
1425 | /******/
1426 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
1427 | /******/ (() => {
1428 | /******/ __nccwpck_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
1429 | /******/ })();
1430 | /******/
1431 | /******/ /* webpack/runtime/make namespace object */
1432 | /******/ (() => {
1433 | /******/ // define __esModule on exports
1434 | /******/ __nccwpck_require__.r = (exports) => {
1435 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
1436 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1437 | /******/ }
1438 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
1439 | /******/ };
1440 | /******/ })();
1441 | /******/
1442 | /******/ /* webpack/runtime/compat */
1443 | /******/
1444 | /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";/************************************************************************/
1445 | var __webpack_exports__ = {};
1446 | // This entry need to be wrapped in an IIFE because it need to be in strict mode.
1447 | (() => {
1448 | "use strict";
1449 | // ESM COMPAT FLAG
1450 | __nccwpck_require__.r(__webpack_exports__);
1451 |
1452 | // EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
1453 | var core = __nccwpck_require__(186);
1454 | // EXTERNAL MODULE: ./node_modules/@actions/http-client/index.js
1455 | var http_client = __nccwpck_require__(925);
1456 | // EXTERNAL MODULE: external "fs"
1457 | var external_fs_ = __nccwpck_require__(747);
1458 | var external_fs_default = /*#__PURE__*/__nccwpck_require__.n(external_fs_);
1459 | ;// CONCATENATED MODULE: ./src/utils/checkForSections.ts
1460 |
1461 | const checkForSections = (readmeLines) => {
1462 | const startIdx = readmeLines.findIndex((content) => content.trim() === '');
1463 | if (startIdx === -1) {
1464 | core.setFailed(`Couldn't find the comment. Exiting!`);
1465 | }
1466 | const endIdx = readmeLines.findIndex((content) => content.trim() === '');
1467 | if (endIdx === -1) {
1468 | core.setFailed(`Couldn't find the comment. Exiting!`);
1469 | }
1470 | return [startIdx, endIdx];
1471 | };
1472 |
1473 | ;// CONCATENATED MODULE: external "child_process"
1474 | const external_child_process_namespaceObject = require("child_process");;
1475 | ;// CONCATENATED MODULE: ./src/utils/commitFile.ts
1476 | var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
1477 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1478 | return new (P || (P = Promise))(function (resolve, reject) {
1479 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1480 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1481 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1482 | step((generator = generator.apply(thisArg, _arguments || [])).next());
1483 | });
1484 | };
1485 |
1486 | const exec = (cmd, args = []) => new Promise((resolve, reject) => {
1487 | const app = (0,external_child_process_namespaceObject.spawn)(cmd, args, { stdio: 'pipe' });
1488 | let stdout = '';
1489 | app.stdout.on('data', (data) => {
1490 | stdout = data;
1491 | });
1492 | app.on('close', (code) => {
1493 | if (code !== 0 && !stdout.includes('nothing to commit')) {
1494 | const err = new Error(`Invalid status code: ${code}`);
1495 | err.code = code;
1496 | return reject(err);
1497 | }
1498 | return resolve(code);
1499 | });
1500 | app.on('error', reject);
1501 | });
1502 | const commitFile = () => __awaiter(void 0, void 0, void 0, function* () {
1503 | yield exec('git', [
1504 | 'config',
1505 | '--global',
1506 | 'user.email',
1507 | '41898282+github-actions[bot]@users.noreply.github.com'
1508 | ]);
1509 | yield exec('git', ['config', '--global', 'user.name', 'readme-bot']);
1510 | yield exec('git', ['add', 'README.md']);
1511 | yield exec('git', ['commit', '-m', 'Updated readme with learn section']);
1512 | yield exec('git', ['push']);
1513 | });
1514 |
1515 | ;// CONCATENATED MODULE: ./src/utils/constructCategoriesMap.ts
1516 | const constructCategoriesMap = (schema_unit) => {
1517 | const categories = schema_unit.options
1518 | .map((option) => ({
1519 | color: option.color,
1520 | value: option.value
1521 | }))
1522 | .sort((categoryA, categoryB) => categoryA.value > categoryB.value ? 1 : -1);
1523 | const categories_map = new Map();
1524 | categories.forEach((category) => {
1525 | categories_map.set(category.value, Object.assign({ items: [] }, category));
1526 | });
1527 | return categories_map;
1528 | };
1529 |
1530 | ;// CONCATENATED MODULE: external "querystring"
1531 | const external_querystring_namespaceObject = require("querystring");;
1532 | var external_querystring_default = /*#__PURE__*/__nccwpck_require__.n(external_querystring_namespaceObject);
1533 | ;// CONCATENATED MODULE: ./src/utils/constructNewContents.ts
1534 |
1535 | const ColorMap = {
1536 | default: '505558',
1537 | gray: '979a9b',
1538 | brown: '695b55',
1539 | orange: '9f7445',
1540 | yellow: '9f9048',
1541 | green: '467870',
1542 | blue: '487088',
1543 | purple: '6c598f',
1544 | pink: '904d74',
1545 | red: '9f5c58',
1546 | teal: '467870'
1547 | };
1548 | const constructNewContents = (categoriesMap, colorSchemaUnitKey, base64SchemaUnitKey) => {
1549 | const newContents = [];
1550 | for (const [category, categoryInfo] of categoriesMap) {
1551 | const content = [
1552 | `.escape(category)}-${ColorMap[categoryInfo.color]})
`
1553 | ];
1554 | categoryInfo.items.forEach((item) => {
1555 | var _a, _b, _c;
1556 | const title = item.title && item.title[0][0];
1557 | if (!title)
1558 | throw new Error(`Each row must have value in the Name column`);
1559 | let logo = external_querystring_default().escape(title);
1560 | if ((_a = item[base64SchemaUnitKey]) === null || _a === void 0 ? void 0 : _a[0][0]) {
1561 | logo = item[base64SchemaUnitKey][0][0];
1562 | }
1563 | content.push(`
`);
1564 | });
1565 | newContents.push(...content, '
');
1566 | }
1567 | return newContents;
1568 | };
1569 |
1570 | ;// CONCATENATED MODULE: ./src/utils/fetchData.ts
1571 | var fetchData_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
1572 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1573 | return new (P || (P = Promise))(function (resolve, reject) {
1574 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1575 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1576 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1577 | step((generator = generator.apply(thisArg, _arguments || [])).next());
1578 | });
1579 | };
1580 |
1581 | const fetchData = (id, table, http) => fetchData_awaiter(void 0, void 0, void 0, function* () {
1582 | const response = yield http.post(`https://www.notion.so/api/v3/syncRecordValues`, JSON.stringify({
1583 | requests: [
1584 | {
1585 | id,
1586 | table,
1587 | version: -1
1588 | }
1589 | ]
1590 | }));
1591 | const body = JSON.parse(yield response.readBody());
1592 | const data = body.recordMap[table][id].value;
1593 | if (!data) {
1594 | core.setFailed(`Either your NOTION_TOKEN_V2 has expired or a ${table} with id:${id} doesn't exist`);
1595 | }
1596 | return data;
1597 | });
1598 |
1599 | ;// CONCATENATED MODULE: ./src/utils/getSchemaEntries.ts
1600 |
1601 | const getSchemaEntries = (schema) => {
1602 | const schemaEntries = Object.entries(schema);
1603 | let categorySchemaEntry = undefined, nameSchemaEntry = undefined, colorSchemaEntry = undefined, base64SchemaEntry = undefined;
1604 | schemaEntries.forEach((schemaEntry) => {
1605 | if (schemaEntry[1].type === 'text' && schemaEntry[1].name === 'Color') {
1606 | colorSchemaEntry = schemaEntry;
1607 | }
1608 | else if (schemaEntry[1].type === 'title' &&
1609 | schemaEntry[1].name === 'Name') {
1610 | nameSchemaEntry = schemaEntry;
1611 | }
1612 | else if (schemaEntry[1].type === 'select' &&
1613 | schemaEntry[1].name === 'Category') {
1614 | categorySchemaEntry = schemaEntry;
1615 | }
1616 | else if (schemaEntry[1].type === 'text' &&
1617 | schemaEntry[1].name === 'Base64') {
1618 | base64SchemaEntry = schemaEntry;
1619 | }
1620 | });
1621 | if (!categorySchemaEntry)
1622 | core.setFailed("Couldn't find Category named select type column in the database");
1623 | if (!nameSchemaEntry)
1624 | core.setFailed("Couldn't find Color named text type column in the database");
1625 | if (!colorSchemaEntry)
1626 | core.setFailed("Couldn't find Name named title type column in the database");
1627 | return [
1628 | categorySchemaEntry,
1629 | colorSchemaEntry,
1630 | nameSchemaEntry,
1631 | base64SchemaEntry
1632 | ];
1633 | };
1634 |
1635 | ;// CONCATENATED MODULE: ./src/utils/modifyRows.ts
1636 | const modifyRows = (recordMap, databaseId) => {
1637 | return Object.values(recordMap.block)
1638 | .filter((block) => block.value.id !== databaseId)
1639 | .map((block) => block.value)
1640 | .sort((rowA, rowB) => rowA.properties.title[0][0] > rowB.properties.title[0][0] ? 1 : -1);
1641 | };
1642 |
1643 | ;// CONCATENATED MODULE: ./src/utils/populateCategoriesMapItems.ts
1644 | const populateCategoriesMapItems = (rows, category_schema_id, categories_map) => {
1645 | rows.forEach((row) => {
1646 | const category = row.properties[category_schema_id] &&
1647 | row.properties[category_schema_id][0][0];
1648 | if (!category)
1649 | throw new Error('Each row must have a category value');
1650 | const category_value = categories_map.get(category);
1651 | category_value.items.push(row.properties);
1652 | });
1653 | };
1654 |
1655 | ;// CONCATENATED MODULE: ./src/utils/index.ts
1656 |
1657 |
1658 |
1659 |
1660 |
1661 |
1662 |
1663 |
1664 | const ActionUtils = {
1665 | checkForSections: checkForSections,
1666 | commitFile: commitFile,
1667 | constructCategoriesMap: constructCategoriesMap,
1668 | constructNewContents: constructNewContents,
1669 | fetchData: fetchData,
1670 | getSchemaEntries: getSchemaEntries,
1671 | modifyRows: modifyRows,
1672 | populateCategoriesMapItems: populateCategoriesMapItems
1673 | };
1674 |
1675 | ;// CONCATENATED MODULE: ./src/action.ts
1676 | var action_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
1677 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1678 | return new (P || (P = Promise))(function (resolve, reject) {
1679 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1680 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1681 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1682 | step((generator = generator.apply(thisArg, _arguments || [])).next());
1683 | });
1684 | };
1685 |
1686 |
1687 |
1688 |
1689 | function action() {
1690 | return action_awaiter(this, void 0, void 0, function* () {
1691 | try {
1692 | const NOTION_TOKEN_V2 = core.getInput('token_v2');
1693 | let id = core.getInput('database_id').replace(/-/g, '');
1694 | const databaseId = `${id.substr(0, 8)}-${id.substr(8, 4)}-${id.substr(12, 4)}-${id.substr(16, 4)}-${id.substr(20)}`;
1695 | const headers = {
1696 | Accept: 'application/json',
1697 | 'Content-Type': 'application/json',
1698 | cookie: `token_v2=${NOTION_TOKEN_V2}`
1699 | };
1700 | const http = new http_client/* HttpClient */.eN(undefined, undefined, {
1701 | headers
1702 | });
1703 | const collectionView = yield ActionUtils.fetchData(databaseId, 'block', http);
1704 | core.info('Fetched database');
1705 | const collection_id = collectionView.collection_id;
1706 | const collection = yield ActionUtils.fetchData(collection_id, 'collection', http);
1707 | core.info('Fetched collection');
1708 | const response = yield http.post(`https://www.notion.so/api/v3/queryCollection`, JSON.stringify({
1709 | collection: {
1710 | id: collection_id,
1711 | spaceId: collectionView.space_id
1712 | },
1713 | collectionView: {
1714 | id: collectionView.view_ids[0],
1715 | spaceId: collectionView.space_id
1716 | },
1717 | loader: {
1718 | type: 'reducer',
1719 | reducers: {
1720 | collection_group_results: {
1721 | type: 'results'
1722 | }
1723 | },
1724 | searchQuery: '',
1725 | userTimeZone: 'Asia/Dhaka'
1726 | }
1727 | }));
1728 | const { recordMap } = JSON.parse(yield response.readBody());
1729 | core.info('Fetched rows');
1730 | const { schema } = collection;
1731 | const [categorySchemaEntry, colorSchemaEntry, , base64SchemaEntry] = ActionUtils.getSchemaEntries(schema);
1732 | const rows = ActionUtils.modifyRows(recordMap, databaseId);
1733 | const categoriesMap = ActionUtils.constructCategoriesMap(categorySchemaEntry[1]);
1734 | ActionUtils.populateCategoriesMapItems(rows, categorySchemaEntry[0], categoriesMap);
1735 | const README_PATH = `${process.env.GITHUB_WORKSPACE}/README.md`;
1736 | core.info(`Reading from ${README_PATH}`);
1737 | const readmeLines = external_fs_default().readFileSync(README_PATH, 'utf-8').split('\n');
1738 | const [startIdx, endIdx] = ActionUtils.checkForSections(readmeLines);
1739 | const newLines = ActionUtils.constructNewContents(categoriesMap, colorSchemaEntry[0], base64SchemaEntry[0]);
1740 | const finalLines = [
1741 | ...readmeLines.slice(0, startIdx + 1),
1742 | ...newLines,
1743 | ...readmeLines.slice(endIdx)
1744 | ];
1745 | core.info(`Writing to ${README_PATH}`);
1746 | external_fs_default().writeFileSync(README_PATH, finalLines.join('\n'), 'utf-8');
1747 | yield ActionUtils.commitFile();
1748 | }
1749 | catch (err) {
1750 | core.error(err.message);
1751 | core.setFailed(err.message);
1752 | }
1753 | });
1754 | }
1755 |
1756 | ;// CONCATENATED MODULE: ./src/index.ts
1757 |
1758 | action();
1759 |
1760 | })();
1761 |
1762 | module.exports = __webpack_exports__;
1763 | /******/ })()
1764 | ;
--------------------------------------------------------------------------------
/dist/licenses.txt:
--------------------------------------------------------------------------------
1 | @actions/core
2 | MIT
3 | The MIT License (MIT)
4 |
5 | Copyright 2019 GitHub
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 |
13 | @vercel/ncc
14 | MIT
15 | Copyright 2018 ZEIT, Inc.
16 |
17 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = async () => {
2 | return {
3 | rootDir: process.cwd(),
4 | testTimeout: 30000,
5 | testEnvironment: 'node',
6 | verbose: true,
7 | testPathIgnorePatterns: [
8 | '/node_modules',
9 | '/dist',
10 | '/src/utils/commitFile.ts'
11 | ],
12 | modulePathIgnorePatterns: ['/dist'],
13 | roots: ['/tests'],
14 | testMatch: ['/tests/**/*.test.ts'],
15 | transform: {
16 | '^.+\\.(ts)$': 'ts-jest'
17 | },
18 | collectCoverageFrom: ['src/utils/{!(commitFile),}.ts', 'src/action.ts'],
19 | collectCoverage: true,
20 | coverageDirectory: './coverage',
21 | coverageThreshold: {
22 | global: {
23 | branches: 95,
24 | functions: 95,
25 | lines: 95,
26 | statements: -10
27 | }
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/media/Logo-600.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/Logo-600.png
--------------------------------------------------------------------------------
/media/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/Logo.png
--------------------------------------------------------------------------------
/media/github_readme_learn_section.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/github_readme_learn_section.png
--------------------------------------------------------------------------------
/media/notion_full_page_db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/notion_full_page_db.png
--------------------------------------------------------------------------------
/media/notion_full_page_db_id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/notion_full_page_db_id.png
--------------------------------------------------------------------------------
/media/notion_table_schema_options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devorein/github-readme-learn-section-notion/f523364bf0b18514fedc2d19894d731ac131554a/media/notion_table_schema_options.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-readme-learn-section-notion",
3 | "version": "1.0.2",
4 | "description": "A github action to auto-populate github readme learn section with data fetched from a remote notion database",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "prebuild": "npm run format && npm run transpile",
8 | "build": "npx ncc build ./src/index.ts -o dist -t",
9 | "format": "npx prettier --write src/**/*.ts",
10 | "transpile": "npx tsc",
11 | "test": "npx jest"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/Devorein/github-readme-learn-section-notion.git"
16 | },
17 | "keywords": [],
18 | "author": "Safwan Shaheer ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/Devorein/github-readme-learn-section-notion/issues"
22 | },
23 | "homepage": "https://github.com/Devorein/github-readme-learn-section-notion#readme",
24 | "dependencies": {
25 | "@actions/core": "^1.2.7",
26 | "@actions/http-client": "^1.0.11"
27 | },
28 | "devDependencies": {
29 | "@nishans/types": "^0.0.35",
30 | "@types/jest": "^26.0.23",
31 | "@types/node": "^15.0.1",
32 | "jest": "^26.6.3",
33 | "prettier": "^2.2.1",
34 | "ts-jest": "^26.5.5",
35 | "@vercel/ncc": "^0.28.5",
36 | "typescript": "^4.2.4"
37 | }
38 | }
--------------------------------------------------------------------------------
/scripts/codecov.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | codecov_file="${GITHUB_WORKSPACE}/scripts/codecov.sh"
4 |
5 | curl -s https://codecov.io/bash > $codecov_file
6 | chmod +x $codecov_file
7 |
8 | file="${GITHUB_WORKSPACE}/coverage/lcov.info"
9 | $codecov_file -f $file -v -t $CODECOV_TOKEN
--------------------------------------------------------------------------------
/src/action.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import { HttpClient } from '@actions/http-client';
3 | import { IRequestOptions } from '@actions/http-client/interfaces';
4 | import { ICollection, RecordMap, TCollectionBlock } from '@nishans/types';
5 | import fs from 'fs';
6 | import { ActionUtils } from './utils';
7 |
8 | export async function action() {
9 | try {
10 | const NOTION_TOKEN_V2 = core.getInput('token_v2');
11 | let id = core.getInput('database_id').replace(/-/g, '');
12 | const databaseId = `${id.substr(0, 8)}-${id.substr(8, 4)}-${id.substr(
13 | 12,
14 | 4
15 | )}-${id.substr(16, 4)}-${id.substr(20)}`;
16 |
17 | const headers: IRequestOptions['headers'] = {
18 | Accept: 'application/json',
19 | 'Content-Type': 'application/json',
20 | cookie: `token_v2=${NOTION_TOKEN_V2}`
21 | };
22 |
23 | const http = new HttpClient(undefined, undefined, {
24 | headers
25 | });
26 |
27 | const collectionView = await ActionUtils.fetchData(
28 | databaseId,
29 | 'block',
30 | http
31 | );
32 | core.info('Fetched database');
33 |
34 | const collection_id = collectionView.collection_id;
35 | const collection = await ActionUtils.fetchData(
36 | collection_id,
37 | 'collection',
38 | http
39 | );
40 |
41 | core.info('Fetched collection');
42 |
43 | const response = await http.post(
44 | `https://www.notion.so/api/v3/queryCollection`,
45 | JSON.stringify({
46 | collection: {
47 | id: collection_id,
48 | spaceId: collectionView.space_id
49 | },
50 | collectionView: {
51 | id: collectionView.view_ids[0],
52 | spaceId: collectionView.space_id
53 | },
54 | loader: {
55 | type: 'reducer',
56 | reducers: {
57 | collection_group_results: {
58 | type: 'results'
59 | }
60 | },
61 | searchQuery: '',
62 | userTimeZone: 'Asia/Dhaka'
63 | }
64 | })
65 | );
66 |
67 | const { recordMap } = JSON.parse(await response.readBody()) as {
68 | recordMap: RecordMap;
69 | };
70 |
71 | core.info('Fetched rows');
72 | const { schema } = collection;
73 | const [
74 | categorySchemaEntry,
75 | colorSchemaEntry,
76 | ,
77 | base64SchemaEntry
78 | ] = ActionUtils.getSchemaEntries(schema);
79 |
80 | const rows = ActionUtils.modifyRows(recordMap, databaseId);
81 | const categoriesMap = ActionUtils.constructCategoriesMap(
82 | categorySchemaEntry[1]
83 | );
84 | ActionUtils.populateCategoriesMapItems(
85 | rows,
86 | categorySchemaEntry[0],
87 | categoriesMap
88 | );
89 |
90 | const README_PATH = `${process.env.GITHUB_WORKSPACE}/README.md`;
91 | core.info(`Reading from ${README_PATH}`);
92 |
93 | const readmeLines = fs.readFileSync(README_PATH, 'utf-8').split('\n');
94 |
95 | const [startIdx, endIdx] = ActionUtils.checkForSections(readmeLines);
96 | const newLines = ActionUtils.constructNewContents(
97 | categoriesMap,
98 | colorSchemaEntry[0],
99 | base64SchemaEntry[0]
100 | );
101 |
102 | const finalLines = [
103 | ...readmeLines.slice(0, startIdx + 1),
104 | ...newLines,
105 | ...readmeLines.slice(endIdx)
106 | ];
107 |
108 | core.info(`Writing to ${README_PATH}`);
109 |
110 | fs.writeFileSync(README_PATH, finalLines.join('\n'), 'utf-8');
111 | await ActionUtils.commitFile();
112 | } catch (err) {
113 | core.error(err.message);
114 | core.setFailed(err.message);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { action } from './action';
2 |
3 | action();
4 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { IPage, TTextColor } from '@nishans/types';
2 |
3 | export type ICategoryMap = Map<
4 | string,
5 | {
6 | items: IPage['properties'][];
7 | color: TTextColor;
8 | }
9 | >;
10 |
--------------------------------------------------------------------------------
/src/utils/checkForSections.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 |
3 | export const checkForSections = (readmeLines: string[]) => {
4 | const startIdx = readmeLines.findIndex(
5 | (content) => content.trim() === ''
6 | );
7 |
8 | if (startIdx === -1) {
9 | core.setFailed(
10 | `Couldn't find the comment. Exiting!`
11 | );
12 | }
13 |
14 | const endIdx = readmeLines.findIndex(
15 | (content) => content.trim() === ''
16 | );
17 |
18 | if (endIdx === -1) {
19 | core.setFailed(
20 | `Couldn't find the comment. Exiting!`
21 | );
22 | }
23 |
24 | return [startIdx, endIdx] as const;
25 | };
26 |
--------------------------------------------------------------------------------
/src/utils/commitFile.ts:
--------------------------------------------------------------------------------
1 | import { spawn } from 'child_process';
2 |
3 | const exec = (cmd: string, args: string[] = []) =>
4 | new Promise((resolve, reject) => {
5 | const app = spawn(cmd, args, { stdio: 'pipe' });
6 | let stdout = '';
7 | app.stdout.on('data', (data) => {
8 | stdout = data;
9 | });
10 | app.on('close', (code) => {
11 | if (code !== 0 && !stdout.includes('nothing to commit')) {
12 | const err = new Error(`Invalid status code: ${code}`) as any;
13 | err.code = code;
14 | return reject(err);
15 | }
16 | return resolve(code);
17 | });
18 | app.on('error', reject);
19 | });
20 |
21 | export const commitFile = async () => {
22 | await exec('git', [
23 | 'config',
24 | '--global',
25 | 'user.email',
26 | '41898282+github-actions[bot]@users.noreply.github.com'
27 | ]);
28 | await exec('git', ['config', '--global', 'user.name', 'readme-bot']);
29 | await exec('git', ['add', 'README.md']);
30 | await exec('git', ['commit', '-m', 'Updated readme with learn section']);
31 | await exec('git', ['push']);
32 | };
33 |
--------------------------------------------------------------------------------
/src/utils/constructCategoriesMap.ts:
--------------------------------------------------------------------------------
1 | import { SelectSchemaUnit } from '@nishans/types';
2 | import { ICategoryMap } from '../types';
3 |
4 | export const constructCategoriesMap = (schema_unit: SelectSchemaUnit) => {
5 | const categories = schema_unit.options
6 | .map((option) => ({
7 | color: option.color,
8 | value: option.value
9 | }))
10 | .sort((categoryA, categoryB) =>
11 | categoryA.value > categoryB.value ? 1 : -1
12 | );
13 |
14 | const categories_map: ICategoryMap = new Map();
15 |
16 | categories.forEach((category) => {
17 | categories_map.set(category.value, {
18 | items: [],
19 | ...category
20 | });
21 | });
22 | return categories_map;
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/constructNewContents.ts:
--------------------------------------------------------------------------------
1 | import { TTextColor } from '@nishans/types';
2 | import qs from 'querystring';
3 | import { ICategoryMap } from '../types';
4 |
5 | const ColorMap: Record = {
6 | default: '505558',
7 | gray: '979a9b',
8 | brown: '695b55',
9 | orange: '9f7445',
10 | yellow: '9f9048',
11 | green: '467870',
12 | blue: '487088',
13 | purple: '6c598f',
14 | pink: '904d74',
15 | red: '9f5c58',
16 | teal: '467870'
17 | };
18 |
19 | export const constructNewContents = (
20 | categoriesMap: ICategoryMap,
21 | colorSchemaUnitKey: string,
22 | base64SchemaUnitKey: string
23 | ) => {
24 | const newContents: string[] = [];
25 | for (const [category, categoryInfo] of categoriesMap) {
26 | const content = [
27 | `}-${ColorMap[categoryInfo.color]})
`
30 | ];
31 | categoryInfo.items.forEach((item) => {
32 | const title = item.title && item.title[0][0];
33 | if (!title)
34 | throw new Error(`Each row must have value in the Name column`);
35 | let logo: string = qs.escape(title);
36 | // At first check if the user provided a base64 encoded svg logo
37 | if (item[base64SchemaUnitKey]?.[0][0]) {
38 | logo = item[base64SchemaUnitKey][0][0];
39 | }
40 | content.push(
41 | `
`
44 | );
45 | });
46 | newContents.push(...content, '
');
47 | }
48 | return newContents;
49 | };
50 |
--------------------------------------------------------------------------------
/src/utils/fetchData.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import { HttpClient } from '@actions/http-client';
3 | import { RecordMap, TData } from '@nishans/types';
4 |
5 | export const fetchData = async (
6 | id: string,
7 | table: keyof RecordMap,
8 | http: HttpClient
9 | ) => {
10 | const response = await http.post(
11 | `https://www.notion.so/api/v3/syncRecordValues`,
12 | JSON.stringify({
13 | requests: [
14 | {
15 | id,
16 | table,
17 | version: -1
18 | }
19 | ]
20 | })
21 | );
22 |
23 | const body = JSON.parse(await response.readBody()) as {
24 | recordMap: RecordMap;
25 | };
26 |
27 | const data = body.recordMap[table]![id].value as T;
28 |
29 | if (!data) {
30 | core.setFailed(
31 | `Either your NOTION_TOKEN_V2 has expired or a ${table} with id:${id} doesn't exist`
32 | );
33 | }
34 |
35 | return data;
36 | };
37 |
--------------------------------------------------------------------------------
/src/utils/getSchemaEntries.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import {
3 | Schema,
4 | SelectSchemaUnit,
5 | TextSchemaUnit,
6 | TitleSchemaUnit
7 | } from '@nishans/types';
8 |
9 | export const getSchemaEntries = (schema: Schema) => {
10 | const schemaEntries = Object.entries(schema);
11 | let categorySchemaEntry: [string, SelectSchemaUnit] = undefined as any,
12 | nameSchemaEntry: [string, TitleSchemaUnit] = undefined as any,
13 | colorSchemaEntry: [string, TextSchemaUnit] = undefined as any,
14 | base64SchemaEntry: [string, TextSchemaUnit] = undefined as any;
15 |
16 | schemaEntries.forEach((schemaEntry) => {
17 | if (schemaEntry[1].type === 'text' && schemaEntry[1].name === 'Color') {
18 | colorSchemaEntry = schemaEntry as [string, TextSchemaUnit];
19 | } else if (
20 | schemaEntry[1].type === 'title' &&
21 | schemaEntry[1].name === 'Name'
22 | ) {
23 | nameSchemaEntry = schemaEntry as [string, TitleSchemaUnit];
24 | } else if (
25 | schemaEntry[1].type === 'select' &&
26 | schemaEntry[1].name === 'Category'
27 | ) {
28 | categorySchemaEntry = schemaEntry as [string, SelectSchemaUnit];
29 | } else if (
30 | schemaEntry[1].type === 'text' &&
31 | schemaEntry[1].name === 'Base64'
32 | ) {
33 | base64SchemaEntry = schemaEntry as [string, TextSchemaUnit];
34 | }
35 | });
36 |
37 | if (!categorySchemaEntry)
38 | core.setFailed(
39 | "Couldn't find Category named select type column in the database"
40 | );
41 | if (!nameSchemaEntry)
42 | core.setFailed(
43 | "Couldn't find Color named text type column in the database"
44 | );
45 | if (!colorSchemaEntry)
46 | core.setFailed(
47 | "Couldn't find Name named title type column in the database"
48 | );
49 | return [
50 | categorySchemaEntry,
51 | colorSchemaEntry,
52 | nameSchemaEntry,
53 | base64SchemaEntry
54 | ] as const;
55 | };
56 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { checkForSections } from './checkForSections';
2 | import { commitFile } from './commitFile';
3 | import { constructCategoriesMap } from './constructCategoriesMap';
4 | import { constructNewContents } from './constructNewContents';
5 | import { fetchData } from './fetchData';
6 | import { getSchemaEntries } from './getSchemaEntries';
7 | import { modifyRows } from './modifyRows';
8 | import { populateCategoriesMapItems } from './populateCategoriesMapItems';
9 |
10 | export const ActionUtils = {
11 | checkForSections,
12 | commitFile,
13 | constructCategoriesMap,
14 | constructNewContents,
15 | fetchData,
16 | getSchemaEntries,
17 | modifyRows,
18 | populateCategoriesMapItems
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/modifyRows.ts:
--------------------------------------------------------------------------------
1 | import { IPage, RecordMap } from '@nishans/types';
2 |
3 | /**
4 | * Sorts an array of page blocks by their title
5 | * @param recordMap Record map to sort blocks from
6 | * @param databaseId Database id to filter block
7 | * @returns An array of pages sorted by their title
8 | */
9 | export const modifyRows = (
10 | recordMap: Pick,
11 | databaseId: string
12 | ) => {
13 | return Object.values(recordMap.block)
14 | .filter((block) => block.value.id !== databaseId)
15 | .map((block) => block.value as IPage)
16 | .sort((rowA, rowB) =>
17 | rowA.properties.title[0][0] > rowB.properties.title[0][0] ? 1 : -1
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/populateCategoriesMapItems.ts:
--------------------------------------------------------------------------------
1 | import { IPage } from '@nishans/types';
2 | import { ICategoryMap } from '../types';
3 |
4 | export const populateCategoriesMapItems = (
5 | rows: IPage[],
6 | category_schema_id: string,
7 | categories_map: ICategoryMap
8 | ) => {
9 | rows.forEach((row) => {
10 | const category =
11 | row.properties[category_schema_id] &&
12 | row.properties[category_schema_id][0][0];
13 | if (!category) throw new Error('Each row must have a category value');
14 | const category_value = categories_map.get(category);
15 | category_value!.items.push(row.properties);
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/tests/action.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import { HttpClient } from '@actions/http-client';
3 | import { Schema, SelectSchemaUnit } from '@nishans/types';
4 | import fs from 'fs';
5 | import { action } from '../src/action';
6 | import { ActionUtils } from '../src/utils';
7 |
8 | afterEach(() => {
9 | jest.restoreAllMocks();
10 | });
11 |
12 | it(`Should work`, async () => {
13 | const GITHUB_WORKSPACE = `https://github.com/Devorein/github-readme-learn-section-notion`;
14 | process.env.GITHUB_WORKSPACE = GITHUB_WORKSPACE;
15 |
16 | const category_schema_unit: SelectSchemaUnit = {
17 | name: 'Category',
18 | options: [
19 | {
20 | color: 'teal',
21 | id: '1',
22 | value: 'Runtime'
23 | },
24 | {
25 | color: 'yellow',
26 | id: '2',
27 | value: 'Library'
28 | }
29 | ],
30 | type: 'select'
31 | },
32 | block_3 = {
33 | id: 'block_3',
34 | properties: {
35 | title: [['Node.js']],
36 | color: 'green',
37 | category: [['Runtime']]
38 | }
39 | } as any,
40 | block_2 = {
41 | id: 'block_2',
42 | properties: {
43 | title: [['React']],
44 | color: 'blue',
45 | category: [['Library']]
46 | }
47 | } as any,
48 | schema = {
49 | title: {
50 | type: 'title',
51 | name: 'Name'
52 | },
53 | color: {
54 | type: 'text',
55 | name: 'Color'
56 | },
57 | category: category_schema_unit
58 | } as Schema,
59 | recordMap = {
60 | block: {
61 | block_1: {
62 | role: 'editor',
63 | value: {
64 | id: 'block_1'
65 | }
66 | },
67 | block_2: {
68 | role: 'editor',
69 | value: block_2
70 | },
71 | block_3: {
72 | role: 'editor',
73 | value: block_3
74 | }
75 | }
76 | } as any;
77 |
78 | const getInputMock = jest
79 | .spyOn(core, 'getInput')
80 | .mockImplementationOnce(() => 'token_v2')
81 | .mockImplementationOnce(() => 'block_1');
82 | const coreInfo = jest.spyOn(core, 'info');
83 | jest
84 | .spyOn(ActionUtils, 'fetchData')
85 | .mockImplementationOnce(async () => {
86 | return {
87 | collection_id: 'collection_1',
88 | space_id: 'space_id',
89 | view_ids: ['view_1']
90 | } as any;
91 | })
92 | .mockImplementationOnce(async () => {
93 | return {
94 | schema
95 | } as any;
96 | });
97 |
98 | let http = new HttpClient();
99 |
100 | jest.spyOn(http, 'post' as any).mockImplementationOnce(async () => {
101 | return {
102 | async resBody() {
103 | return JSON.stringify({ recordMap });
104 | }
105 | };
106 | });
107 |
108 | jest.spyOn(ActionUtils, 'getSchemaEntries').mockImplementationOnce(() => {
109 | return [
110 | ['category', category_schema_unit],
111 | ['color', { name: 'Color', type: 'text' }],
112 | ['title', { name: 'Name', type: 'title' }],
113 | ['base64', { name: 'Base64', type: 'text' }]
114 | ];
115 | });
116 |
117 | jest
118 | .spyOn(ActionUtils, 'modifyRows')
119 | .mockImplementationOnce(() => [block_3, block_2]);
120 |
121 | jest.spyOn(ActionUtils, 'constructCategoriesMap').mockImplementationOnce(
122 | () =>
123 | new Map([
124 | [
125 | 'Runtime',
126 | {
127 | color: 'teal',
128 | items: []
129 | }
130 | ],
131 | [
132 | 'Library',
133 | {
134 | color: 'yellow',
135 | items: []
136 | }
137 | ]
138 | ])
139 | );
140 | const readFileSyncMock = jest
141 | .spyOn(fs, 'readFileSync')
142 | .mockImplementationOnce(
143 | () =>
144 | '# Header\nfirst\n\n\nsecond'
145 | );
146 | jest
147 | .spyOn(ActionUtils, 'checkForSections')
148 | .mockImplementationOnce(() => [2, 3]);
149 |
150 | jest.spyOn(ActionUtils, 'constructNewContents').mockImplementation(() => {
151 | return ['new line 1', 'new line 2'];
152 | });
153 | const writeFileSyncMock = jest
154 | .spyOn(fs, 'writeFileSync')
155 | .mockImplementationOnce(() => undefined);
156 | jest
157 | .spyOn(ActionUtils, 'commitFile')
158 | .mockImplementationOnce(async () => undefined);
159 |
160 | await action();
161 |
162 | expect(coreInfo).toHaveBeenNthCalledWith(1, 'Fetched database');
163 | expect(coreInfo).toHaveBeenNthCalledWith(2, 'Fetched collection');
164 | expect(coreInfo).toHaveBeenNthCalledWith(3, 'Fetched rows');
165 | expect(coreInfo).toHaveBeenNthCalledWith(
166 | 4,
167 | `Reading from ${GITHUB_WORKSPACE}/README.md`
168 | );
169 | expect(coreInfo).toHaveBeenNthCalledWith(
170 | 5,
171 | `Writing to ${GITHUB_WORKSPACE}/README.md`
172 | );
173 | expect(getInputMock).toHaveBeenNthCalledWith(1, 'token_v2');
174 | expect(getInputMock).toHaveBeenNthCalledWith(2, 'database_id');
175 | expect(readFileSyncMock).toHaveBeenCalledWith(
176 | `${GITHUB_WORKSPACE}/README.md`,
177 | 'utf-8'
178 | );
179 | expect(writeFileSyncMock).toHaveBeenCalledWith(
180 | `${GITHUB_WORKSPACE}/README.md`,
181 | '# Header\nfirst\n\nnew line 1\nnew line 2\n\nsecond',
182 | 'utf-8'
183 | );
184 | });
185 |
--------------------------------------------------------------------------------
/tests/utils/checkForSections.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import { checkForSections } from '../../src/utils/checkForSections';
3 |
4 | afterEach(() => {
5 | jest.restoreAllMocks();
6 | });
7 |
8 | it(`Should return correct start and end index`, () => {
9 | const [startIdx, endIdx] = checkForSections([
10 | '1',
11 | '',
12 | '2',
13 | '',
14 | '3'
15 | ]);
16 | expect(startIdx).toBe(1);
17 | expect(endIdx).toBe(3);
18 | });
19 |
20 | it(`Should return correct start and end index`, () => {
21 | const setFailedMock = jest.spyOn(core, 'setFailed');
22 |
23 | const [startIdx, endIdx] = checkForSections(['1', '2', '3']);
24 | expect(setFailedMock).toHaveBeenNthCalledWith(
25 | 1,
26 | `Couldn't find the comment. Exiting!`
27 | );
28 | expect(setFailedMock).toHaveBeenNthCalledWith(
29 | 2,
30 | `Couldn't find the comment. Exiting!`
31 | );
32 | expect(startIdx).toBe(-1);
33 | expect(endIdx).toBe(-1);
34 | });
35 |
--------------------------------------------------------------------------------
/tests/utils/constructCategoriesMap.test.ts:
--------------------------------------------------------------------------------
1 | import { constructCategoriesMap } from '../../src/utils/constructCategoriesMap';
2 |
3 | it(`Should work`, () => {
4 | const option_1 = {
5 | color: 'blue',
6 | value: 'A'
7 | } as any,
8 | option_2 = {
9 | color: 'yellow',
10 | value: 'C'
11 | } as any,
12 | option_3 = {
13 | color: 'red',
14 | value: 'B'
15 | } as any;
16 | const categories_map = constructCategoriesMap({
17 | name: 'Options',
18 | options: [option_1, option_2, option_3],
19 | type: 'select'
20 | });
21 |
22 | expect(Array.from(categories_map.entries())).toStrictEqual([
23 | [
24 | 'A',
25 | {
26 | items: [],
27 | ...option_1
28 | }
29 | ],
30 | [
31 | 'B',
32 | {
33 | items: [],
34 | ...option_3
35 | }
36 | ],
37 | [
38 | 'C',
39 | {
40 | items: [],
41 | ...option_2
42 | }
43 | ]
44 | ]);
45 | });
46 |
--------------------------------------------------------------------------------
/tests/utils/constructNewContents.test.ts:
--------------------------------------------------------------------------------
1 | import { constructNewContents } from '../../src/utils/constructNewContents';
2 |
3 | it('Should Work', () => {
4 | const categories_map = new Map([
5 | [
6 | 'Tech Tools',
7 | {
8 | items: [
9 | {
10 | title: [['React']],
11 | color: [['blue']]
12 | },
13 | {
14 | title: [['Apollo Graphql']]
15 | },
16 | {
17 | title: [['Terraform']],
18 | base64: [['base64']]
19 | }
20 | ],
21 | color: 'teal'
22 | }
23 | ]
24 | ]) as any;
25 |
26 | const newContents = constructNewContents(categories_map, 'color', 'base64');
27 | expect(newContents).toStrictEqual([
28 | '
',
29 | '
',
30 | '
',
31 | '
',
32 | '
'
33 | ]);
34 | });
35 |
36 | it('Should throw error if title not present', () => {
37 | const categories_map = new Map([
38 | [
39 | 'Tech Tools',
40 | {
41 | items: [{}],
42 | color: 'teal'
43 | }
44 | ]
45 | ]) as any;
46 |
47 | expect(() =>
48 | constructNewContents(categories_map, 'color', 'base64')
49 | ).toThrow();
50 | });
51 |
--------------------------------------------------------------------------------
/tests/utils/fetchData.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import { HttpClient } from '@actions/http-client';
3 | import { fetchData } from '../../src/utils/fetchData';
4 |
5 | afterEach(() => {
6 | jest.restoreAllMocks();
7 | });
8 |
9 | it(`Should fetch data successfully`, async () => {
10 | let http = new HttpClient();
11 |
12 | const syncRecordValuesMock = jest
13 | .spyOn(http, 'post' as any)
14 | .mockImplementationOnce(async () => {
15 | return {
16 | async readBody() {
17 | return JSON.stringify({
18 | recordMap: {
19 | block: {
20 | block_1: {
21 | role: 'comment_only',
22 | value: {
23 | id: 'block_1'
24 | }
25 | }
26 | }
27 | }
28 | });
29 | }
30 | };
31 | });
32 |
33 | const data = await fetchData('block_1', 'block', http);
34 |
35 | expect(data).toStrictEqual({
36 | id: 'block_1'
37 | });
38 | expect(syncRecordValuesMock).toHaveBeenCalledWith(
39 | `https://www.notion.so/api/v3/syncRecordValues`,
40 | JSON.stringify({
41 | requests: [
42 | {
43 | id: 'block_1',
44 | table: 'block',
45 | version: -1
46 | }
47 | ]
48 | })
49 | );
50 | });
51 |
52 | it(`Should not fetch data`, async () => {
53 | let http = new HttpClient();
54 | const setFailed = jest.spyOn(core, 'setFailed');
55 | const syncRecordValuesMock = jest
56 | .spyOn(http, 'post' as any)
57 | .mockImplementationOnce(async () => {
58 | return {
59 | async readBody() {
60 | return JSON.stringify({
61 | recordMap: {
62 | block: {
63 | block_1: {
64 | role: 'comment_only'
65 | }
66 | } as any
67 | }
68 | });
69 | }
70 | };
71 | });
72 |
73 | const data = await fetchData('block_1', 'block', http);
74 |
75 | expect(data).toStrictEqual(undefined);
76 | expect(syncRecordValuesMock).toHaveBeenCalledWith(
77 | `https://www.notion.so/api/v3/syncRecordValues`,
78 | JSON.stringify({
79 | requests: [
80 | {
81 | id: 'block_1',
82 | table: 'block',
83 | version: -1
84 | }
85 | ]
86 | })
87 | );
88 | expect(setFailed).toHaveBeenCalledWith(
89 | `Either your NOTION_TOKEN_V2 has expired or a block with id:block_1 doesn't exist`
90 | );
91 | });
92 |
--------------------------------------------------------------------------------
/tests/utils/getSchemaEntries.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core';
2 | import {
3 | SelectSchemaUnit,
4 | TextSchemaUnit,
5 | TitleSchemaUnit
6 | } from '@nishans/types';
7 | import { getSchemaEntries } from '../../src/utils/getSchemaEntries';
8 |
9 | afterEach(() => {
10 | jest.restoreAllMocks();
11 | });
12 |
13 | it(`Should find both color and category entries`, () => {
14 | const color_schema_unit = {
15 | type: 'text',
16 | name: 'Color'
17 | } as TextSchemaUnit,
18 | category_schema_unit = {
19 | type: 'select',
20 | name: 'Category',
21 | options: []
22 | } as SelectSchemaUnit,
23 | title_schema_unit = {
24 | type: 'title',
25 | name: 'Name',
26 | options: []
27 | } as TitleSchemaUnit;
28 | const [
29 | category_schema_entry,
30 | color_schema_entry,
31 | title_schema_entry
32 | ] = getSchemaEntries({
33 | color: color_schema_unit,
34 | category: category_schema_unit,
35 | title: title_schema_unit
36 | });
37 |
38 | expect(category_schema_entry).toStrictEqual([
39 | 'category',
40 | category_schema_unit
41 | ]);
42 |
43 | expect(title_schema_entry).toStrictEqual(['title', title_schema_unit]);
44 | expect(color_schema_entry).toStrictEqual(['color', color_schema_unit]);
45 | });
46 |
47 | it(`Should not find both color, title, category entries`, () => {
48 | const color_schema_unit = {
49 | type: 'text',
50 | name: 'color'
51 | } as TextSchemaUnit,
52 | category_schema_unit = {
53 | type: 'select',
54 | name: 'category',
55 | options: []
56 | } as SelectSchemaUnit,
57 | title_schema_unit = {
58 | type: 'title',
59 | name: 'name',
60 | options: []
61 | } as TitleSchemaUnit;
62 |
63 | const setFailedMock = jest.spyOn(core, 'setFailed');
64 |
65 | const [
66 | category_schema_entry,
67 | color_schema_entry,
68 | title_schema_entry
69 | ] = getSchemaEntries({
70 | color: color_schema_unit,
71 | category: category_schema_unit,
72 | title: title_schema_unit
73 | });
74 |
75 | expect(setFailedMock).toHaveBeenNthCalledWith(
76 | 1,
77 | "Couldn't find Category named select type column in the database"
78 | );
79 | expect(setFailedMock).toHaveBeenNthCalledWith(
80 | 2,
81 | "Couldn't find Color named text type column in the database"
82 | );
83 | expect(setFailedMock).toHaveBeenNthCalledWith(
84 | 3,
85 | "Couldn't find Name named title type column in the database"
86 | );
87 | expect(category_schema_entry).toStrictEqual(undefined);
88 | expect(color_schema_entry).toStrictEqual(undefined);
89 | expect(title_schema_entry).toStrictEqual(undefined);
90 | });
91 |
--------------------------------------------------------------------------------
/tests/utils/modifyRows.test.ts:
--------------------------------------------------------------------------------
1 | import { modifyRows } from '../../src/utils/modifyRows';
2 |
3 | it('Should work', () => {
4 | const rows = modifyRows(
5 | {
6 | block: {
7 | block_1: {
8 | role: 'editor',
9 | value: {
10 | properties: {
11 | title: [['A']]
12 | }
13 | } as any
14 | },
15 | block_2: {
16 | role: 'editor',
17 | value: {
18 | properties: {
19 | title: [['C']]
20 | }
21 | } as any
22 | },
23 | block_3: {
24 | role: 'editor',
25 | value: {
26 | properties: {
27 | title: [['B']]
28 | }
29 | } as any
30 | }
31 | }
32 | },
33 | 'block_4'
34 | );
35 | expect(rows).toStrictEqual([
36 | {
37 | properties: {
38 | title: [['A']]
39 | }
40 | },
41 | {
42 | properties: {
43 | title: [['B']]
44 | }
45 | },
46 | {
47 | properties: {
48 | title: [['C']]
49 | }
50 | }
51 | ]);
52 | });
53 |
--------------------------------------------------------------------------------
/tests/utils/populateCategoriesMapItems.test.ts:
--------------------------------------------------------------------------------
1 | import { IPage } from '@nishans/types';
2 | import { ICategoryMap } from '../../src/types';
3 | import { populateCategoriesMapItems } from '../../src/utils/populateCategoriesMapItems';
4 |
5 | it(`Should work`, () => {
6 | const category_map: ICategoryMap = new Map([
7 | [
8 | 'Library',
9 | {
10 | color: 'teal',
11 | items: []
12 | }
13 | ]
14 | ]);
15 | const rows: IPage[] = [
16 | {
17 | properties: {
18 | title: [['React']],
19 | category: [['Library']]
20 | }
21 | } as any
22 | ];
23 | populateCategoriesMapItems(rows, 'category', category_map);
24 |
25 | expect(Array.from(category_map.entries())).toStrictEqual([
26 | [
27 | 'Library',
28 | {
29 | color: 'teal',
30 | items: [
31 | {
32 | title: [['React']],
33 | category: [['Library']]
34 | }
35 | ]
36 | }
37 | ]
38 | ]);
39 | });
40 |
41 | it(`Should fail if category not provided`, () => {
42 | const category_map: ICategoryMap = new Map([
43 | [
44 | 'Library',
45 | {
46 | color: 'teal',
47 | items: []
48 | }
49 | ]
50 | ]);
51 | const rows: IPage[] = [
52 | {
53 | properties: {
54 | title: [['React']]
55 | }
56 | } as any
57 | ];
58 | expect(() =>
59 | populateCategoriesMapItems(rows, 'category', category_map)
60 | ).toThrow();
61 | });
62 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
5 | "lib": [
6 | "es6"
7 | ],
8 | "strict": false,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "outDir": "./build",
12 | "moduleResolution": "node",
13 | "removeComments": true,
14 | "noImplicitAny": true,
15 | "strictNullChecks": true,
16 | "strictFunctionTypes": true,
17 | "noImplicitThis": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noImplicitReturns": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "allowSyntheticDefaultImports": true,
23 | "esModuleInterop": true,
24 | "emitDecoratorMetadata": true,
25 | "experimentalDecorators": true,
26 | "resolveJsonModule": true,
27 | "baseUrl": ".",
28 | "incremental": false
29 | },
30 | "exclude": [
31 | "__tests__",
32 | "build",
33 | "node_modules"
34 | ]
35 | }
--------------------------------------------------------------------------------