first comit

This commit is contained in:
Johan
2025-12-17 09:49:48 +01:00
commit 69f2bea0e7
23 changed files with 10487 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/
# System files
.DS_Store
Thumbs.db

149
README.md Normal file
View File

@@ -0,0 +1,149 @@
# Démonstration 1 : Todo List (Hello, Signals!) - Gestion d'une Todo-List locale
Bienvenue dans cette première démonstration pratique d'Angular !
L'objectif est de construire une application de type "Todo List" simple et entièrement fonctionnelle.
Toutes les données et la logique seront contenues dans un seul composant,
ce qui nous permettra de nous concentrer sur les concepts fondamentaux d'Angular moderne.
---
### Objectifs Pédagogiques
À la fin de cette démonstration, vous saurez :
* **Créer un état réactif** avec `signal()` pour stocker les données de votre application.
* **Dériver un état** à partir d'un autre avec `computed()` pour des calculs automatiques.
* **Gérer les entrées utilisateur** de manière robuste avec `FormControl`.
* **Mettre à jour l'état** de manière immuable en utilisant la méthode `.update()` des signaux.
* **Afficher dynamiquement des données** dans un template en utilisant la nouvelle syntaxe `@if` et `@for`.
* **Lier des événements** du template (comme un clic) à des méthodes dans votre composant.
Voici un aperçu de la circulation des données que vous allez construire :
```mermaid
graph TD
subgraph "État (Signals)"
A["todos = signal<Todo[]>"]
B[remainingTodos = computed]
end
subgraph "Formulaire"
C[newTodoTitle = FormControl]
end
subgraph "Actions (Méthodes)"
E[addTodo]
F[removeTodo]
G[toggleTodo]
end
subgraph "Vue (Template HTML)"
D[Affichage & Événements]
end
%% Dépendances de données
A -- "dérive de" --> B
A -- "lu par" --> D
B -- "lu par" --> D
C -- "lu par" --> D
%% Flux d'actions
D -- "clic / keydown" --> E
D -- "clic" --> F
D -- "clic" --> G
D -- "saisie" --> C
E -- "appelle todos.update()" --> A
F -- "appelle todos.update()" --> A
G -- "appelle todos.update()" --> A
E -- "appelle .reset()" --> C
```
-----
### Installation
1. Ouvrir le projet sous WebStorm.
2. Ouvrir un terminal dans WebStorm.
3. Lancer la commande `npm install` pour installer les dépendances.
4. Lancer la commande `ng serve` pour démarrer le serveur de développement.
5. Ouvrir le navigateur à l'adresse `http://localhost:4200/`.
---
### Instructions
Le projet est volontairement déjà configuré.
Votre mission est de compléter le code dans les fichiers `todo-list.component.ts` et `todo-list.component.html` en suivant les indications `// TODO`.
Vous travaillerez sur deux fichiers qui interagissent comme suit :
```mermaid
graph LR
subgraph "Logique (Étape 1)"
TS[todo-list.component.ts]
end
subgraph "Vue (Étape 2)"
HTML[todo-list.component.html]
end
TS -- "expose les données (signals, formControl)" --> HTML
HTML -- "déclenche les méthodes (click), (keydown.enter)" --> TS
```
#### Étape 1 : la logique du composant (`todo-list.component.ts`)
Ouvrez le fichier `app/features/todo/todo-list/todo-list.component.ts`.
Nous allons d'abord définir l'état et les actions de notre composant.
1. **`// TODO 1.1` : Créer le signal d'état principal.**
* Déclarez une propriété `readonly todos` et initialisez-la avec `signal<Todo[]>([...])`.
* Utilisez le tableau de tâches fourni dans les commentaires pour avoir des données de départ.
2. **`// TODO 1.2` : Créer un signal dérivé (computed).**
* Déclarez une propriété `readonly remainingTodos` et utilisez la fonction `computed()` pour calculer automatiquement le nombre de tâches non complétées à partir du signal `todos`.
3. **`// TODO 1.3` : Créer le contrôle de formulaire.**
* Déclarez une propriété `readonly newTodoTitle` et initialisez-la avec un `new FormControl()`.
* Assurez-vous qu'il ne peut pas être nul (`nonNullable: true`) et ajoutez des validateurs pour qu'il soit requis (`Validators.required`) et ait une longueur minimale (`Validators.minLength(3)`).
4. **`// TODO 1.4` : Implémenter la méthode `addTodo()`.**
* À l'intérieur de la méthode, vérifiez d'abord si `newTodoTitle` est valide.
* Créez un nouvel objet `Todo`.
* Utilisez `this.todos.update(...)` pour ajouter la nouvelle tâche à la liste existante.
* Réinitialisez le champ de saisie avec `.reset()`.
5. **`// TODO 1.5` : Implémenter la méthode `removeTodo()`.**
* Utilisez `this.todos.update(...)` et la méthode `.filter()` des tableaux pour retourner une nouvelle liste sans la tâche à supprimer.
6. **`// TODO 1.6` : Implémenter la méthode `toggleTodo()`.**
* Utilisez `this.todos.update(...)` et la méthode `.map()` des tableaux pour créer une nouvelle liste où l'état `completed` de la tâche ciblée est inversé.
#### Étape 2 : l'affichage et les interactions (`todo-list.component.html`)
Ouvrez le fichier `app/features/todo/todo-list/todo-list.component.html`.
Maintenant que la logique est prête, connectons-la à notre vue.
1. **`// TODO 2.1` : Connecter le champ de saisie.**
* Liez l'input au `FormControl` que vous avez créé en utilisant la liaison de propriété `[formControl]`.
* Ajoutez un événement `(keydown.enter)` pour appeler la méthode `addTodo()` lorsque l'utilisateur appuie sur "Entrée".
2. **`// TODO 2.2` : Connecter le bouton "Ajouter".**
* Liez l'événement `(click)` du bouton à la méthode `addTodo()`.
* Désactivez le bouton si le formulaire est invalide en utilisant `[disabled]`.
3. **`// TODO 2.3` : Afficher la liste des tâches.**
* Utilisez un bloc `@if` pour vérifier si la liste `todos()` contient des éléments.
* À l'intérieur, utilisez une boucle `@for` pour itérer sur `todos()`. N'oubliez pas le `track todo.id` pour les performances !
* Affichez le titre de la tâche en utilisant l'interpolation `{{ ... }}`.
* Appliquez la classe `.completed` dynamiquement avec `[class.completed]`.
4. **`// TODO 2.4` : Ajouter les actions sur chaque tâche.**
* Dans la boucle `@for`, liez l'événement `(click)` du `<span>` à la méthode `toggleTodo(todo.id)`.
* Liez l'événement `(click)` du bouton de suppression à la méthode `removeTodo(todo.id)`.
5. **`// TODO 2.5` : Afficher le compteur de tâches restantes.**
* Dans le `<footer>`, affichez la valeur du signal `remainingTodos()` en utilisant l'interpolation. N'oubliez pas les parenthèses `()` pour lire la valeur d'un signal !
Une fois toutes les étapes terminées, votre application devrait être entièrement fonctionnelle !

