Add 12 files
Browse files- Dockerfile +57 -0
- README.md +26 -11
- cypress/fixtures/seedData.json +20 -0
- cypress/integration/index.spec.js +50 -0
- package.json +26 -0
- public/manifest.json +56 -0
- src/index.css +69 -0
- src/pages/index.tsx +29 -0
- src/styles.module.css +53 -0
- src/variables.css +10 -0
- test/test.js +10 -0
- tsconfig.json +28 -0
Dockerfile
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
FROM node:18-alpine AS base
|
3 |
+
|
4 |
+
# Install dependencies only when needed
|
5 |
+
FROM base AS deps
|
6 |
+
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
7 |
+
RUN apk add --no-cache libc6-compat
|
8 |
+
WORKDIR /app
|
9 |
+
|
10 |
+
# Install dependencies based on the preferred package manager
|
11 |
+
COPY package.json package-lock.json* ./
|
12 |
+
RUN npm install
|
13 |
+
|
14 |
+
# Uncomment the following lines if you want to use a secret at buildtime,
|
15 |
+
# for example to access your private npm packages
|
16 |
+
# RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true # $(cat /run/secrets/HF_EXAMPLE_SECRET)
|
17 |
+
|
18 |
+
# Rebuild the source code only when needed
|
19 |
+
FROM base AS builder
|
20 |
+
WORKDIR /app
|
21 |
+
COPY --from=deps /app/node_modules ./node_modules
|
22 |
+
COPY . .
|
23 |
+
|
24 |
+
# Next.js collects completely anonymous telemetry data about general usage.
|
25 |
+
# Learn more here: https://nextjs.org/telemetry
|
26 |
+
# Uncomment the following line in case you want to disable telemetry during the build.
|
27 |
+
# ENV NEXT_TELEMETRY_DISABLED 1
|
28 |
+
|
29 |
+
RUN npm run build
|
30 |
+
|
31 |
+
# Production image, copy all the files and run next
|
32 |
+
FROM base AS runner
|
33 |
+
WORKDIR /app
|
34 |
+
|
35 |
+
ENV NODE_ENV production
|
36 |
+
# Uncomment the following line in case you want to disable telemetry during runtime.
|
37 |
+
# ENV NEXT_TELEMETRY_DISABLED 1
|
38 |
+
|
39 |
+
RUN addgroup --system --gid 1001 nodejs
|
40 |
+
RUN adduser --system --uid 1001 nextjs
|
41 |
+
|
42 |
+
COPY --from=builder /app/public ./public
|
43 |
+
|
44 |
+
# Automatically leverage output traces to reduce image size
|
45 |
+
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
46 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
47 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
48 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
|
49 |
+
# COPY --from=builder --chown=nextjs:nodejs /app/.next/cache/fetch-cache ./.next/cache/fetch-cache
|
50 |
+
|
51 |
+
USER nextjs
|
52 |
+
|
53 |
+
EXPOSE 3000
|
54 |
+
|
55 |
+
ENV PORT 3000
|
56 |
+
|
57 |
+
CMD ["node", "server.js"]
|
README.md
CHANGED
@@ -1,11 +1,26 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{{< TOC >}}
|
2 |
+
|
3 |
+
# {{< Warning >}}
|
4 |
+
`<APPNAME>` is a simple todo list app built using NextJS and Progressive Web Apps. The app is designed to work on
|
5 |
+
Android devices and has a minimalistic design to provide a clean and easy-to-use interface.
|
6 |
+
|
7 |
+
## Features
|
8 |
+
|
9 |
+
* Create new tasks with a title, deadline, and description
|
10 |
+
* View tasks in a list with sorting options
|
11 |
+
* Mark tasks as complete or incomplete
|
12 |
+
* Delete tasks
|
13 |
+
|
14 |
+
## Future Plans
|
15 |
+
|
16 |
+
* Implement a dark mode theme
|
17 |
+
* Add more advanced features such as reminders, notifications, and a calendar view
|
18 |
+
* Optimize the app for better performance on mobile devices
|
19 |
+
|
20 |
+
## Contributing
|
21 |
+
|
22 |
+
Contributions are welcome! If you have any ideas or suggestions, feel free to open an issue or pull request.
|
23 |
+
|
24 |
+
## License
|
25 |
+
|
26 |
+
MIT
|
cypress/fixtures/seedData.json
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"id": 1,
|
4 |
+
"title": "First task",
|
5 |
+
"deadline": "2024-01-01",
|
6 |
+
"description": "A task"
|
7 |
+
},
|
8 |
+
{
|
9 |
+
"id": 2,
|
10 |
+
"title": "Second task",
|
11 |
+
"deadline": "2023-01-01",
|
12 |
+
"description": "Another task"
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"id": 3,
|
16 |
+
"title": "Third task",
|
17 |
+
"deadline": "2023-01-02",
|
18 |
+
"description": "A third task"
|
19 |
+
}
|
20 |
+
]
|
cypress/integration/index.spec.js
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
describe("Complete App", () => {
|
2 |
+
beforeEach(() => cy.visit("/"));
|
3 |
+
|
4 |
+
it("displays the title", () => {
|
5 |
+
cy.get("h1").should("have.text", "Todo List");
|
6 |
+
});
|
7 |
+
|
8 |
+
it("displays the create task form", () => {
|
9 |
+
cy.get("form").should("have.attr", {
|
10 |
+
"method": "POST",
|
11 |
+
"action": "/task/create",
|
12 |
+
});
|
13 |
+
cy.get("input[name=title]").should("be.visible");
|
14 |
+
cy.get("input[name=deadline]").should("be.visible");
|
15 |
+
cy.get("textarea[name=description]").should("be.visible");
|
16 |
+
cy.get("button[type=submit]").should("be.visible");
|
17 |
+
});
|
18 |
+
|
19 |
+
it("allows the user to create a new task", () => {
|
20 |
+
const newTask = {
|
21 |
+
title: "New task",
|
22 |
+
deadline: "2024-01-01",
|
23 |
+
description: "A new task"
|
24 |
+
};
|
25 |
+
cy.get("input[name=title]").type(newTask.title);
|
26 |
+
cy.get("input[name=deadline]").type(newTask.deadline);
|
27 |
+
cy.get("textarea[name=description]").type(newTask.description);
|
28 |
+
cy.get("button[type=submit]").click();
|
29 |
+
|
30 |
+
cy.get("h2").should("have.text", "Tasks");
|
31 |
+
cy.get("li").should("have.length", 1);
|
32 |
+
cy.get("li:first").should("contain.text", newTask.title);
|
33 |
+
cy.get("li:first").should("contain.text", newTask.deadline);
|
34 |
+
cy.get("li:first").should("contain.text", newTask.description);
|
35 |
+
});
|
36 |
+
|
37 |
+
it("allows the user to mark a task as complete", () => {
|
38 |
+
cy.get("input[type=checkbox]").should("be.visible");
|
39 |
+
cy.get("input[type=checkbox]").click();
|
40 |
+
cy.get("input[type=checkbox]").should("be.checked");
|
41 |
+
});
|
42 |
+
|
43 |
+
it("allows the user to delete a task", () => {
|
44 |
+
cy.get("li:first").should("contain.text", "New task");
|
45 |
+
cy.get("li:first").find("button.delete-task").click();
|
46 |
+
cy.get("li:first").find("button.delete-task").should("have.text", "Delete");
|
47 |
+
cy.get("li:first").should("contain.text", "Task deleted");
|
48 |
+
cy.get("li").should("have.length", 0);
|
49 |
+
});
|
50 |
+
});
|
package.json
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "<APPNAME>",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"description": "A simple todo list app using nextjs with progressive web app that can be installed on Android",
|
5 |
+
"main": "index.js",
|
6 |
+
"scripts": {
|
7 |
+
"start": "next dev",
|
8 |
+
"build": "next build",
|
9 |
+
"test": "jest",
|
10 |
+
"eject": "next eject"
|
11 |
+
},
|
12 |
+
"dependencies": {
|
13 |
+
"@types/next": "12.3.5",
|
14 |
+
"@types/node": "20.4.2",
|
15 |
+
"@types/react": "18.2.15",
|
16 |
+
"@types/react-dom": "18.2.7",
|
17 |
+
"next": "12.3.5",
|
18 |
+
"react": "18.2.0",
|
19 |
+
"react-dom": "18.2.0"
|
20 |
+
},
|
21 |
+
"devDependencies": {
|
22 |
+
"@types/jest": "28.2.2",
|
23 |
+
"jest": "28.2.2",
|
24 |
+
"tsc": "5.1.5"
|
25 |
+
}
|
26 |
+
}
|
public/manifest.json
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"short_name": "<APPNAME>",
|
3 |
+
"name": "<APPNAME> - A simple todo list app using nextjs with progressive web app that can be installed on Android",
|
4 |
+
"description": "A simple todo list app using nextjs with progressive web app that can be installed on Android",
|
5 |
+
"icons": [
|
6 |
+
{
|
7 |
+
"src": "icons/icon-72x72.png",
|
8 |
+
"sizes": "72x72",
|
9 |
+
"type": "image/png"
|
10 |
+
},
|
11 |
+
{
|
12 |
+
"src": "icons/icon-96x96.png",
|
13 |
+
"sizes": "96x96",
|
14 |
+
"type": "image/png"
|
15 |
+
},
|
16 |
+
{
|
17 |
+
"src": "icons/icon-128x128.png",
|
18 |
+
"sizes": "128x128",
|
19 |
+
"type": "image/png"
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"src": "icons/icon-144x144.png",
|
23 |
+
"sizes": "144x144",
|
24 |
+
"type": "image/png"
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"src": "icons/icon-152x152.png",
|
28 |
+
"sizes": "152x152",
|
29 |
+
"type": "image/png"
|
30 |
+
},
|
31 |
+
{
|
32 |
+
"src": "icons/icon-192x192.png",
|
33 |
+
"sizes": "192x192",
|
34 |
+
"type": "image/png"
|
35 |
+
},
|
36 |
+
{
|
37 |
+
"src": "icons/icon-256x256.png",
|
38 |
+
"sizes": "256x256",
|
39 |
+
"type": "image/png"
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"src": "icons/icon-384x384.png",
|
43 |
+
"sizes": "384x384",
|
44 |
+
"type": "image/png"
|
45 |
+
},
|
46 |
+
{
|
47 |
+
"src": "icons/icon-512x512.png",
|
48 |
+
"sizes": "512x512",
|
49 |
+
"type": "image/png"
|
50 |
+
}
|
51 |
+
],
|
52 |
+
"start_url": "/",
|
53 |
+
"display": "standalone",
|
54 |
+
"theme_color": "#000000",
|
55 |
+
"background_color": "#ffffff"
|
56 |
+
}
|
src/index.css
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html,
|
2 |
+
body {
|
3 |
+
margin: 0;
|
4 |
+
padding: 0;
|
5 |
+
font-family: Arial, sans-serif;
|
6 |
+
background-color: var(--background-color);
|
7 |
+
}
|
8 |
+
|
9 |
+
.container {
|
10 |
+
margin: 0;
|
11 |
+
padding: 0;
|
12 |
+
display: flex;
|
13 |
+
flex-flow: row wrap;
|
14 |
+
justify-content: space-evenly;
|
15 |
+
}
|
16 |
+
|
17 |
+
.task-container {
|
18 |
+
background-color: var(--primary-lightgray-color);
|
19 |
+
box-shadow: 0px 0px 10px 0px rgba(167, 167, 167, 0.2);
|
20 |
+
border-radius: 0.25rem;
|
21 |
+
padding: 1rem;
|
22 |
+
margin-bottom: 1rem;
|
23 |
+
}
|
24 |
+
|
25 |
+
.task-title {
|
26 |
+
font-weight: bold;
|
27 |
+
font-size: 1.25rem;
|
28 |
+
line-height: 1.5;
|
29 |
+
color: var(--text-color);
|
30 |
+
}
|
31 |
+
|
32 |
+
.task-deadline {
|
33 |
+
font-size: 0.75rem;
|
34 |
+
line-height: 1.25;
|
35 |
+
color: var(--text-color);
|
36 |
+
}
|
37 |
+
|
38 |
+
.task-description {
|
39 |
+
font-size: 0.875rem;
|
40 |
+
line-height: 1.25;
|
41 |
+
color: var(--text-color);
|
42 |
+
}
|
43 |
+
|
44 |
+
.task-field {
|
45 |
+
width: 100%;
|
46 |
+
outline: none;
|
47 |
+
background-color: var(--primary-beige-color);
|
48 |
+
padding: 0.5rem;
|
49 |
+
border: none;
|
50 |
+
border-radius: 0.25rem;
|
51 |
+
text-align: center;
|
52 |
+
}
|
53 |
+
|
54 |
+
.task-button {
|
55 |
+
display: inline-block;
|
56 |
+
width: 100%;
|
57 |
+
font-size: 1.25rem;
|
58 |
+
background-color: var(--primary-color);
|
59 |
+
color: #fff;
|
60 |
+
border: none;
|
61 |
+
cursor: pointer;
|
62 |
+
padding: 1rem;
|
63 |
+
transition: background-color 0.3s ease;
|
64 |
+
}
|
65 |
+
|
66 |
+
.task-button:hover {
|
67 |
+
background-color: var(--primary-color);
|
68 |
+
color: #fff;
|
69 |
+
}
|
src/pages/index.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import styles from "../styles.module.css";
|
2 |
+
|
3 |
+
const Home = () => {
|
4 |
+
return (
|
5 |
+
<div className="text-gray-900 text-center">
|
6 |
+
<h1 className="text-6xl m-20 mb-6 font-bold">Todo List</h1>
|
7 |
+
<div className="min-h-$screen-2 bg-white px-6 py-4">
|
8 |
+
<h2 className="text-5xl font-medium">Create a New Task:</h2>
|
9 |
+
<form className="mx-auto w-full md:w-3/4">
|
10 |
+
<input
|
11 |
+
type="text"
|
12 |
+
className="block bg-white text-gray-900 font-medium text-xl mb-2 mt-4"
|
13 |
+
placeholder="Task Title"
|
14 |
+
/>
|
15 |
+
<input
|
16 |
+
type="date"
|
17 |
+
className="block bg-white text-gray-900 font-medium text-sm mb-2 mt-4"
|
18 |
+
placeholder="Task Deadline"
|
19 |
+
/>
|
20 |
+
<button className="block font-medium text-sm text-white transition duration-150 ease-in-out bg-blue-500 hover:bg-blue-600 border-blue-500 hover:border-blue-600 font-logo rounded px-2 mt-6 py-2">
|
21 |
+
Create Task
|
22 |
+
</button>
|
23 |
+
</form>
|
24 |
+
</div>
|
25 |
+
</div>
|
26 |
+
);
|
27 |
+
};
|
28 |
+
|
29 |
+
export { Home };
|
src/styles.module.css
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "./variables.css";
|
2 |
+
|
3 |
+
.container {
|
4 |
+
display: grid;
|
5 |
+
grid-template-columns: repeat(12, 1fr);
|
6 |
+
gap: 2rem;
|
7 |
+
}
|
8 |
+
|
9 |
+
.task-container {
|
10 |
+
border-radius: 0.25rem;
|
11 |
+
background-color: #fff;
|
12 |
+
padding: 1rem;
|
13 |
+
}
|
14 |
+
|
15 |
+
.task-title {
|
16 |
+
font-weight: bold;
|
17 |
+
font-size: 1.25rem;
|
18 |
+
}
|
19 |
+
|
20 |
+
.task-deadline {
|
21 |
+
font-size: 0.875rem;
|
22 |
+
}
|
23 |
+
|
24 |
+
.task-description {
|
25 |
+
font-size: 0.875rem;
|
26 |
+
}
|
27 |
+
|
28 |
+
.task-field {
|
29 |
+
width: 100%;
|
30 |
+
outline: none;
|
31 |
+
background-color: #f5f5f5;
|
32 |
+
padding: 0.5rem;
|
33 |
+
border: none;
|
34 |
+
border-radius: 0.25rem;
|
35 |
+
text-align: center;
|
36 |
+
}
|
37 |
+
|
38 |
+
.task-button {
|
39 |
+
display: inline-block;
|
40 |
+
width: 100%;
|
41 |
+
font-size: 1.25rem;
|
42 |
+
background-color: #3498db;
|
43 |
+
color: #fff;
|
44 |
+
border: none;
|
45 |
+
cursor: pointer;
|
46 |
+
padding: 1rem;
|
47 |
+
transition: background-color 0.3s ease;
|
48 |
+
}
|
49 |
+
|
50 |
+
.task-button:hover {
|
51 |
+
background-color: #555;
|
52 |
+
color: #fafafa;
|
53 |
+
}
|
src/variables.css
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
|
4 |
+
:root {
|
5 |
+
--text-color: #4e4c4d;
|
6 |
+
--primary-color: #159969;
|
7 |
+
--primary-lightgray-color: #f1f1f1;
|
8 |
+
--primary-beige-color: #f7f7f7;
|
9 |
+
--background-color: #ffffff;
|
10 |
+
}
|
test/test.js
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import renderer from "react-test-renderer";
|
3 |
+
import MyComponent from "../components/MyComponent";
|
4 |
+
|
5 |
+
describe("MyComponent", () => {
|
6 |
+
it("renders correctly", () => {
|
7 |
+
const tree = renderer.create(<MyComponent />).toJSON();
|
8 |
+
expect(tree).toMatchSnapshot();
|
9 |
+
});
|
10 |
+
});
|
tsconfig.json
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ES2022",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"strict": true,
|
8 |
+
"forceConsistentCasingInFileNames": true,
|
9 |
+
"noEmit": true,
|
10 |
+
"esModuleInterop": true,
|
11 |
+
"module": "esnext",
|
12 |
+
"moduleResolution": "node",
|
13 |
+
"resolveJsonModule": true,
|
14 |
+
"isolatedModules": true,
|
15 |
+
"jsx": "preserve",
|
16 |
+
"incremental": true,
|
17 |
+
"plugins": [
|
18 |
+
{
|
19 |
+
"name": "next"
|
20 |
+
}
|
21 |
+
],
|
22 |
+
"paths": {
|
23 |
+
"@/*": ["./src/*"]
|
24 |
+
}
|
25 |
+
},
|
26 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
27 |
+
"exclude": ["node_modules"]
|
28 |
+
}
|