{"id":22199825,"url":"https://github.com/betagouv/etabli","last_synced_at":"2025-07-27T02:32:25.164Z","repository":{"id":207003237,"uuid":"718190281","full_name":"betagouv/etabli","owner":"betagouv","description":"Allow searching for public french digital initiatives across a directory or by talking to an assistant","archived":false,"fork":false,"pushed_at":"2024-09-18T09:11:55.000Z","size":6555,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-04T06:03:11.588Z","etag":null,"topics":["ai","directory","france","french","government","gpt","initiative","llm","local-authority","product","project","service"],"latest_commit_sha":null,"homepage":"https://etabli.incubateur.net","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/betagouv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-13T15:12:44.000Z","updated_at":"2024-09-18T09:11:59.000Z","dependencies_parsed_at":"2023-12-15T14:46:47.363Z","dependency_job_id":"e788df86-9214-4865-9779-3719aa7d3e12","html_url":"https://github.com/betagouv/etabli","commit_stats":null,"previous_names":["betagouv/etabli"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betagouv%2Fetabli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betagouv%2Fetabli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betagouv%2Fetabli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/betagouv%2Fetabli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/betagouv","download_url":"https://codeload.github.com/betagouv/etabli/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227750184,"owners_count":17814129,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","directory","france","french","government","gpt","initiative","llm","local-authority","product","project","service"],"created_at":"2024-12-02T15:18:11.986Z","updated_at":"2024-12-02T15:18:12.602Z","avatar_url":"https://github.com/betagouv.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Établi\n\nA french national platform allowing agents and citizens to search for public digital initiatives across a directory or by talking to an assistant. It improves visibility and so discoverability so people no longer miss what has been done.\n\n- The project is available onto: https://etabli.incubateur.net _(#production)_\n\nAll the states of the application are described visually on a Storybook. It renders fake data but allows you to discover the project without dealing with real flows: https://main--65cba764ca6d6f49d5b40e50.chromatic.com/\n\n## Datasets\n\nWe rely on 3 datasets to compute initiative sheets into Établi:\n\n- [dinum/noms-de-domaine-organismes-secteur-public](https://gitlab.adullact.net/dinum/noms-de-domaine-organismes-secteur-public/) to retrieve public french domains\n- [code.gouv.fr](https://code.gouv.fr/public/) to retrieve public french repositories\n- [captn3m0/stackshare-dataset](https://github.com/captn3m0/stackshare-dataset) based on the [StackShare](https://stackshare.io/) data to build a fixed list of tools to bind initiatives to\n\n_This is important to understand to detect new websites or new repositories they must be listed in the first two. Also, note we have no control over their acceptance process._\n\n## Usage\n\nThis repository uses `make` CLI to ease the commands, have it installed and run:\n\n```\nmake deps\n```\n\nThen you can have a look at the `Makefile` file to see common available commands.\n\nFor example to start developing and interact with the frontend, launch the application and the Storybook with:\n\n```\nmake serve\n```\n\nIf you want to only launch one of the two:\n\n- Application: `npm run dev:app`\n- Storybook: `npm run dev:storybook`\n\n**Note the application needs some tools to run locally:**\n\n- the database\n- [Pandoc](https://pandoc.org/) (to format legal documents, can be installed through a manager like `brew`)\n- [Semgrep](https://semgrep.dev/) (to analyze code files, can be installed through the Python package manager `pip` by running `pip install -r src/semgrep/requirements.txt`)\n- [Bibliothecary](https://github.com/librariesio/bibliothecary) (to analyze code dependencies, can be installed through the Ruby package manager `bundle` by running `bundle --gemfile src/bibliothecary/Gemfile`)\n\nNote for long-running tools the easiest way to use them is to use `docker-compose`. In another terminal just set up all tools:\n\n```\ndocker-compose up\n```\n\nAnd when calling it a day you can stop those containers with:\n\n```\ndocker-compose down\n```\n\nNow the database schema and client need to be initialized:\n`npm run db:migration:deploy \u0026\u0026 npm run db:schema:compile`\n\nNote since the application has a logic of jobs to be run regularly to fetch, analyze, and compute data. Those are not available in the UI, but you can trigger them through our custom CLI by running:\n`npm run cli`\n\nThat's it! But still, we advise you to read the documentation below to better understand how the project is structured.\n\n## Technical setup\n\n### GitHub\n\n#### Apps \u0026 Actions\n\n- [CodeCov](https://github.com/marketplace/codecov): code coverage reports _(we have CodeQL in addition in the CI/CD... pick just one in the future)_\n- [Lighthouse](https://github.com/apps/lighthouse-ci): accessibility reports _(we also have an accessibility plugin for Storybook, but this one only helps while developping)_\n\n#### Environments\n\nYou must configure 1 environment in the CI/CD settings:\n\n- `prod` (to restrict to `main` branch only)\n\n#### Secrets\n\nThe following ones must be repository secrets (not environment ones):\n\n- `CHROMATIC_PROJECT_TOKEN`: [SECRET]\n- `CLEVER_APP_ID_PRODUCTION`: [GENERATED] _(format `app_{uuid}`, can be retrieved into the Clever Cloud interface)\\_\n- `CLEVER_TOKEN`: [GENERATED] _(can be retrieved from `clever login`, but be warned it gives wide access)_\n- `CLEVER_SECRET`: [GENERATED] _(can be retrieved from `clever login`, but be warned it gives wide access)_\n- `LHCI_GITHUB_APP_TOKEN`: [SECRET]\n- `SENTRY_URL`: [SECRET] _(format `https://xxx.yyy.zzz/`)_\n- `SENTRY_AUTH_TOKEN`: [SECRET]\n- `SENTRY_ORG`: [SECRET]\n- `SENTRY_PROJECT`: [SECRET]\n\n**IMPORTANT: When building Next.js in a standalone mode the frontend `NEXT_PUBLIC_*` environement variables are hardcoded. It means you have to set all of them here too. This is a workaround but we have no simple other choice as of today. For more information have a look at the build step comment into `.github/workflows/ci.yml`.**\n\n#### Default branch\n\nThe default branch is `main`.\n\n#### Branch protection rules\n\n1.  Pattern: `main`\n    Checked:\n\n    - Require status checks to pass before merging\n    - Do not allow bypassing the above settings\n    - Allow force pushes (+ \"Specify who can force push\" and leave for administrators) _(we allow this as the product is still experimental without a development environment)_\n\n### Sentry\n\n#### Helpful to monitor\n\nWe use Sentry to monitor errors that happen on frontend and backend. It has been added everywhere it was needed to catch unexpected errors no matter the tool used (Next.js, `pg-boss`...).\n\nTo keep safe sensitive information we prevent sensitive elements from being recorded by `rrweb` (useful to replay the session up to the issue).\n\n_Note: `BusinessError` errors are not tracked because they are intented to describe a possible error in the user flow (validation error, entity deleted by someone and no longer existing...)_\n\nSince Sentry gets only error details from the built application, we provide source maps during the CI/CD so debugging is easier by seeing original source code. For this we set `SENTRY_RELEASE_UPLOAD = true` into the `ci.yaml` file.\n\n#### Upload sourcemaps\n\nTo upload sourcemaps to Sentry you need a specific \"auth token\", it must have these scopes:\n\n- `project:releases`\n- `org:read`\n\nYou can create this token at https://${SENTRY_URL}/settings/account/api/auth-tokens/ ;)\n\n#### Silent odd errors\n\nSince the application watches all kinds of errors, it happens we collect some that are not reproductible and without any impact for the user experience.\n\nTo avoid being notified of those we chose to silent them from the Sentry interface. Just go to your Sentry project interface, then `Processing \u003e Inbound Filters \u003e Custom Filters \u003e Error Message` and enter the following silent patterns:\n\n```\n*ResizeObserver loop completed with undelivered notifications*\n```\n\n### Clever Cloud\n\n#### Global\n\nCreate into the Clever Cloud Paris region both:\n\n1. Docker application named `etabli` (at least a plan of 2 Go of memory)\n\n- Enable into settings:\n  - `Zero downtime deployment`\n  - `Sticky sessions`` (will help with assistant session history since not in database)\n  - `Enable dedicated build instance` with `XL` plan (otherwise the build is so slow even if only getting from another Docker image)\n  - `Cancel ongoing deployment on new push`\n\n2. PostgresSQL `v15` named `etabli` (at least a plan with 3 Go of storage)\n\n- Enable the disk encryption at creation\n\n#### Domains\n\nYou need to configure the domain DNS records to target the Clever Cloud instance when accessing the domain.\n\nIf a subdomain:\n\n- Type: `CNAME`\n- Hostname: `etabli`\n- Value: `domain.par.clever-cloud.com.`\n\nBut if a root domain you have to use fixed IPs (reapeat this record X times if X IPs):\n\n- Type: `A`\n- Hostname: ``\n- Value: `xxx.xxx.xxx.xxx` _(those can be found into the application domain names configuration)_\n\nOnce done, go to your Clever Cloud domains settings and add your domain.\n\n#### Postgres\n\n##### Tooling\n\nWe advise you to use [DBeaver](https://dbeaver.io/) (RDBMS-agnostic) to deal with your database.\n\n#### Environment variables\n\nClever Cloud is used as a PaaS to host our builds.\n\nFor each build and runtime (since they are shared), you should have set some environment variables.\n\n- `CC_DOCKERFILE`: [TO_DEFINE] _(should be `Dockerfile.prod.clevercloud` for the production environment and `Dockerfile.dev.clevercloud` for the development one. It's used during the build stage on the Clever Cloud side)_\n- `CC_DOCKER_EXPOSED_HTTP_PORT`: `3000` _(it tells to Clever Cloud the port we are listening on)_\n- `PORT`: `3000` _(since Clever Cloud has a default value for this variable we have to override it)_\n- `NODE_OPTIONS`: `--max-old-space-size=1536` _(since we do some exports that fetch the whole database with post-processing, we expliticly tells Node.js our memory capacity (in this case we have 2 GB, but minus a few for Docker memory usage and minus the free memory to allocate))_\n- `APP_MODE`: `prod` _(can be `dev` in case you would like to deploy a development environment)_\n- `DATABASE_URL`: `$POSTGRESQL_ADDON_URI` _(you must copy/paste the value provided by Clever Cloud into `$POSTGRESQL_ADDON_URI`, and note you must add as query parameter `sslmode=prefer`)_\n- `MAINTENANCE_API_KEY`: [SECRET] _(random string that can be generated with `openssl rand -base64 32`. Note this is needed to perform maintenance through dedicated API endpoints)_\n- `MISTRAL_API_KEY`: [SECRET] _(you can create an API key from your MistralAI \"La plateforme\" account)_\n- `LLM_MANAGER_MAXIMUM_API_REQUESTS_PER_SECOND`: [TO_DEFINE] _(by default the MistralAI platform has a limit of `5` request per second, but they may increase this limit on-demand. If so, you can increase the rate limit here to parallelize underlying requests)_\n- `CHROMIUM_MAXIMUM_CONCURRENCY`: [TO_DEFINE] _(by default it will be `1` but it takes a long time when analyzing thousands of websites through the headless Chromium. After some testing we think on Clever Cloud having `4` is fine for the `S` plan (and `8` for `XL` plan for a quick test to speed things up), and locally it will depend on your hardware. Consider to lower the value when having more than 10% of analyses timed out)_\n- `SEMGREP_PER_FILE_MEMORY_LIMIT_IN_MB`: [TO_DEFINE] _(semgrep will skip analyzing a specific file if it reaches this limit (and pass to the next one), we advise you to set it to 50% of the memory capacity of the server)_\n- `NEXT_PUBLIC_APP_BASE_URL`: [TO_DEFINE] _(must be the root URL to access the application, format `https://xxx.yyy.zzz`)_\n- `NEXT_PUBLIC_CRISP_WEBSITE_ID`: [TO_DEFINE] _(this ID is defined in your Crisp account and depends on the development or production environment)_\n- `NEXT_PUBLIC_SENTRY_DSN`: [SECRET] _(format `https://xxx.yyy.zzz/nn`)_\n- `NEXT_PUBLIC_MATOMO_URL`: [PROVIDED] _(format `https://xxx.yyy.zzz/`)_\n- `NEXT_PUBLIC_MATOMO_SITE_ID`: [GENERATED] _(number format)_\n\n**IMPORTANT: When building Next.js in a standalone mode the frontend `NEXT_PUBLIC_*` environement variables are hardcoded. It means you have to set them into the build environment too. For more information have a look at the build step comment into `.github/workflows/ci.yml`.**\n\n_Note: `OPENAI_API_KEY` variable can be found in the code even if not used in production. It remains for comparing purposes, but also as a legacy since the proof of concept of Établi was based on GPT models._\n\n#### Monitoring\n\nIt's important to be aware of some events, for this we decided to monitor:\n\n- Clever Cloud (responsible for the application and database) seems to only alert by email\n- Sentry (responsible for gathering runtime issues) can alert by email, webhooks (Slack format by default, but adapters can be used for Mattermost)\n\n#### Debug the runtime\n\nTo debug Clever Cloud apps you can use their CLI to connect to instances:\n\n```shell\nclever ssh\n```\n\nNote that Clever Cloud databases are reachable by the public network directly... So no bridge to connect to it, but keep that in mind since it's more sensible than with some other providers.\n\nTo debug a remote database we advise creating a specific user (because you are not suppose to store credentials of production users). Make sure the user created has been granted needed roles on business tables, something like `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA $SCHEMA TO $USERNAME;` that you can customize as needed ;) . _(If you still see no table that's probably because you logged into the wrong database)_\n\n#### Debug the CI/CD pipeline\n\nThis is not a silver bullet but for testing we use [act](https://github.com/nektos/act) locally to mimic what GitHub Actions would do. Install it and run:\n`make simulate-cicd-with-push`\n\n### Matomo\n\nWe use the minimal tracking configuration allowing the frontend to not request the user consent. Our goal is just to track pageviews, or usage of specific features of the application.\n\nOn the Matomo instance you have access to, just create a new website to get a new site ID.\n\n### Crisp\n\nCrisp is used as a livechat for user support.\n\nFrom their interface we create a website with the production domain name while setting the name to `Établi`.\n\nThen upload as the icon the one used for the website (usually `apple-touch-icon.png` has enough quality).\n\nAdd to the team the people you need (without giving too many rights depending on their role).\n\nInto the `Chatbox \u0026 Email settings` section go to `Chat Appearance` and set:\n\n- Color theme (chatbot color): `Red`\n- Chatbox background (message texture): `Default (No background)`\n\nThen go to `Chatbox Security` and enable `Lock the chatbox to website domain (and subdomains)` (no need to enable it inside the development environment).\n\nNote the public \"website ID\" will be used as `NEXT_PUBLIC_CRISP_WEBSITE_ID`.\n\n### Maintenance\n\nSince everything stateful is inside the PostgreSQL you should be able to do most of the maintenance from `DBeaver`.\n\n#### Retrieve old data from backups\n\nJust download the database backup from the Clever Cloud interface and run it locally with Docker with no volume (to be sure not keeping sensitive data locally):\n\n- Terminal 1:\n\n```sh\ndocker run -it --rm -p 15432:5432 --name tmp_postgres -e POSTGRES_PASSWORD=postgres -v $(pwd)/${BACKUP_FILENAME}.pgsql:/backup.pgsql postgres\n```\n\n- Terminal 2:\n\n```sh\ndocker exec -it tmp_postgres bash\npg_restore -U postgres -d postgres --no-owner -v /backup.pgsql\npsql -U postgres -d postgres\n```\n\n_Note: sometimes `pg_restore` will fail creating the `public` schema despite having the command to. Create it manually through DBeaver within the database and rerun the `pg_restore` command to make it successful._\n\nThen debug from SQL inline or use DBeaver to connect to `localhost:15432` with above credentials.\n\nOnce done, stop the container and remove the downloaded `.tar.gz / .psql` files.\n\n#### Replay jobs\n\nExcept the case of replaying queueing jobs once they fail because they may be archived already. And since it's a bit tricky to move a job directly from SQL while cleaning its right properties, we decided to make an endpoint for this as an helper:\n\n- [POST] `/api/maintenance/jobs/replay` _(it expects as body `{ jobId: \"...\" }`)_\n\n**Note that to reach maintenance endpoints you have to pass a header `X-API-KEY` that must match the server environment variable `MAINTENANCE_API_KEY`.**\n\n### IDE\n\nSince the most used IDE as of today is Visual Studio Code we decided to go we it. Using it as well will make you benefit from all the settings we set for this project.\n\n#### Manual steps\n\nEvery settings should work directly when opening the project with `vscode`, except for TypeScript.\n\nEven if your project uses a TypeScript program located inside your `node_modules`, the IDE generally uses its own. Which may imply differences since it's not the same version. We do recommend using the exact same, for this it's simple:\n\n1. Open a project TypeScript file\n2. Open the IDE command input\n3. Type `TypeScript` and click on the item `TypeScript: Select TypeScript Version...`\n4. Then select `Use Workspace Version`\n\nIn addition, using the workspace TypeScript will load `compilerOptions.plugins` specified in your `tsconfig.json` files, which is not the case otherwise. Those plugins will bring more confort while developing!\n\n### Tips\n\n#### Cron tasks\n\nDoing tasks on a regular basis is a real subject, ask yourself:\n\n- Is it critical if a task schedule is missed? (ideally it could be trigger manually if the support team notices that, so keep track of it)\n- Is it critical if multiple app instances run the same task concurrently?\n- Does the job needs to be restarted if it fails?\n\n... doing only in-app scheduling would break the persistence and concurrency challenges. On the other side, using a third-party to trigger our tasks is risky too since you rely on it and on the network. Even in the last case you should use a central locker to be sure you don't run 2 times the job in case of a close network retry.\n\nThe conclusion, in all cases we need something out of the app and that can manage atomicity for concurrency. So we chose to adopt `pg-boss` that allows us to use our own PostgreSQL like a basic tasks queue, it brings persistence, locking, and task monitoring with states (scheduled, canceled, failed, archived)... this is great because in 1 place we now finally have all things to debug, same in case of backups we do have the \"task log\".\n\n#### Frontend development\n\n##### i18n\n\nCurrently we only use i18n to help displaying ENUM values. We use the `i18n Ally` VSCode extension to improve a bit the usage but everything can be written manually in `.json` files :)\n\n##### Storybook\n\n###### Use it first\n\nDeveloping a UI component by launching the whole application is annoying because you may have to do specific interactions to end viewing the right state of the component you are building.\n\nWe advise when doing some UI to only run Storybook locally, so you do not worry about other stuff. You can also mock your API calls! At the end splitting your component into different stories makes easy for non-technical people of the team to view deepest details. They will be able to review the visual changes with Chromatic, and also from the Storybook they will save too their time avoiding complex interactions to see specific state of the application.\n\nIt's not magical, it does not replace unit and end-to-end testing, but it helps :)\n\n###### Advantages\n\n- Accessibility reports\n- See almost all tests of your application\n- Helps architecturing your components split\n- Their rendering is tested in the CI/CD, so it's very likely your components are valid at runtime\n\n###### How we test stories\n\nYou can do UI testing scoped to components (I mean, not full end-to-end), and if so, it's recommended to reuse the stories to benefit from their mocking set up.\n\nA decision we took to keep in mind: it's common to create a `.test.js` file to test that a component renders correctly (thanks to Jest, Playwright or Cypress), but since we have \"Storybook test runners\" already in place that pop each component, we feel it's better to use the story `play` function concept (also used by the interaction addon) to make sure they are rendering correctly.\n\n- It avoids rendering each component one more time\n- It saves 1 file and it's clearer since from Storybook you can see in the \"Interactions\" tab if the expected behavior is matched\n\nThose kind of tests may be useful to:\n\n- Make sure for an atomic component the data is displayed correctly (but it's likely the work of your team to review visual changes)\n- Guarantee the sequences when mocking (e.g. first a loader, then the form is displayed), it helps also the \"Storybook test runners\" to wait the right moment to take a screenshot/snapshot of the final state (through Chromatic in this case), not intermediate ones since the runner doesn't know when the component is fully loaded otherwise\n\n_(in case you have specific needs of testing that should not pollute the Storybook stories, go with a `.test.js` file, see examples [here](https://storybook.js.org/docs/react/writing-tests/importing-stories-in-tests))_\n\n_Tip: during the testing you could `findByText` but it's recommended to `findByRole`. It helps thinking and building for accessibility from scratch (because no, accessibility is not done/advised by an accessibility automated check unfortunately)._\n\n###### Testing performance\n\nDuring the test we render the story, we test the light theme accessibility and then we switch to the dark theme to test it too. The re-rendering for color change over the entire DOM is unpredictable, it depends on the CPU saturation. We had some leakage of previous theme rendered over the new one.\n\nWe evaluated 2 solutions:\n\n- limit the number of Jest workers with `--maxWorkers`\n- increase the amount of time we wait after triggering a theme change\n\nAfter dozens of tests it appears the most reliable and the fastest is by keeping parallelism (no worker limitation), but increase the amount of time. But this latter depends on the environment:\n\n- when testing headless: there is less work done, the delay is shorter\n- when testing with a browser rendering: it is in higher demand, to avoid color style leakage we need to increase the delay (tests duration is +50%, which is fine)\n\n##### Hydratation issue\n\nWhen developing a frontend it's likely you will have client hydratation according to the server content. It will fail if some browser extensions are enabled and modify the DOM. You need to identify the source of the issue and then, either disable the extension, or request it to not modify the DOM when developing on `http://localhost:xxxx/`.\n\nFrom our experience, this can be caused by:\n\n- Password managers _(make sure to have no credentials that match your development URL)_\n- Cookie banner automatic rejection _(in their settings you're likely to be able to exclude your development URL from being analyzed)_\n\n_(in React the error was `Extra attributes from the server: xxxxx`)_\n\n##### Cannot fetch specific files\n\nAs for any `hydratation issue` it worths taking a look at your browser extensions, some may block outgoing requests.\n\nFor example:\n\n- Ad blockers _(whitelist the blocked URL in your extension)_\n\n#### Jest not working in VSCode\n\nSometimes it appears Jest VSCode extension will be stuck and will keep throwing:\n\n```\nenv: node: No such file or directory\n```\n\nWe found no fix. The only solution is to relaunch VSCode, and if it still doesn't work, try to close entirely VSCode and open it through your terminal with a something like:\n\n```sh\ncode /Users/xxxxx/yyyyy/etabli\n```\n\n#### Formatting documents for compliance\n\nLegal documents are mainly written out of technical scope in a basic text editor, and they may be updated quite often. Either you host them on a Markdown website or you embed them as HTML in your website. For both you have to maintain some transformations and you probably don't want to scan in detail for each modification, ideally you just want to redo all at once to be sure there will be no missing patch.\n\nIn this repository you can use `./format-legal-documents.sh` to transform the initial `.docx` files into `.html` files:\n\n- No matter the name of the file it will convert it (though 1 per folder)\n- It allows to collaborate on Word-like software (mainly used by legal people)\n\n## Technical architecture document\n\nIt has been written into [TECHNICAL_ARCHITECTURE_DOCUMENT.md](./TECHNICAL_ARCHITECTURE_DOCUMENT.md).\n\n## Reporting security issue\n\nIf you identify a security issue or have any security concerns, please inform us immediately by opening an [issue](https://github.com/betagouv/etabli/issues) as specified into [our security recommandations](https://www.mediateur-public.fr/.well-known/security.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbetagouv%2Fetabli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbetagouv%2Fetabli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbetagouv%2Fetabli/lists"}