98
angular.json Normal file
View File

@@ -0,0 +1,98 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"signals": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "signals:build:production"
},
"development": {
"buildTarget": "signals:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
}
}
}
}
}
}

9649
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "signals",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"prettier": {
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "^20.3.6",
"@angular/cli": "^20.3.6",
"@angular/compiler-cli": "^20.3.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.9.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.2"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

24
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './core/layout/header/header.component';
@Component({
selector: 'app-root',
template: `
<app-header />
<main class="container">
<router-outlet></router-outlet>
</main>
`,
styles: `
.container {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterOutlet, HeaderComponent],
})
export class AppComponent {
}

13
src/app/app.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withComponentInputBinding())
]
};

19
src/app/app.routes.ts Normal file
View File

@@ -0,0 +1,19 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path:'todos',
// Lazy loading des routes de la fonctionnalité "todos"
loadChildren: () =>
import('./features/todo/todo.routes').then((m) => m.TODO_ROUTES),
},
{
path:'',
redirectTo:'todos',
pathMatch: 'full',
}
];

View File

@@ -0,0 +1,42 @@
:host {
display: block; // Assure que le header prend toute la largeur
}
.header {
background-color: var(--surface-color);
padding: 0 2rem;
box-shadow: var(--box-shadow);
border-bottom: 1px solid var(--border-color);
}
.header-nav {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
height: 64px;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
.nav-link {
font-size: 1rem;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
transition: background-color 0.2s ease, color 0.2s ease;
&.active {
background-color: var(--primary-color);
color: var(--text-color-light);
}
&:not(.active):hover {
background-color: var(--background-color);
}
}

View File

@@ -0,0 +1,20 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {RouterLink, RouterLinkActive} from '@angular/router';
@Component({
selector: 'app-header',
template: `
<header class="header">
<nav class="header-nav">
<a class="logo" routerLink="/">ToDo List</a>
<a class="nav-link" routerLink="/todos" routerLinkActive="active">
Liste des tâches
</a>
</nav>
</header>
`,
styleUrls: ['./header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, RouterLinkActive],
})
export class HeaderComponent {}

View File

@@ -0,0 +1,5 @@
export interface Todo {
id: number;
title: string;
completed: boolean;
}

View File

@@ -0,0 +1,29 @@
<div class="todo-container">
<header>
<h1>Ma Todo List</h1>
<div class="todo-input-group">
<input
type="text"
placeholder="Ajouter une nouvelle tâche..."
/>
<button>Ajouter</button>
</div>
</header>
<ul class="todo-list">
<li>
<span class="todo-title">
Tâche d'exemple
</span>
<button class="btn-remove">X</button>
</li>
</ul>
<footer>
<span>X tâche(s) restante(s)</span>
</footer>
<p class="empty-state">Bravo, aucune tâche pour le moment !</p>
</div>

View File

@@ -0,0 +1,118 @@
.todo-container {
max-width: 600px;
margin: 2rem auto;
background: var(--surface-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 2rem;
h1 {
text-align: center;
color: var(--primary-color);
margin-bottom: 1.5rem;
}
}
.todo-input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
input[type='text'] {
flex-grow: 1;
padding: 0.75rem;
font-size: 1rem;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
transition: border-color 0.2s, box-shadow 0.2s;
&:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(103, 58, 183, 0.2);
}
}
button {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
color: var(--text-color-light);
background-color: var(--primary-color);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
transition: background-color 0.2s;
&:hover:not(:disabled) {
background-color: var(--primary-color-dark);
}
&:disabled {
background-color: #ccc;
cursor: not-allowed;
}
}
}
.todo-list {
list-style: none;
padding: 0;
margin: 0;
}
li {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s;
&:last-child {
border-bottom: none;
}
&.completed .todo-title {
text-decoration: line-through;
color: #999;
}
}
.todo-title {
flex-grow: 1;
cursor: pointer;
}
.btn-remove {
background-color: transparent;
border: none;
color: var(--error-color);
font-weight: bold;
font-size: 1.2rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 50%;
line-height: 1;
width: 30px;
height: 30px;
transition: background-color 0.2s, color 0.2s;
&:hover {
background-color: rgba(244, 67, 54, 0.1);
}
}
footer {
margin-top: 1.5rem;
text-align: center;
color: #777;
font-size: 0.9rem;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #888;
font-style: italic;
}

View File

@@ -0,0 +1,67 @@
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { Todo } from '../../../core/models/todo.model';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule],
})
export class TodoListComponent {
// --- ÉTAT (State) ---
// TODO 1.1: Créer un signal pour stocker la liste des tâches.
// Utilisez `signal<Todo[]>()` et initialisez-le avec le tableau ci-dessous.
/*
[
{ id: 1, title: 'Apprendre les bases d\'Angular', completed: true },
{ id: 2, title: 'Comprendre les signals', completed: false },
{ id: 3, title: 'Créer un premier composant', completed: false },
]
*/
readonly todos = signal<Todo[]>([]); // <- REMPLACER [] par le tableau ci-dessus
// TODO 1.2: Créer un signal "computed" pour calculer le nombre de tâches restantes.
// Utilisez `computed(() => ...)` et filtrez le signal `todos` pour ne compter
// que les tâches où `completed` est `false`.
readonly remainingTodos = computed(() => 0); // <- REMPLACER 0 par le calcul
// TODO 1.3: Créer un FormControl pour le champ de saisie d'une nouvelle tâche.
// Utilisez `new FormControl()` avec une chaîne vide comme valeur initiale.
// Dans les options, ajoutez `nonNullable: true` et des validateurs :
// `Validators.required` et `Validators.minLength(3)`.
readonly newTodoTitle = new FormControl(''); // <- AJOUTER les options
// --- ACTIONS ---
/** Ajoute une nouvelle tâche à la liste. */
addTodo(): void {
// TODO 1.4: Implémenter la logique d'ajout.
// 1. Vérifiez si `this.newTodoTitle` est invalide. Si c'est le cas, arrêtez la fonction avec `return;`.
// 2. Créez un objet `newTodo` de type `Todo` avec un `id` unique (Date.now()),
// le titre provenant de `this.newTodoTitle.value`, et `completed: false`.
// 3. Mettez à jour le signal `todos` avec `this.todos.update(currentTodos => [...currentTodos, newTodo])`.
// 4. Réinitialisez le champ de saisie avec `this.newTodoTitle.reset()`.
}
/** Supprime une tâche de la liste. */
removeTodo(id: number): void {
// TODO 1.5: Implémenter la logique de suppression.
// Utilisez `this.todos.update(currentTodos => ...)` et la méthode `.filter()`
// pour retourner un nouveau tableau sans la tâche correspondant à l'`id`.
}
/** Bascule l'état "complété" d'une tâche. */
toggleTodo(id: number): void {
// TODO 1.6: Implémenter la logique de bascule.
// Utilisez `this.todos.update(currentTodos => ...)` et la méthode `.map()`.
// Pour chaque `todo` dans le tableau, si son `id` correspond, retournez un nouvel objet
// avec la propriété `completed` inversée (`!todo.completed`). Sinon, retournez le `todo` original.
}
}

View File

@@ -0,0 +1,11 @@
import { Routes } from '@angular/router';
import {TodoListComponent} from './todo-list/todo-list.component';
export const TODO_ROUTES: Routes = [
{
path: '',
component: TodoListComponent,
title: 'Todo List',
},
];

13
src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Signals</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

6
src/main.ts Normal file
View File

@@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

53
src/styles.scss Normal file
View File

@@ -0,0 +1,53 @@
/* --- Variables de couleur et de style --- */
:root {
--primary-color: #673ab7;
--primary-color-dark: #512da8;
--accent-color: #ff4081;
--text-color: #333;
--text-color-light: #f5f5f5;
--background-color: #f0f2f5;
--surface-color: #ffffff;
--border-color: #e0e0e0;
--success-color: #4caf50;
--error-color: #f44336;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* --- Réinitialisation et styles de base --- */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease-in-out;
}
a:hover {
color: var(--primary-color-dark);
}
main.container {
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}

15
tsconfig.app.json Normal file
View File

@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}

34
tsconfig.json Normal file
View File

@@ -0,0 +1,34 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

14
tsconfig.spec.json Normal file
View File

@@ -0,0 +1,14 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.ts"
]
}