Compare commits
459 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89ad91a055 | |||
| d810047ddd | |||
| 19c73f7747 | |||
| 2f2d772882 | |||
| 32e300f730 | |||
| 2f31d14194 | |||
| 980aa0d07f | |||
| 07351587f3 | |||
| 3027b828a8 | |||
| eef2cd26e0 | |||
| 8190915e3a | |||
| dd157d6ef9 | |||
| 58f4ecb260 | |||
| ab441d1fa7 | |||
| 7538031261 | |||
| 18f66ff79b | |||
| b48f7244b7 | |||
| 22844a37fa | |||
| bc444929ed | |||
| 0813c14e15 | |||
| 5331cc0c8c | |||
| ba89a7e388 | |||
| 90a4d20436 | |||
| d938e82998 | |||
| fc15c01c90 | |||
| ffdf0f91ac | |||
| fa6b2f4c89 | |||
| 245b5c535b | |||
| 2bb0bdb2b0 | |||
| e368cc5e23 | |||
| eb4966e88e | |||
| 64c4c1174c | |||
| 315225184d | |||
| 39fcc33600 | |||
| 1adf380798 | |||
| ac20a83e7e | |||
| b17521063a | |||
| f449c4537e | |||
| 3c413c38bd | |||
| 750e4b6f22 | |||
| e6e455e966 | |||
| 54de22d8c4 | |||
| 40005c4e02 | |||
| 32a76a2560 | |||
| f3cf9b05ae | |||
| c946c43462 | |||
| 2ecfb0d5c0 | |||
| 6b02c40ad4 | |||
| 3334bddeb2 | |||
| 0335c8c1ad | |||
| 0fa83fb1a2 | |||
| b24466fb11 | |||
| c0b4fa8717 | |||
| 16b8125206 | |||
| 8ce1292960 | |||
| dbc205a09c | |||
| 0e5fcd4b57 | |||
| 42b7a22a29 | |||
| 13983a5362 | |||
| 557f412e27 | |||
| f8f21b69b6 | |||
| 7b656ee961 | |||
| c044ab45a7 | |||
| eda328f861 | |||
| f75fce87cf | |||
| 7c0999a5fe | |||
| a7b5e92c46 | |||
| 4d91bc0a72 | |||
| 3b3f601ff1 | |||
| abb12e7b3d | |||
| 66c3d91dfa | |||
| 53ec3ae280 | |||
| f293a1dee8 | |||
| a786f11d28 | |||
| 604581c64a | |||
| ae6ece7415 | |||
| b0a7557d83 | |||
| fde01421dc | |||
| 497532e6fe | |||
| 724ac83429 | |||
| c7ca9ca8b4 | |||
| 7934a7cbab | |||
| 35b1de5d22 | |||
| 2cfad3de12 | |||
| 78a742c4c8 | |||
| 57f0c9fa20 | |||
| 8dde08892c | |||
| da5fac0af2 | |||
| 12aa12c94d | |||
| 6dbdafa888 | |||
| 97082840f8 | |||
| d562aa87a3 | |||
| 2301083edc | |||
| 1c45b29ed1 | |||
| bfd0041c6a | |||
| 9f14689ca0 | |||
| b32b192f70 | |||
| 486a254a1e | |||
| 67de777fd1 | |||
| 8fcebae4a9 | |||
| 77c902a7a5 | |||
| da194524ee | |||
| 81e7af6f5e | |||
| fd2357cedc | |||
| 9db6f02701 | |||
| 9b6f8098b6 | |||
| a322ce2fad | |||
| 73b7a8f056 | |||
| 0e6546d4d0 | |||
| bf77ed1794 | |||
| e34c10bc34 | |||
| 310579cf76 | |||
| 1ba94238ed | |||
| 5f6bad637c | |||
| 3063763667 | |||
| caf1a58a0b | |||
| 059f3d6476 | |||
| 303a177344 | |||
| d26918fc2f | |||
| 31f0a167e9 | |||
| d71185c9cb | |||
| 798f8f1bf8 | |||
| 06612f07b0 | |||
| 067535c2f6 | |||
| d679982949 | |||
| de5ac53cc3 | |||
| ca4a7b3466 | |||
| 0a4d1e2995 | |||
| d6fc18e2f6 | |||
| 8e04abbe42 | |||
| 2905cfae3d | |||
| b632cf43ba | |||
| 06bc3700b7 | |||
| d4648afe01 | |||
| 0653349c08 | |||
| 12e0937174 | |||
| d37b655add | |||
| e3b1c0e013 | |||
| be77068c73 | |||
| 907c92bedc | |||
| 6794ab20d3 | |||
| e00dad9200 | |||
| 9aa6aedee0 | |||
| 0114867fe6 | |||
| 4f58d42582 | |||
| 40add60fd8 | |||
| 9a4bad95ed | |||
| 3e28aeea27 | |||
| 58e48147b8 | |||
| 68eda84053 | |||
| 253f35dbbb | |||
| 0f72506f95 | |||
| ebe1abe422 | |||
| f5a7480f69 | |||
| c0139fddc8 | |||
| 9e08a949bb | |||
| d86c97979b | |||
| 4e266522b6 | |||
| a58a1a4da9 | |||
| 22c1dd04c2 | |||
| 74e34dbbf9 | |||
| 4f38de3dea | |||
| 9d97f2d345 | |||
| 938cf3d8c6 | |||
| e1443274e3 | |||
| b9f8c0312f | |||
| 674a817131 | |||
| 2cbe53f7b7 | |||
| f3033ef0d2 | |||
| 0a0fb8c7f8 | |||
| f72a1a9e5c | |||
| fb29a26246 | |||
| 79d4aff758 | |||
| 7ea8414d6c | |||
| 1a3c9a7fbe | |||
| cecf2ba8cc | |||
| eaaf2513d7 | |||
| fa4d435320 | |||
| 2aff72942c | |||
| 11666fc422 | |||
| aef456cbf2 | |||
| 97b9d71caa | |||
| 5fa38b9506 | |||
| 98e50da575 | |||
| c8cf0771d4 | |||
| 1c6e78ad57 | |||
| 2b6f8c16ce | |||
| 2403ef3d64 | |||
| 003c0766cf | |||
| 9d7e59d793 | |||
| 43f03adedf | |||
| 211c6401e9 | |||
| 01b90b327e | |||
| ad0b09c3df | |||
| 362e1be58d | |||
| eb9039daa3 | |||
| ff7433f434 | |||
| a4e207688a | |||
| fdc87ed8f1 | |||
| 0ab9c297a5 | |||
| 04d6fcc1e1 | |||
| bf18ac0ab0 | |||
| bbe9605f6f | |||
| bd34c194cb | |||
| c0aa707315 | |||
| b6fa448202 | |||
| 2e141be0fc | |||
| 93b5e3e68f | |||
| 39dac5065f | |||
| a16a313f71 | |||
| 82560ddc20 | |||
| 83f3cae6f0 | |||
| 99df4a5575 | |||
| d33c211a01 | |||
| 21ee89ada4 | |||
| dcedb1c6c0 | |||
| d5cc65983e | |||
| 3cb150b090 | |||
| 305ae59086 | |||
| 8fe8930def | |||
| 8730bdbfb9 | |||
| 3ee47e9481 | |||
| 4e649acd60 | |||
| 4768d70a8a | |||
| c7bff7ed09 | |||
| 3035d2a645 | |||
| 0750037385 | |||
| 8c4d61ee65 | |||
| 1a3ccb112e | |||
| 118172d27f | |||
| 69f1d85360 | |||
| f0d042c85b | |||
| 550830c232 | |||
| 6be12b4e58 | |||
| 33aa37ed7d | |||
| 43910fd75d | |||
| 5efcc86a63 | |||
| 17b61a5a41 | |||
| 5dae5c31b7 | |||
| 96deea7f31 | |||
| 7632e6a464 | |||
| 23e2a2e3ff | |||
| b19512dffc | |||
| 11c6fc820b | |||
| 1a8231851d | |||
| 1dfb83b1aa | |||
| 93cb1ae8ba | |||
| 631b168e05 | |||
| af8a43c93a | |||
| 875d12f514 | |||
| a9a2ef67ea | |||
| 5caf3ed6e7 | |||
| 580ec704f2 | |||
| a9918ae555 | |||
| 4b3f4bfab8 | |||
| fed3b1581b | |||
| 9b2f0aab32 | |||
| 818a6bd2ff | |||
| 94a43347a1 | |||
| 16d793c588 | |||
| 0322e5bd8d | |||
| cfe76a9ab3 | |||
| ff6014317b | |||
| 64220f2e27 | |||
| e75414abfc | |||
| d6fae42dc3 | |||
| 0db1245a6b | |||
| 91c0a9ec31 | |||
| 7670c37685 | |||
| ae41c5629c | |||
| 3e7fa05fce | |||
| 6833640c47 | |||
| 18541c45de | |||
| 45bb0dd0d2 | |||
| db0aad2c37 | |||
| d55ca12125 | |||
| ad2cada94b | |||
| f9545f513b | |||
| c75d83dc7c | |||
| 91236e5533 | |||
| 1356be248e | |||
| 7e8be92cd5 | |||
| c3fda4a3a5 | |||
| dfc66cfd50 | |||
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 | |||
| 4abc163a59 | |||
| 5bb9cf1e7c | |||
| a0cdf408a1 | |||
| 8467506915 | |||
| 704255774d | |||
| 10bb7b1cca | |||
| 7f82af3d3b | |||
| d7c729c035 | |||
| 51fa904f9b | |||
| 6191e55745 | |||
| c6e93c266a | |||
| bcf7e5b5ab | |||
| 466f122cc4 | |||
| 539484ea25 | |||
| db1cee25bf | |||
| 96502b18d0 | |||
| c491ece8c5 | |||
| c76464bf8a | |||
| 08b75b1a79 | |||
| 90da869277 | |||
| 80c4affaa8 | |||
| aa087193fe | |||
| af7e51f69b | |||
| 1b62d8ddc2 | |||
| 0839579733 | |||
| 7f9bf08f76 | |||
| 79ec1c6922 | |||
| c68246df83 | |||
| abc0ad7120 | |||
| 4356ca1cff | |||
| d6215d009c | |||
| 0b501e95a5 | |||
| f157ff37e0 | |||
| 11d425faa9 | |||
| 5fc68a1926 | |||
| ec4577c187 | |||
| 8052de8ad5 | |||
| 2fda959cd6 | |||
| 05fa8a5ae2 | |||
| a341b41e9a | |||
| a2e0a4d78a | |||
| 4fb1d6077c | |||
| 53ab927544 | |||
| 897cc2daf2 | |||
| 73ff31daf5 | |||
| c387a9e17e | |||
| 1f92931076 | |||
| d3105a1532 | |||
| 7f50f31837 | |||
| 4c3e90ea4b | |||
| 8d80fc4aa9 | |||
| 78a04ffcf1 | |||
| bcb2c91823 | |||
| 9058f209dd | |||
| 1f172d583e | |||
| 439b4757df | |||
| 9f8809bd51 | |||
| 4e326650cc | |||
| e27be27692 | |||
| 88eb2a1897 | |||
| 8c84ef73c8 | |||
| 859e49f16c | |||
| 14ba8b0a23 | |||
| d7c70f75f3 | |||
| 2760e3ea08 | |||
| f2f041fe55 | |||
| 9ec9700ed4 | |||
| c6b047fc02 | |||
| 6727264e39 | |||
| d1a5fe234d | |||
| e84c7cd276 | |||
| 087813fab9 | |||
| 3a4e7398c6 | |||
| 1630fd7e71 | |||
| 4aa99beaff | |||
| eaa0ba28d8 | |||
| 3f3e1120b2 | |||
| 89d7a2c740 | |||
| 56c31f0204 | |||
| 338cfb5be2 | |||
| 43f6fe664b | |||
| 209c9fb7f7 | |||
| a562f67e8d | |||
| ef0fe5a425 | |||
| b50902d23e | |||
| 55490579ec | |||
| 957d08701f | |||
| 61e1aed35d | |||
| 2034f0ef5f | |||
| fcb92c7f0b | |||
| 28ea1ca1df | |||
| ebc1a458a9 | |||
| df92ef5296 | |||
| 253e0a8378 | |||
| a9fbe4eddd | |||
| cfccc638c6 | |||
| 9f1460cc26 | |||
| f0a4541420 | |||
| d0ce482680 | |||
| 40e2734a4e | |||
| 8552796343 | |||
| 6d72ffb876 | |||
| 65faaf0183 | |||
| 7f7c2e8d6f | |||
| 3be491baa7 | |||
| 48b974ae58 | |||
| c394898fcd | |||
| f22249f3e9 | |||
| 120cec52e9 | |||
| 535cc6b3d9 | |||
| 18a060e2bf | |||
| 7b8165cee6 | |||
| 6331eb64e6 | |||
| 3c38b551f0 | |||
| 8e761347f2 | |||
| e2d44e0e6a | |||
| 607da92c71 | |||
| ae0427604e | |||
| 2aa981c581 | |||
| 5d8ec82502 | |||
| fc9bb52c41 | |||
| 3224e4138f | |||
| 7ea28c4fd3 | |||
| acb5567f6a | |||
| cc877c7c11 | |||
| 87bdddf7cf | |||
| d939b64d64 | |||
| 3f64f71947 |
@@ -0,0 +1,10 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"reportUnusedDisableDirectives": true,
|
|
||||||
"ignorePatterns": ["**/build", "**/dist"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint", "import"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/stylistic",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2020": true,
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"tsconfigRootDir": ".",
|
|
||||||
"project": true,
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": true,
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"version": "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/array-type": "error",
|
|
||||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
|
||||||
"@typescript-eslint/no-empty-object-type": "error",
|
|
||||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "error",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"error",
|
|
||||||
{ "prefer": "type-imports" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
|
||||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "typeParameter",
|
|
||||||
"format": ["PascalCase"],
|
|
||||||
"leadingUnderscore": "forbid",
|
|
||||||
"trailingUnderscore": "forbid",
|
|
||||||
"custom": {
|
|
||||||
"regex": "^(T|T[A-Z][A-Za-z]+)$",
|
|
||||||
"match": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-empty-function": "error",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
|
||||||
"@typescript-eslint/no-inferrable-types": [
|
|
||||||
"error",
|
|
||||||
{ "ignoreParameters": true },
|
|
||||||
],
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/namespace": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"import/no-cycle": "error",
|
|
||||||
"import/no-duplicates": "off",
|
|
||||||
"import/no-named-as-default-member": "error",
|
|
||||||
"import/no-unused-modules": "error",
|
|
||||||
"import/order": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"object",
|
|
||||||
"type",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-case-declarations": "error",
|
|
||||||
"no-empty": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-redeclare": "error",
|
|
||||||
"no-shadow": "error",
|
|
||||||
"no-undef": "off",
|
|
||||||
"sort-imports": "off",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
## Description
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
## Expected behaviour
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
## How to reproduce
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
## Tooling and environment
|
|
||||||
- Tuono version [e.g. 0.4.0]
|
|
||||||
- OS: [e.g. MacOS, Windows]
|
|
||||||
- Browser: [e.g. chrome, safari]
|
|
||||||
- Node version: [e.g. 20.0.0]
|
|
||||||
- Rust version: [e.g. 1.79.0]
|
|
||||||
- Crate version: [e.g. 1.78.0]
|
|
||||||
- Node Package Manger version: [e.g. pnpm: 9.5.0]
|
|
||||||
|
|
||||||
## Additional context
|
|
||||||
Add any other context about the problem here.
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: File a bug report
|
||||||
|
title: '[Bug]: '
|
||||||
|
type: Bug
|
||||||
|
labels: ['bug']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behaviour
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: How to reproduce
|
||||||
|
description: Steps to reproduce the behavior (code, example repository)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: system-info
|
||||||
|
attributes:
|
||||||
|
label: System Info
|
||||||
|
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||||
|
render: shell
|
||||||
|
placeholder: System, Binaries, Browsers
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: system-info-rust
|
||||||
|
attributes:
|
||||||
|
label: System info (Rust)
|
||||||
|
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||||
|
render: shell
|
||||||
|
placeholder: 'rustc, cargo and tuono binary version'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the bug here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Ask a question / Get support
|
||||||
|
url: https://discord.com/invite/khQzPa654B
|
||||||
|
about: Ask questions and discuss with other community members
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
title: '[Feature request]: '
|
||||||
|
type: Feature
|
||||||
|
labels: ['enhancement']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Is your feature request related to a problem? Please describe.
|
||||||
|
description: A clear and concise description of what the problem is.
|
||||||
|
placeholder: I'm always frustrated when [...]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Install NodeJS Dependencies
|
||||||
|
description: This is a composite GitHub Action that sets up pnpm, node and installs the project's dependencies.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
node-version:
|
||||||
|
description: 'Explicit node version. Otherwise fallback reading `.nvmrc`. Use in conjunction with matrix'
|
||||||
|
required: false
|
||||||
|
registry-url:
|
||||||
|
description: 'https://github.com/actions/setup-node?tab=readme-ov-file#usage'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js (via input)
|
||||||
|
if: ${{ inputs.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.node-version }}
|
||||||
|
registry-url: ${{ inputs.registry-url }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Setup Node.js (via .nvmrc)
|
||||||
|
if: ${{ !inputs.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
registry-url: ${{ inputs.registry-url }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
shell: bash
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['crates/**', 'Cargo.toml']
|
||||||
|
|
||||||
|
typescript:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'devtools/**',
|
||||||
|
'packages/**',
|
||||||
|
'package.json',
|
||||||
|
'pnpm-*.yaml',
|
||||||
|
'eslint.config.js',
|
||||||
|
'tsconfig.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
'CI/CD':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['.github/**']
|
||||||
|
|
||||||
|
'repo maintenance':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'devtools/**',
|
||||||
|
'.npmrc',
|
||||||
|
'.nvmrc',
|
||||||
|
'.prettierrc',
|
||||||
|
'.prettierignore',
|
||||||
|
'renovate.json',
|
||||||
|
'turbo.json',
|
||||||
|
]
|
||||||
@@ -1,13 +1,24 @@
|
|||||||
## Context & Description
|
<!--
|
||||||
|
👋 Thank you for your Pull Request 🙏
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] I have read [Contributing > Pull requests](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
|
|
||||||
|
### Related issue
|
||||||
|
|
||||||
|
Fixes #0
|
||||||
|
|
||||||
|
<!-- Replace the content with "None" if you haven't an issue to link -->
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Thank you for your Pull Request.
|
|
||||||
|
|
||||||
Explain the context and why you're making that change. What is the problem
|
Explain the context and why you're making that change.
|
||||||
you're trying to solve? If a new feature is being added, describe the intended
|
What is the problem you're trying to solve?
|
||||||
use case that feature fulfills.
|
If a new feature is being added,
|
||||||
|
describe the intended use case that feature fulfills.
|
||||||
|
|
||||||
Bug fixes and new features should include tests.
|
|
||||||
|
|
||||||
Contributors guide: https://github.com/Valerioageno/tuono/blob/main/CONTRIBUTING.md
|
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
name: Documentation Website CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'apps/documentation/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'apps/documentation/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
timeout-minutes: 15
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./apps/documentation
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Setup rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
|
|
||||||
- name: Install tuono
|
|
||||||
run: cargo install tuono@0.10.4
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm i -g pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --ignore-workspace
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: tuono build --static
|
|
||||||
|
|
||||||
fmt-lint-and-types:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./apps/documentation
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm i -g pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --ignore-workspace
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
run: pnpm format:check
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: pnpm lint
|
|
||||||
|
|
||||||
- name: Types
|
|
||||||
run: pnpm types
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
name: Deploy documentation website on AWS S3
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'apps/documentation/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-documentation:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./apps/documentation
|
|
||||||
env:
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Setup rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
|
|
||||||
- name: Install tuono
|
|
||||||
run: cargo install tuono@0.10.4
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm i -g pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --ignore-workspace
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: tuono build --static
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
uses: reggionick/s3-deploy@v4
|
|
||||||
with:
|
|
||||||
folder: apps/documentation/out/static
|
|
||||||
bucket: tuono-documentation
|
|
||||||
bucket-region: eu-west-3
|
|
||||||
dist-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
|
|
||||||
invalidation: /
|
|
||||||
delete-removed: true
|
|
||||||
no-cache: true
|
|
||||||
private: true
|
|
||||||
files-to-include: '{.*/**,**}'
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: Docker CI
|
||||||
|
|
||||||
|
# If we need support more OS refer to
|
||||||
|
# https://github.com/tuono-labs/tuono/pull/332#issuecomment-2614439692
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Build and Test Docker Image on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build and Run Docker Compose
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml up --build -d
|
||||||
|
|
||||||
|
- name: Check if the container is running
|
||||||
|
run: |
|
||||||
|
docker ps --filter "name=tuono-source-container" --format "table {{.Names}}\t{{.Status}}"
|
||||||
|
if ! docker ps --filter "name=tuono-source-container" --format "{{.Status}}" | grep -q "Up"; then
|
||||||
|
echo "Error: Container 'tuono-source-container' is not running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check commands inside container
|
||||||
|
run: |
|
||||||
|
docker exec tuono-source-container pnpm --version || { echo "Error: pnpm is not available."; exit 1; }
|
||||||
|
docker exec tuono-source-container tuono --version || { echo "Error: tuono is not available."; exit 1; }
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
run: docker exec tuono-source-container cargo test
|
||||||
|
|
||||||
|
- name: Install node dependencies in each tuono's sub-projets
|
||||||
|
run: docker exec tuono-source-container bash -c 'for dir in /tuono/packages/*; do [ -d "$dir" ] && (cd "$dir" && yes | pnpm install --frozen-lockfile); done'
|
||||||
|
|
||||||
|
- name: Run pnpm test
|
||||||
|
run: docker exec tuono-source-container pnpm test
|
||||||
|
|
||||||
|
- name: Tear Down Docker Compose
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml down --volumes
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: E2E CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e:
|
||||||
|
name: E2E Test (${{ matrix.os }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: pnpm exec playwright install
|
||||||
|
|
||||||
|
- name: Run Playwright E2E tests
|
||||||
|
run: pnpm test:e2e
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: E2E CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- e2e
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
name: Examples CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
example_list:
|
||||||
|
name: Get examples names
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
names: ${{ steps.find_folder.outputs.names }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- id: find_folder
|
||||||
|
working-directory: examples
|
||||||
|
# produces the list of folder inside examples without leading `./` and trailing `/`:
|
||||||
|
# ```text
|
||||||
|
# tuono-tutorial
|
||||||
|
# with-tailwind
|
||||||
|
# tuono-app
|
||||||
|
# with-mdx
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# after, using `jq` the output is converted into a JSON array:
|
||||||
|
# ```json
|
||||||
|
# ["tuono-tutorial","with-tailwind","tuono-app","with-mdx"]
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# and added to `$GITHUB_OUTPUT` env variable:
|
||||||
|
run: |
|
||||||
|
NAMES=$(find . -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
|
echo "names=$(echo "$NAMES" | jq -R -s -c 'split("\n")[:-1]')" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
check_example:
|
||||||
|
needs:
|
||||||
|
- example_list
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
example_name: ${{ fromJson(needs.example_list.outputs.names) }}
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
# to be added
|
||||||
|
# - 'windows-latest'
|
||||||
|
|
||||||
|
# Only on this repo some examples build including plugins fails
|
||||||
|
# However, if the project is downloaded via `tuono new`` it will work
|
||||||
|
# @see https://github.com/tuono-labs/tuono/pull/605#pullrequestreview-2656262230
|
||||||
|
# needs further investigations
|
||||||
|
exclude:
|
||||||
|
- example_name: with-tailwind
|
||||||
|
|
||||||
|
name: 'Check `${{ matrix.example_name }}` build on ${{ matrix.os }}'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Build Rust
|
||||||
|
run: cargo build
|
||||||
|
|
||||||
|
- name: Build typescript
|
||||||
|
run: pnpm run build
|
||||||
|
|
||||||
|
- name: Check "build"
|
||||||
|
working-directory: ./examples/${{ matrix.example_name }}
|
||||||
|
run: ../../target/debug/tuono build
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: pnpm run format
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Examples CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- example_list
|
||||||
|
- check_example
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: 'PR Labeler'
|
||||||
|
|
||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
# @see .github/labeler.yml
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
name: 'Manage labels'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Add checkout step to load labeler.yml locally,
|
||||||
|
# rather than from an API request
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
sync-labels: true
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: 'PR Title Checker'
|
||||||
|
|
||||||
|
# This workflow ensures that PR titles adhere to a pattern
|
||||||
|
# similar to conventional commits standard
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_pr_title:
|
||||||
|
name: 'PR Title Checker'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check type
|
||||||
|
id: type
|
||||||
|
# @warning Keep in sync with https://github.com/tuono-labs/tuono-documentation/blob/main/src/routes/documentation/contributing/pull-requests.mdx
|
||||||
|
run: |
|
||||||
|
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
|
||||||
|
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
|
||||||
|
|
||||||
|
if ! [[ $PR_TITLE =~ $REGEX ]]; then
|
||||||
|
echo "::error title=Type::The title has an incorrect type. Valid types are ${VALID_COMMIT_TYPES}"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Type::The title is using a valid type"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check length
|
||||||
|
id: length
|
||||||
|
run: |
|
||||||
|
MAX_LENGTH=100
|
||||||
|
LENGTH=${#PR_TITLE}
|
||||||
|
|
||||||
|
if [ $LENGTH -gt $MAX_LENGTH ]; then
|
||||||
|
echo "::error title=Length::The title is longer than $MAX_LENGTH characters"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Length::The title length is within the maximum value of $MAX_LENGTH characters"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check result
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(steps.*.outputs.result, ','), 'fail') }}
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE"
|
||||||
|
if [ "$FAILURE" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,78 +1,68 @@
|
|||||||
name: Publish on npm and crates
|
name: Publish on npm and crates
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-on-npm-dry-run:
|
publish_npm_dry_run:
|
||||||
|
name: NPM publish (dry run)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install NodeJS Dependencies
|
||||||
uses: actions/setup-node@v4
|
uses: ./.github/actions/install-node-dependencies
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm publish -r --no-git-checks --dry-run
|
run: pnpm publish -r --no-git-checks --dry-run
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
publish-on-crates-dry-run:
|
publish_crates_dry_run:
|
||||||
|
name: Crates publish (dry run)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout code
|
||||||
- uses: actions-rs/toolchain@v1
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
toolchain: stable
|
- name: Setup rust toolchain
|
||||||
override: true
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
- uses: katyo/publish-crates@v2
|
with:
|
||||||
with:
|
toolchain: 'stable'
|
||||||
|
|
||||||
|
- uses: katyo/publish-crates@v2
|
||||||
|
with:
|
||||||
path: './crates/tuono'
|
path: './crates/tuono'
|
||||||
dry-run: true
|
dry-run: true
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
check-repo: true
|
check-repo: true
|
||||||
|
|
||||||
publish-on-npm:
|
publish_npm:
|
||||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
name: NPM publish
|
||||||
|
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install NodeJS Dependencies
|
||||||
uses: actions/setup-node@v4
|
uses: ./.github/actions/install-node-dependencies
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Clone root README.md on tuono package
|
- name: Clone root README.md on tuono package
|
||||||
run: cp README.md packages/tuono/README.md
|
run: cp README.md packages/tuono/README.md
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
@@ -81,17 +71,22 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
publish-on-crates:
|
publish_crates:
|
||||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
name: Crates publish
|
||||||
|
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout code
|
||||||
- uses: actions-rs/toolchain@v1
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
toolchain: stable
|
- name: Setup rust toolchain
|
||||||
override: true
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
- uses: katyo/publish-crates@v2
|
with:
|
||||||
with:
|
toolchain: 'stable'
|
||||||
|
|
||||||
|
- uses: katyo/publish-crates@v2
|
||||||
|
with:
|
||||||
path: './crates/tuono'
|
path: './crates/tuono'
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
check-repo: true
|
check-repo: true
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Repo root CI
|
||||||
|
|
||||||
|
# All workflows are triggered based on specific paths,
|
||||||
|
# the root repository is something that is not applicable to rust nor typescript
|
||||||
|
# so it has been moved in a standalone workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
format:
|
||||||
|
name: Check format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Test project
|
||||||
|
run: pnpm repo:root:format
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Repo root CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- format
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Rust CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Build and test crates on ${{ matrix.os }} with rust ${{ matrix.toolchain }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
toolchain:
|
||||||
|
- 'stable'
|
||||||
|
|
||||||
|
# Test `beta` and `nightly` toolchains only on `ubuntu`
|
||||||
|
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations
|
||||||
|
include:
|
||||||
|
- toolchain: 'beta'
|
||||||
|
os: 'ubuntu-latest'
|
||||||
|
- toolchain: 'nightly'
|
||||||
|
os: 'ubuntu-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.toolchain }}
|
||||||
|
|
||||||
|
- run: cargo build --verbose
|
||||||
|
- run: cargo test --verbose
|
||||||
|
|
||||||
|
lint_and_fmt:
|
||||||
|
name: Check format and lint rust crates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Rust CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- build_and_test
|
||||||
|
- lint_and_fmt
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
name: Rust CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'crates/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'crates/**'
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint_and_fmt:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: cargo fmt --all -- --check
|
|
||||||
- run: cargo clippy -- -D warnings
|
|
||||||
|
|
||||||
build_and_test:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Build and test rust crates
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
toolchain:
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
|
|
||||||
- run: cargo build --verbose
|
|
||||||
- run: cargo test --verbose
|
|
||||||
@@ -1,21 +1,70 @@
|
|||||||
name: Spell Checker
|
name: Spell Checker
|
||||||
|
|
||||||
on: [pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
spell_checking:
|
spell_checking:
|
||||||
name: runner / LanguageTools
|
name: LanguageTool
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout code
|
||||||
- uses: reviewdog/action-languagetool@v1
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.REVIEWDOG_TOKEN }}
|
# Keep this value in order to show the error comments
|
||||||
|
# also on edited files.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If the workflow is triggered by a push use check reporter
|
||||||
|
#
|
||||||
|
# If the workflow is triggered by a PR coming from the
|
||||||
|
# same repository, the author has write access so we can
|
||||||
|
# use pr-review reporter since the GITHUB_TOKEN will have review
|
||||||
|
# write permissions
|
||||||
|
#
|
||||||
|
# Otherwise use annotation reporter that do not require any
|
||||||
|
# particular permission
|
||||||
|
#
|
||||||
|
# Why this?
|
||||||
|
# https://github.com/Valerioageno/tuono/pull/137#pullrequestreview-2455455272
|
||||||
|
- name: Set reporter
|
||||||
|
id: reporter
|
||||||
|
env:
|
||||||
|
EVENT_NAME: ${{ github.event_name }}
|
||||||
|
FROM_MAIN_REPO: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
|
||||||
|
run: |
|
||||||
|
echo "EVENT_NAME: $EVENT_NAME FROM_MAIN_REPO: $FROM_MAIN_REPO"
|
||||||
|
if [ "$EVENT_NAME" == 'push' ]; then
|
||||||
|
echo "name=github-check" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "$FROM_MAIN_REPO" == 'true' ]; then
|
||||||
|
echo "name=github-pr-review" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "name=github-pr-annotations" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Review dog languagetool
|
||||||
|
uses: reviewdog/action-languagetool@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
||||||
reporter: 'github-pr-review'
|
reporter: ${{ steps.reporter.outputs.name }}
|
||||||
# Change reporter level if you need.
|
# Change reporter level if you need.
|
||||||
level: 'error'
|
level: 'warning'
|
||||||
patterns: '**/*.md **/*.txt **/*.mdx'
|
patterns: '**/*.md **/*.txt **/*.mdx'
|
||||||
#enabled_categories: 'STYLE,PLAIN_ENGLISH,WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END'
|
# enabled_categories: "STYLE,PLAIN_ENGLISH,WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END"
|
||||||
disabled_categories: ''
|
disabled_categories: ''
|
||||||
disabled_rules: ''
|
disabled_rules: ''
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
name: Typescript CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Check build and test
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Test project
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
lint_and_fmt:
|
||||||
|
name: Check format and lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: pnpm run format
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Typescript CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- build_and_test
|
||||||
|
- lint_and_fmt
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
name: Typescript CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'packages/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-test:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
timeout-minutes: 15
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm i -g pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Test project
|
|
||||||
run: pnpm test
|
|
||||||
|
|
||||||
fmt-lint-and-types:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
run: npm i -g pnpm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
run: pnpm format:check
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: pnpm lint
|
|
||||||
|
|
||||||
- name: Types
|
|
||||||
run: pnpm types
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
|
## Node related ignores
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.turbo/
|
.turbo/
|
||||||
|
|
||||||
*/**/routeTree.gen.ts
|
*/**/routeTree.gen.ts
|
||||||
|
|
||||||
pnpm-lock.yaml
|
examples/*/pnpm-lock.yaml
|
||||||
|
|
||||||
examples/playground/
|
vite.config.ts.timestamp*
|
||||||
|
|
||||||
## Rust related ignores
|
## Rust related ignores
|
||||||
|
|
||||||
@@ -24,3 +26,7 @@ examples/playground/
|
|||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
# Files, generated by the code editors
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
dist
|
||||||
|
.tuono
|
||||||
|
out
|
||||||
|
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||||
|
|
||||||
|
e2e/fixtures/*/test-results
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* @Valerioageno
|
||||||
|
|
||||||
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community include:
|
community include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
- Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
- Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best, not just for us as individuals, but for the
|
- Focusing on what is best, not just for us as individuals, but for the
|
||||||
overall community
|
overall community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
- The use of sexualized language or imagery, and sexual attention or
|
||||||
Advances of any kind
|
Advances of any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
- Publishing others' private information, such as a physical or email
|
||||||
address, without their explicit permission
|
address, without their explicit permission
|
||||||
* Other conduct that could reasonably be considered inappropriate in a
|
- Other conduct that could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
|||||||
### 4. Permanent Ban
|
### 4. Permanent Ban
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||

|

|
||||||

|

|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
# How to Contribute to Tuono
|
# How to Contribute to Tuono
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Any feature contribution or suggestion is strongly appreciated.
|
Any feature contribution or suggestion is strongly appreciated.
|
||||||
Since the current project size, there isn't yet a defined way to start a discussion. Consider [opening a new issue](https://github.com/Valerioageno/tuono/issues/new)
|
|
||||||
or to reach me using my email address, [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it). I'm also available on Twitter (X); DM @valerioageno.
|
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project. You can pick any task that you see in the `ready` column that is not already assigned.
|
||||||
|
|
||||||
|
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
|
||||||
|
|
||||||
|
Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/choose) if you find a bug.
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
**Did you find a bug?**
|
**Did you find a bug?**
|
||||||
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/Valerioageno/tuono/issues).
|
|
||||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Valerioageno/tuono/issues/new). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
|
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/tuono-labs/tuono/issues).
|
||||||
|
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/tuono-labs/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
|
||||||
|
|
||||||
**Did you write a patch that fixes a bug?**
|
**Did you write a patch that fixes a bug?**
|
||||||
- Open a new GitHub pull request with the patch.
|
|
||||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable.
|
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
- The pull requests must pass all the CI pipelines
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tuono",
|
"crates/tuono",
|
||||||
"crates/tuono_lib",
|
"crates/tuono_internal",
|
||||||
"crates/tuono_lib_macros",
|
"crates/tuono_lib",
|
||||||
|
"crates/tuono_lib_macros"
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
"apps/documentation",
|
"examples/with-mdx",
|
||||||
"examples/mdx",
|
"examples/tuono-app",
|
||||||
"examples/tuono",
|
"examples/tuono-tutorial",
|
||||||
"examples/tutorial",
|
"examples/with-tailwind",
|
||||||
"benches/tuono"
|
"e2e/fixtures/base",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,69 +1,60 @@
|
|||||||
[Documentation](https://tuono.dev) | [Tutorial](https://tuono.dev/documentation/tutorial) |
|
[Documentation](https://tuono.dev) | [Tutorial](https://tuono.dev/documentation/tutorial) |
|
||||||
[✨Contributing](https://tuono.dev/documentation/contributing)
|
[Discord](https://discord.com/invite/khQzPa654B) | [✨Contributing](https://tuono.dev/documentation/contributing)
|
||||||
|
|
||||||
# Tuono
|
# Tuono
|
||||||
|
|
||||||
<img align="right" src="https://raw.githubusercontent.com/Valerioageno/tuono/main/assets/logo.png" width="160px">
|
<img align="right" src="https://raw.githubusercontent.com/tuono-labs/tuono/main/assets/logo.png" width="160px">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
**Tuono is the fastest React framework out there**
|
**Tuono is a full-stack web framework for building React applications using Rust as the backend with
|
||||||
|
a strong focus on usability and performance.**
|
||||||
|
|
||||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
||||||
Why Tuono? Just a badass name.
|
Why Tuono? Just a badass name.
|
||||||
|
|
||||||
Some of its features are:
|
If you have experience with [Next.js](https://nextjs.org/) you will feel home here.
|
||||||
|
|
||||||
- 🟦 Native Typescript
|
Some of its features are:
|
||||||
- 🌐 NextJs like Routing
|
|
||||||
- 🍭 CSS/SCSS modules
|
|
||||||
- 🧬 Server Side Rendering
|
|
||||||
- 🔥 Hot Module Reload
|
|
||||||
|
|
||||||
## 📖 Documentation
|
- 🟦 Native Typescript
|
||||||
|
- 🌐 Next.js like Routing
|
||||||
|
- 🍭 CSS/SCSS modules
|
||||||
|
- 🧬 Server Side Rendering
|
||||||
|
- 🔥 Hot Module Reload
|
||||||
|
|
||||||
The [documentation](https://tuono.dev/documentation) is available on
|
## Documentation
|
||||||
|
|
||||||
|
The [documentation](https://tuono.dev/) is available on
|
||||||
[tuono.dev](https://tuono.dev/).
|
[tuono.dev](https://tuono.dev/).
|
||||||
|
|
||||||
## Introduction
|
## Installation
|
||||||
|
|
||||||
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
|
Available on MacOS, Linux and Windows.
|
||||||
|
|
||||||
Tuono is a full-stack React framework with the server side written in Rust.
|
```sh
|
||||||
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
|
cargo install tuono
|
||||||
React is still React - it is just superpowered.
|
```
|
||||||
|
|
||||||
**Rust is a hard language, then writing server-side code is hard as well, right? (no again)**
|
|
||||||
|
|
||||||
Tuono provides a collection of utilities to handle the server side code seamlessly with the React code.
|
|
||||||
Each server-side route is managed in a separate file alongside the React route. The routing is handled
|
|
||||||
by Tuono based on the files defined within the `./src/routes` directory.
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
As already mentioned above, I strongly suggest you take a look at the
|
Create a new project with:
|
||||||
[tutorial](https://github.com/Valerioageno/tuono/blob/main/docs/tutorial.md).
|
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
```sh
|
||||||
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
|
tuono new [PROJECT_NAME]
|
||||||
package manager.
|
```
|
||||||
|
|
||||||
Then run `cargo install tuono`.
|
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
|
||||||
|
|
||||||
To list all the available commands, run `tuono -h`
|
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||||
|
|
||||||
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
|
```sh
|
||||||
[examples](https://github.com/Valerioageno/tuono/tree/main/examples) folder).
|
tuono dev
|
||||||
|
```
|
||||||
Then, to run the local development environment, run it inside the project folder `tuono dev`
|
|
||||||
|
|
||||||
Finally, when the project is ready to be deployed, just run `tuono build` to create the final React assets
|
|
||||||
and to set the server project in `production` mode.
|
|
||||||
|
|
||||||
Now to execute it just run `cargo run --release`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Any help or suggestion will be appreciated and encouraged.
|
Any help or suggestion will be appreciated and encouraged.
|
||||||
Check the [✨Contributing](https://tuono.dev/documentation/contributing) page
|
Check the [✨Contributing](https://tuono.dev/documentation/contributing) page
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# Stadalone apps folder
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"reportUnusedDisableDirectives": true,
|
|
||||||
"ignorePatterns": ["**/build", "**/dist"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint", "import"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/stylistic",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2020": true,
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"tsconfigRootDir": ".",
|
|
||||||
"project": true,
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": true,
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"version": "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/array-type": "error",
|
|
||||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
|
||||||
"@typescript-eslint/no-empty-object-type": "error",
|
|
||||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "error",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"error",
|
|
||||||
{ "prefer": "type-imports" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
|
||||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "typeParameter",
|
|
||||||
"format": ["PascalCase"],
|
|
||||||
"leadingUnderscore": "forbid",
|
|
||||||
"trailingUnderscore": "forbid",
|
|
||||||
"custom": {
|
|
||||||
"regex": "^(T|T[A-Z][A-Za-z]+)$",
|
|
||||||
"match": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-empty-function": "error",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
|
||||||
"@typescript-eslint/no-inferrable-types": [
|
|
||||||
"error",
|
|
||||||
{ "ignoreParameters": true },
|
|
||||||
],
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/namespace": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"import/no-cycle": "error",
|
|
||||||
"import/no-duplicates": "off",
|
|
||||||
"import/no-named-as-default-member": "error",
|
|
||||||
"import/no-unused-modules": "error",
|
|
||||||
"import/order": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"object",
|
|
||||||
"type",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-case-declarations": "error",
|
|
||||||
"no-empty": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-redeclare": "error",
|
|
||||||
"no-shadow": "error",
|
|
||||||
"no-undef": "off",
|
|
||||||
"sort-imports": "off",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tuono"
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tuono"
|
|
||||||
path = ".tuono/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tuono_lib = "0.11.1"
|
|
||||||
glob = "0.3.1"
|
|
||||||
time = { version = "0.3", features = ["macros"] }
|
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Tuono starter
|
|
||||||
|
|
||||||
This is the starter tuono project. To download it run in your terminal:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ tuono new [NAME]
|
|
||||||
```
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "tuono",
|
|
||||||
"description": "The react/rust fullstack framework",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint --ext .ts,.tsx ./src -c .eslintrc",
|
|
||||||
"format": "prettier -u --write --ignore-unknown './src/**/*'",
|
|
||||||
"format:check": "prettier --check --ignore-unknown './src/**/*'",
|
|
||||||
"types": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mantine/code-highlight": "^7.11.2",
|
|
||||||
"@mantine/core": "^7.11.2",
|
|
||||||
"@mantine/hooks": "^7.11.2",
|
|
||||||
"@mdx-js/react": "^3.0.1",
|
|
||||||
"@tabler/icons-react": "^3.11.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"react": "18.3.1",
|
|
||||||
"react-dom": "18.3.1",
|
|
||||||
"tuono": "0.11.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mdx": "^2.0.13",
|
|
||||||
"@types/react": "^18.3.3",
|
|
||||||
"@types/react-dom": "^18.3.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
|
||||||
"eslint-plugin-import": "^2.29.1",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
|
||||||
"postcss": "^8.4.39",
|
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
|
||||||
"postcss-simple-vars": "^7.0.1",
|
|
||||||
"prettier": "^3.2.4",
|
|
||||||
"typescript": "^5.5.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
'postcss-preset-mantine': {},
|
|
||||||
'postcss-simple-vars': {
|
|
||||||
variables: {
|
|
||||||
'mantine-breakpoint-xs': '36em',
|
|
||||||
'mantine-breakpoint-sm': '48em',
|
|
||||||
'mantine-breakpoint-md': '62em',
|
|
||||||
'mantine-breakpoint-lg': '75em',
|
|
||||||
'mantine-breakpoint-xl': '88em',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 709 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 314 KiB |
@@ -1 +0,0 @@
|
|||||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { Breadcrumbs, Button } from '@mantine/core'
|
|
||||||
import { Link, Head } from 'tuono'
|
|
||||||
|
|
||||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
interface BreadcrumbsProps {
|
|
||||||
breadcrumbs: Breadcrumb[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TuonoBreadcrumbs({
|
|
||||||
breadcrumbs = [],
|
|
||||||
}: BreadcrumbsProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{`[
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "BreadcrumbList",
|
|
||||||
"itemListElement": [
|
|
||||||
{
|
|
||||||
'@type': "ListItem",
|
|
||||||
"position": 1,
|
|
||||||
"name": "Tuono - Documentation",
|
|
||||||
"item": "https://tuono.dev/documentation",
|
|
||||||
}${breadcrumbs.length > 0 ? ',' : ''}
|
|
||||||
${breadcrumbs
|
|
||||||
.map((br, i) =>
|
|
||||||
JSON.stringify({
|
|
||||||
'@type': 'ListItem',
|
|
||||||
position: i + 2,
|
|
||||||
name: br.label,
|
|
||||||
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.join(',')}]
|
|
||||||
}
|
|
||||||
]`}
|
|
||||||
</script>
|
|
||||||
</Head>
|
|
||||||
<Breadcrumbs
|
|
||||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
|
||||||
mb="md"
|
|
||||||
mt="md"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
href="/documentation"
|
|
||||||
component={Link}
|
|
||||||
variant="subtle"
|
|
||||||
radius="xl"
|
|
||||||
p={5}
|
|
||||||
>
|
|
||||||
<IconBolt />
|
|
||||||
</Button>
|
|
||||||
{breadcrumbs.map((br) => (
|
|
||||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
|
||||||
))}
|
|
||||||
</Breadcrumbs>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BreadcrumbElementProps {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BreadcrumbElement({
|
|
||||||
href,
|
|
||||||
label,
|
|
||||||
}: BreadcrumbElementProps): JSX.Element {
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button component="span" variant="light" radius="xl">
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import Breadcrumbs, { BreadcrumbElement } from './breadcrumbs'
|
|
||||||
|
|
||||||
export default Breadcrumbs
|
|
||||||
|
|
||||||
export { BreadcrumbElement as Element }
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Button } from '@mantine/core'
|
|
||||||
import { IconEdit } from '@tabler/icons-react'
|
|
||||||
import { useRouter } from 'tuono'
|
|
||||||
|
|
||||||
const GITHUB_URL =
|
|
||||||
'https://github.com/Valerioageno/tuono/tree/main/apps/documentation/src/routes'
|
|
||||||
|
|
||||||
export default function EditPage(): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
p={0}
|
|
||||||
mt={60}
|
|
||||||
component="a"
|
|
||||||
variant="transparent"
|
|
||||||
leftSection={<IconEdit />}
|
|
||||||
target="_blank"
|
|
||||||
href={GITHUB_URL.concat(pathname).concat('.mdx')}
|
|
||||||
>
|
|
||||||
Edit page
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import EditPage from './edit-page'
|
|
||||||
|
|
||||||
export default EditPage
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
Center,
|
|
||||||
Container,
|
|
||||||
CopyButton,
|
|
||||||
Group,
|
|
||||||
rem,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { IconCopy, IconCheck } from '@tabler/icons-react'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
export default function Hero(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Container size={1100} my={100}>
|
|
||||||
<Center>
|
|
||||||
<Title order={1}>The React/Rust full-stack framework</Title>
|
|
||||||
</Center>
|
|
||||||
<Center mt={50}>
|
|
||||||
<Text fz="18px">
|
|
||||||
The technologies we love seamlessly working together to unleash the
|
|
||||||
<strong> highest web performance</strong> ever met on React
|
|
||||||
</Text>
|
|
||||||
</Center>
|
|
||||||
<Center mt={50}>
|
|
||||||
<Group>
|
|
||||||
<Button component={Link} href="/documentation" size="lg">
|
|
||||||
Get Started
|
|
||||||
</Button>
|
|
||||||
<CopyButton value="cargo install tuono">
|
|
||||||
{({ copied, copy }) => (
|
|
||||||
<Button
|
|
||||||
onClick={copy}
|
|
||||||
size="lg"
|
|
||||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
|
||||||
color="gray"
|
|
||||||
leftSection="$ cargo install tuono"
|
|
||||||
rightSection={
|
|
||||||
copied ? (
|
|
||||||
<IconCheck style={{ width: rem(20) }} />
|
|
||||||
) : (
|
|
||||||
<IconCopy style={{ width: rem(20) }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CopyButton>
|
|
||||||
</Group>
|
|
||||||
</Center>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Hero from './hero'
|
|
||||||
|
|
||||||
export default Hero
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxProvider from './mdx-provider'
|
|
||||||
|
|
||||||
export default MdxProvider
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Text, type TextProps } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxBold(props: TextProps): JSX.Element {
|
|
||||||
return <Text fw={700} {...props} />
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxCode from './mdx-code'
|
|
||||||
|
|
||||||
export default MdxCode
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { Code } from '@mantine/core'
|
|
||||||
import type { HTMLAttributes } from 'react'
|
|
||||||
|
|
||||||
export default function MdxCode(
|
|
||||||
props: HTMLAttributes<HTMLPreElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
return <Code {...props} style={{ fontSize: 14 }} />
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxLink from './mdx-link'
|
|
||||||
|
|
||||||
export default MdxLink
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import type { AnchorHTMLAttributes } from 'react'
|
|
||||||
import { Button } from '@mantine/core'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconExternalLink } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
export default function MdxLink(
|
|
||||||
props: AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
{...props}
|
|
||||||
target="_blank"
|
|
||||||
rightSection={
|
|
||||||
<IconExternalLink size="16px" style={{ marginLeft: -5 }} />
|
|
||||||
}
|
|
||||||
variant="transparent"
|
|
||||||
style={{ height: '20px' }}
|
|
||||||
mt={-2}
|
|
||||||
p={0}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
{...props}
|
|
||||||
target="_blank"
|
|
||||||
variant="transparent"
|
|
||||||
style={{ height: '20px' }}
|
|
||||||
mt={-2}
|
|
||||||
p={0}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxPre from './mdx-pre'
|
|
||||||
|
|
||||||
export default MdxPre
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.pre {
|
|
||||||
code {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { CodeHighlight } from '@mantine/code-highlight'
|
|
||||||
import styles from './mdx-pre.module.css'
|
|
||||||
|
|
||||||
interface PreProps {
|
|
||||||
children: any
|
|
||||||
}
|
|
||||||
export default function MdxPre({ children }: PreProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CodeHighlight
|
|
||||||
className={styles.pre}
|
|
||||||
code={children.props.children || ''}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 100 }}
|
|
||||||
language={children.props.className?.replace('language-', '')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { MDXProvider } from '@mdx-js/react'
|
|
||||||
|
|
||||||
import MdxLink from './mdx-link'
|
|
||||||
import type { ReactNode } from 'react'
|
|
||||||
import MdxPre from './mdx-pre'
|
|
||||||
import MdxQuote from './mdx-quote'
|
|
||||||
import MdxCode from './mdx-code'
|
|
||||||
import { h } from './mdx-title'
|
|
||||||
import MdxBold from './mdx-bold/mdx-bold'
|
|
||||||
|
|
||||||
interface MdxProviderProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MdxProvider({
|
|
||||||
children,
|
|
||||||
}: MdxProviderProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<MDXProvider
|
|
||||||
components={{
|
|
||||||
a: MdxLink,
|
|
||||||
// @ts-expect-error: useless finding the correct props types
|
|
||||||
pre: MdxPre,
|
|
||||||
blockquote: MdxQuote,
|
|
||||||
code: MdxCode,
|
|
||||||
h1: h(1),
|
|
||||||
h2: h(2),
|
|
||||||
h3: h(3),
|
|
||||||
h4: h(4),
|
|
||||||
h5: h(5),
|
|
||||||
h6: h(6),
|
|
||||||
strong: MdxBold,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</MDXProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxQuote from './mdx-quote'
|
|
||||||
|
|
||||||
export default MdxQuote
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Blockquote, Space } from '@mantine/core'
|
|
||||||
import type { HTMLAttributes } from 'react'
|
|
||||||
|
|
||||||
export default function MdxQuote(
|
|
||||||
props: HTMLAttributes<HTMLQuoteElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Blockquote
|
|
||||||
color="violet"
|
|
||||||
py={1}
|
|
||||||
px={20}
|
|
||||||
mt={30}
|
|
||||||
iconSize={30}
|
|
||||||
{...props}
|
|
||||||
style={{ borderRadius: 8 }}
|
|
||||||
/>
|
|
||||||
<Space h="md" />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import MdxTitle, { h } from './mdx-title'
|
|
||||||
|
|
||||||
export default MdxTitle
|
|
||||||
|
|
||||||
export { h }
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Title, type TitleProps } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxTitle(props: TitleProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Title
|
|
||||||
data-heading={props.children}
|
|
||||||
data-order={props.order}
|
|
||||||
mt={20}
|
|
||||||
{...props}
|
|
||||||
id={String(props.children ?? '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(' ', '-')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const h =
|
|
||||||
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
|
|
||||||
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MetaTags from './meta-tags'
|
|
||||||
|
|
||||||
export default MetaTags
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import { Head } from 'tuono'
|
|
||||||
|
|
||||||
interface MetaTagsProps {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
type?: 'website' | 'article' | 'book' | 'video.movie' | 'music.song'
|
|
||||||
canonical: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MetaTags({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
type = 'article',
|
|
||||||
canonical,
|
|
||||||
}: MetaTagsProps): ReactNode {
|
|
||||||
return (
|
|
||||||
<Head>
|
|
||||||
<title>{title}</title>
|
|
||||||
<link rel="canonical" href={canonical} />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content={
|
|
||||||
description ??
|
|
||||||
'The technologies we love seamlessly working together to unleash the highest web performance ever met on React'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="keywords"
|
|
||||||
content="ReactJs, Typescript, Rust, JavaScript, Fullstack framework, Web development"
|
|
||||||
/>
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:type" content={type} />
|
|
||||||
<meta property="og:image" content="https://tuono.dev/og-cover.png" />
|
|
||||||
<meta property="og:url" content={canonical} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<meta property="og:site_name" content="Tuono" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
</Head>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Flex, Button, ActionIcon, Group } from '@mantine/core'
|
|
||||||
import { IconBrandGithub, IconBook, IconBrandX } from '@tabler/icons-react'
|
|
||||||
import ThemeBtn from '../theme-btn'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
export default function Actions(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Flex gap={8}>
|
|
||||||
<Group gap={8} visibleFrom="sm">
|
|
||||||
<Button
|
|
||||||
href="/documentation"
|
|
||||||
component={Link}
|
|
||||||
size="compact-lg"
|
|
||||||
rightSection={<IconBook />}
|
|
||||||
autoContrast
|
|
||||||
>
|
|
||||||
Get started
|
|
||||||
</Button>
|
|
||||||
<ActionIcon
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Check the project on github"
|
|
||||||
href="https://github.com/Valerioageno/tuono"
|
|
||||||
target="_blank"
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<IconBrandGithub />
|
|
||||||
</ActionIcon>
|
|
||||||
<ActionIcon
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="We are also on X!"
|
|
||||||
href="https://x.com/tuono_dev"
|
|
||||||
target="_blank"
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<IconBrandX />
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
<ThemeBtn />
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Navbar from './navbar'
|
|
||||||
|
|
||||||
export default Navbar
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { AppShell, Burger, Button, Flex } from '@mantine/core'
|
|
||||||
import { Link, useRouter } from 'tuono'
|
|
||||||
import Actions from './actions'
|
|
||||||
|
|
||||||
interface NavbarProps {
|
|
||||||
opened: boolean
|
|
||||||
toggle: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Navbar({ opened, toggle }: NavbarProps): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
return (
|
|
||||||
<AppShell.Header p="sm">
|
|
||||||
<Flex justify="space-between">
|
|
||||||
<Button component={Link} href="/" variant="transparent" p={0} fz={28}>
|
|
||||||
Tuono
|
|
||||||
</Button>
|
|
||||||
<Flex align="center" gap={8}>
|
|
||||||
<Actions />
|
|
||||||
{pathname.startsWith('/documentation') && (
|
|
||||||
<Burger
|
|
||||||
opened={opened}
|
|
||||||
onClick={toggle}
|
|
||||||
hiddenFrom="sm"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</AppShell.Header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import NavigationButtons from './navigation-buttons'
|
|
||||||
|
|
||||||
export default NavigationButtons
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
interface NavigationButton {
|
|
||||||
href: string
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavigationButtonsProps {
|
|
||||||
prev?: NavigationButton
|
|
||||||
next?: NavigationButton
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NavigationButtons({
|
|
||||||
prev,
|
|
||||||
next,
|
|
||||||
}: NavigationButtonsProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Flex mt={50} gap={10}>
|
|
||||||
{prev && <NavigationBtn type="prev" {...prev} />}
|
|
||||||
{next && <NavigationBtn type="next" {...next} />}
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavigationButtonProps extends NavigationButton {
|
|
||||||
type: 'next' | 'prev'
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavigationBtn = ({
|
|
||||||
type,
|
|
||||||
title,
|
|
||||||
href,
|
|
||||||
}: NavigationButtonProps): JSX.Element => {
|
|
||||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
|
||||||
const textAlign = type === 'next' ? 'left' : 'right'
|
|
||||||
const variant = type === 'next' ? 'filled' : 'outline'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
fullWidth
|
|
||||||
variant={variant}
|
|
||||||
href={href}
|
|
||||||
justify="space-between"
|
|
||||||
h="auto"
|
|
||||||
rightSection={type === 'next' && <IconArrowRight />}
|
|
||||||
leftSection={type === 'prev' && <IconArrowLeft />}
|
|
||||||
p="20"
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Title component="span" display="block" order={4} style={{ textAlign }}>
|
|
||||||
{heading}
|
|
||||||
</Title>
|
|
||||||
<Text component="span" display="block" style={{ textAlign }}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Sidebar from './sidebar'
|
|
||||||
|
|
||||||
export default Sidebar
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.link {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { NavLink, type NavLinkProps } from '@mantine/core'
|
|
||||||
import { useState, type ReactNode } from 'react'
|
|
||||||
import { Link, useRouter } from 'tuono'
|
|
||||||
import { IconChevronRight } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
import styles from './sidebar-link.module.css'
|
|
||||||
|
|
||||||
interface SidebarLinkProps {
|
|
||||||
label: string
|
|
||||||
href: string
|
|
||||||
onClick?: () => void
|
|
||||||
children?: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SidebarLink(
|
|
||||||
props: SidebarLinkProps & NavLinkProps,
|
|
||||||
): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(!!props.defaultOpened)
|
|
||||||
|
|
||||||
const internalProps = {
|
|
||||||
active: pathname === props.href,
|
|
||||||
className: styles.link,
|
|
||||||
rightSection: props.children && (
|
|
||||||
<IconChevronRight
|
|
||||||
size="1.2rem"
|
|
||||||
stroke={1.5}
|
|
||||||
className="mantine-rotate-rtl"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
setIsOpen((state) => !state)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
opened: isOpen,
|
|
||||||
autoContrast: true,
|
|
||||||
...props,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.href.startsWith('#')) {
|
|
||||||
return <NavLink component="button" {...internalProps} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <NavLink component={Link} {...internalProps} />
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { AppShell } from '@mantine/core'
|
|
||||||
import SidebarLink from './sidebar-link'
|
|
||||||
|
|
||||||
export default function Sidebar({ close }: { close: () => void }): JSX.Element {
|
|
||||||
return (
|
|
||||||
<AppShell.Navbar p="md">
|
|
||||||
<SidebarLink href="/documentation" label="Overview" onClick={close} />
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/installation"
|
|
||||||
label="Installation"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial"
|
|
||||||
label="Tutorial"
|
|
||||||
defaultOpened
|
|
||||||
onClick={close}
|
|
||||||
>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/development-setup"
|
|
||||||
label="Development setup"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/api-fetching"
|
|
||||||
label="API fetching"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/components"
|
|
||||||
label="Components"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/dynamic-routes"
|
|
||||||
label="Dynamic routes"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/error-handling"
|
|
||||||
label="Error handling"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/seo"
|
|
||||||
label="SEO and meta tags"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/redirections"
|
|
||||||
label="Server redirection"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/production"
|
|
||||||
label="Production build"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/tutorial/conclusion"
|
|
||||||
label="Conclusion"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
</SidebarLink>
|
|
||||||
<SidebarLink href="/documentation/cli" label="CLI" onClick={close} />
|
|
||||||
<SidebarLink
|
|
||||||
label="Routing"
|
|
||||||
href="/documentation/routing"
|
|
||||||
onClick={close}
|
|
||||||
>
|
|
||||||
<SidebarLink
|
|
||||||
href="/documentation/routing/intro"
|
|
||||||
label="Project structure"
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
</SidebarLink>
|
|
||||||
<SidebarLink
|
|
||||||
label="Contributing"
|
|
||||||
href="/documentation/contributing"
|
|
||||||
onClick={close}
|
|
||||||
leftSection="✨"
|
|
||||||
/>
|
|
||||||
</AppShell.Navbar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
export interface Heading {
|
|
||||||
depth: number
|
|
||||||
content: string
|
|
||||||
id: string
|
|
||||||
getNode: () => HTMLHeadingElement
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHeadingsData(headings: HTMLHeadingElement[]): Heading[] {
|
|
||||||
const result: Heading[] = []
|
|
||||||
|
|
||||||
for (const heading of headings) {
|
|
||||||
if (heading.id) {
|
|
||||||
result.push({
|
|
||||||
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
|
|
||||||
content: heading.getAttribute('data-heading') || '',
|
|
||||||
id: heading.id,
|
|
||||||
getNode: () =>
|
|
||||||
document.getElementById(heading.id) as HTMLHeadingElement,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHeadings(): Heading[] {
|
|
||||||
const root = document.getElementById('mdx-root')
|
|
||||||
console.log(root)
|
|
||||||
|
|
||||||
if (!root) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHeadingsData(Array.from(root.querySelectorAll('[data-heading]')))
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
import { TableOfContents } from './table-of-content'
|
|
||||||
|
|
||||||
export default TableOfContents
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
.wrapper {
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
position: sticky;
|
|
||||||
top: var(--mantine-spacing-xl);
|
|
||||||
right: 0;
|
|
||||||
padding-top: 55px;
|
|
||||||
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
|
|
||||||
width: 200px;
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-with-tabs] {
|
|
||||||
padding-top: 0;
|
|
||||||
top: calc(var(--mantine-spacing-xl) + 60px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner {
|
|
||||||
padding-bottom: var(--mantine-spacing-xl);
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
display: block;
|
|
||||||
border-left: 1px solid transparent;
|
|
||||||
padding: 8px var(--mantine-spacing-md);
|
|
||||||
margin-left: -1px;
|
|
||||||
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
|
|
||||||
border-top-right-radius: var(--mantine-radius-sm);
|
|
||||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
color: var(--mantine-color-gray-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
color: var(--mantine-color-dark-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 1px solid transparent;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: -1px;
|
|
||||||
border-top-left-radius: var(--mantine-radius-sm);
|
|
||||||
border-bottom-left-radius: var(--mantine-radius-sm);
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
border-color: var(--mantine-color-violet-5);
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
color: var(--mantine-color-violet-8);
|
|
||||||
background-color: var(--mantine-color-violet-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
color: var(--mantine-color-violet-1);
|
|
||||||
background-color: #534a6d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-left: var(--mantine-spacing-md);
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
border-left: 1px solid;
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
border-color: var(--mantine-color-gray-3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
border-color: var(--mantine-color-dark-4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 1px solid;
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
border-color: var(--mantine-color-gray-3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
border-color: var(--mantine-color-dark-4) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { useRouter, Link } from 'tuono'
|
|
||||||
import { IconList } from '@tabler/icons-react'
|
|
||||||
import { Box, rem, ScrollArea, Text } from '@mantine/core'
|
|
||||||
import { getHeadings, type Heading } from './get-headings'
|
|
||||||
import classes from './table-of-content.module.css'
|
|
||||||
|
|
||||||
interface TableOfContentsProps {
|
|
||||||
withTabs: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveElement(rects: DOMRect[]): number {
|
|
||||||
if (rects.length === 0) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const closest = rects.reduce(
|
|
||||||
(acc, item, index) => {
|
|
||||||
if (Math.abs(acc.position) < Math.abs(item.y)) {
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
index,
|
|
||||||
position: item.y,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ index: 0, position: rects[0].y },
|
|
||||||
)
|
|
||||||
|
|
||||||
return closest.index
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TableOfContents({
|
|
||||||
withTabs,
|
|
||||||
}: TableOfContentsProps): JSX.Element | null {
|
|
||||||
const [active, setActive] = useState(0)
|
|
||||||
const [headings, setHeadings] = useState<Heading[]>([])
|
|
||||||
const headingsRef = useRef<Heading[]>([])
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const filteredHeadings = headings.filter((heading) => heading.depth > 1)
|
|
||||||
|
|
||||||
const handleScroll = (): void => {
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
headingsRef.current.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const _headings = getHeadings()
|
|
||||||
headingsRef.current = _headings
|
|
||||||
setHeadings(_headings)
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
_headings.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return (): void => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [router.pathname])
|
|
||||||
|
|
||||||
if (filteredHeadings.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = filteredHeadings.map((heading, index) => (
|
|
||||||
<Text
|
|
||||||
key={heading.id}
|
|
||||||
component={Link}
|
|
||||||
fz="sm"
|
|
||||||
p={10}
|
|
||||||
className={classes.link}
|
|
||||||
mod={{ active: active === index }}
|
|
||||||
href={`#${heading.id}`}
|
|
||||||
__vars={{ '--toc-link-offset': `${heading.depth - 1}` }}
|
|
||||||
>
|
|
||||||
{heading.content}
|
|
||||||
</Text>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
mod={{ 'with-tabs': withTabs }}
|
|
||||||
className={classes.wrapper}
|
|
||||||
>
|
|
||||||
<div className={classes.inner}>
|
|
||||||
<div>
|
|
||||||
<div className={classes.header}>
|
|
||||||
<IconList
|
|
||||||
style={{ width: rem(20), height: rem(20) }}
|
|
||||||
stroke={1.5}
|
|
||||||
/>
|
|
||||||
<Text className={classes.title}>Table of contents</Text>
|
|
||||||
</div>
|
|
||||||
<ScrollArea.Autosize
|
|
||||||
mah={`calc(100vh - ${rem(140)})`}
|
|
||||||
type="never"
|
|
||||||
offsetScrollbars
|
|
||||||
>
|
|
||||||
<div className={classes.items}>{items}</div>
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import ThemeBtn from './theme-btn'
|
|
||||||
|
|
||||||
export default ThemeBtn
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
.icon {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
@mixin dark {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.light {
|
|
||||||
@mixin light {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
useMantineColorScheme,
|
|
||||||
useComputedColorScheme,
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
|
||||||
import cx from 'clsx'
|
|
||||||
|
|
||||||
import classes from './theme-btn.module.css'
|
|
||||||
|
|
||||||
export default function ThemeBtn(): JSX.Element {
|
|
||||||
const { setColorScheme } = useMantineColorScheme()
|
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
|
||||||
getInitialValueInEffect: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ActionIcon
|
|
||||||
onClick={() =>
|
|
||||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
|
||||||
}
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Toggle color scheme"
|
|
||||||
>
|
|
||||||
<IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
|
|
||||||
<IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// declaration.d.ts
|
|
||||||
declare module '*.css'
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import {
|
|
||||||
ColorSchemeScript,
|
|
||||||
createTheme,
|
|
||||||
MantineProvider,
|
|
||||||
AppShell,
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { useDisclosure } from '@mantine/hooks'
|
|
||||||
import { Head, useRouter } from 'tuono'
|
|
||||||
import Navbar from '../components/navbar'
|
|
||||||
|
|
||||||
import '@mantine/core/styles.css'
|
|
||||||
import '@mantine/code-highlight/styles.css'
|
|
||||||
|
|
||||||
import Sidebar from '../components/sidebar'
|
|
||||||
|
|
||||||
interface RootRouteProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
primaryColor: 'violet',
|
|
||||||
primaryShade: { light: 6, dark: 9 },
|
|
||||||
fontFamily: 'Roboto',
|
|
||||||
respectReducedMotion: true,
|
|
||||||
radius: {
|
|
||||||
xs: '8px',
|
|
||||||
lg: '8px',
|
|
||||||
xl: '8px',
|
|
||||||
md: '8px',
|
|
||||||
sm: '8px',
|
|
||||||
},
|
|
||||||
fontSizes: {
|
|
||||||
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
||||||
xs: '16px',
|
|
||||||
sm: '16px',
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
dark: [
|
|
||||||
'#d5d7e0',
|
|
||||||
'#acaebf',
|
|
||||||
'#8c8fa3',
|
|
||||||
'#666980',
|
|
||||||
'#4d4f66',
|
|
||||||
'#34354a',
|
|
||||||
'#2b2c3d',
|
|
||||||
'#1d1e30',
|
|
||||||
'#0c0d21',
|
|
||||||
'#01010a',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
headings: {
|
|
||||||
sizes: {
|
|
||||||
h1: {
|
|
||||||
fontSize: '48px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
|
||||||
const [opened, { toggle }] = useDisclosure()
|
|
||||||
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<meta charSet="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link
|
|
||||||
rel="apple-touch-icon"
|
|
||||||
sizes="180x180"
|
|
||||||
href="/apple-touch-icon.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/favicon-16x16.png"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
</Head>
|
|
||||||
<ColorSchemeScript />
|
|
||||||
<MantineProvider theme={theme}>
|
|
||||||
<AppShell
|
|
||||||
header={{ height: 60 }}
|
|
||||||
navbar={{
|
|
||||||
width: 300,
|
|
||||||
breakpoint: 'sm',
|
|
||||||
collapsed: { mobile: !opened },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Navbar opened={opened} toggle={toggle} />
|
|
||||||
{pathname.startsWith('/documentation') && <Sidebar close={toggle} />}
|
|
||||||
{children}
|
|
||||||
</AppShell>
|
|
||||||
</MantineProvider>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import { AppShell, Container } from '@mantine/core'
|
|
||||||
import MdxProvider from '../../components/mdx-provider'
|
|
||||||
import EditPage from '../../components/edit-page'
|
|
||||||
|
|
||||||
interface RootRouteProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<AppShell.Main>
|
|
||||||
<Container id="mdx-root" component="article" size="md" p={20}>
|
|
||||||
<MdxProvider>{children}</MdxProvider>
|
|
||||||
<EditPage />
|
|
||||||
</Container>
|
|
||||||
</AppShell.Main>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import MetaTags from '../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - CLI"
|
|
||||||
canonical="https://tuono.dev/documentation/cli"
|
|
||||||
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
|
||||||
|
|
||||||
# CLI
|
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
|
||||||
|
|
||||||
> ☝️ Check the [installation](/documentation/installation) page if you haven't installed the
|
|
||||||
> `tuono` CLI yet.
|
|
||||||
|
|
||||||
To list all the available commands, run `tuono -h`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
The React/Rust full-stack framework
|
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
dev Start the development environment
|
|
||||||
build Build the production assets
|
|
||||||
new Scaffold a new project
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a new project, run `tuono new [NAME]` (optionally you can pass the --template (or -t) flag - check the examples' folder).
|
|
||||||
|
|
||||||
Then to run the local development environment run inside the project folder `tuono dev`
|
|
||||||
|
|
||||||
Finally, when the project will be ready to be deployed just run `tuono build` to create the final React assets and to set the server project in the production mode.
|
|
||||||
|
|
||||||
Now to execute it, just run `cargo run --release`.
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import MetaTags from '../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Contributing"
|
|
||||||
canonical="https://tuono.dev/documentation/contributing"
|
|
||||||
description="The project is massive - if you like it, do consider contributing!"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
The project is massive - if you like it, do consider contributing!
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
The `tuono` project can mostly be split into the following subdomains:
|
|
||||||
|
|
||||||
- The CLI
|
|
||||||
- The Rust backend
|
|
||||||
- The React frontend
|
|
||||||
- The documentation website (which is written with tuono 🚀)
|
|
||||||
|
|
||||||
To check what the knowledge requirements are for each domain, check the
|
|
||||||
[requirements](#requirements) section below.
|
|
||||||
|
|
||||||
Currently, I'm keeping a private dashboard to prioritize new features and bug fixes, but if you
|
|
||||||
want to propose something, please open a new issue on Github or reach out to me using
|
|
||||||
my email address [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it). I'm also available
|
|
||||||
on Twitter (X) DMs `@valerioageno`, [Linkedin](https://www.linkedin.com/in/valerioageno)
|
|
||||||
and discord `@__v__v__`.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
It's not strictly required to know both React (& typescript) and Rust (even though it
|
|
||||||
would be a great nice to have).
|
|
||||||
|
|
||||||
Without taking into account specific cases, we can mostly split the domain requirements by:
|
|
||||||
|
|
||||||
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
|
|
||||||
- The Backend needs just `Rust`
|
|
||||||
- The Frontend needs just `React` & `Typescript`
|
|
||||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
|
||||||
code is markdown).
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import MetaTags from '../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Documentation"
|
|
||||||
canonical="https://tuono.dev/documentation"
|
|
||||||
description="Tuono is a full-stack framework for building React applications using Rust as the backend."
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs from '../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs />
|
|
||||||
|
|
||||||
# Tuono
|
|
||||||
|
|
||||||
Tuono is a full-stack framework for building React applications using Rust as the
|
|
||||||
backend.
|
|
||||||
|
|
||||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/). Why Tuono? Just a badass name.
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)
|
|
||||||
|
|
||||||
Tuono is a full-stack React framework with the server side written in Rust.
|
|
||||||
Because of this, Tuono is extremely fast and, the requests are handled by a multithreaded Rust server.
|
|
||||||
React is still React - it is just superpowered.
|
|
||||||
|
|
||||||
Rust is a hard language, then writing server side code is hard as well, right? (no again)
|
|
||||||
|
|
||||||
Tuono provides a collection of utilities to handle the server side code seamlessly with
|
|
||||||
the React code. Each server side route is managed in a separate file alongside the React route.
|
|
||||||
The routing is handled by Tuono based on the files defined within the `./src/routes` directory.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
Some of its features include:
|
|
||||||
|
|
||||||
- 🟦 Full typescript support
|
|
||||||
- 🌐 File system based routing
|
|
||||||
- 🔥 Hot Module Reload
|
|
||||||
- 🍭 SCSS/CSS modules
|
|
||||||
- 🧬 Server Side Rendering
|
|
||||||
- 🧵 Multi-threaded backend
|
|
||||||
- ⌨️ MDX support
|
|
||||||
- ⚙️ Build optimizations
|
|
||||||
- Custom APIs\*
|
|
||||||
- Image optimization\*
|
|
||||||
- Server streamed content\*
|
|
||||||
|
|
||||||
> \*development in progress
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
|
|
||||||
|
|
||||||
import MetaTags from '../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Installation"
|
|
||||||
canonical="https://tuono.dev/documentation/installation"
|
|
||||||
description="Tuono is a development environment built in rust and typescript that outputs a website written in both the languages"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Installation' }]} />
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
`Tuono` is a development environment built in rust and typescript that outputs a website written in both the
|
|
||||||
languages. Since then, you need the following tools installed on your computer to work with it:
|
|
||||||
|
|
||||||
- `Rust` - The Rust programming language tool chain (Go [here](https://rustup.rs/) for installing both `rust` and `cargo`)
|
|
||||||
- `Cargo` - The rust package manager
|
|
||||||
- `NodeJs` - The JavaScript runtime (Go [here](https://nodejs.org/en/download/package-manager) for installing both `nodejs` and `npm`)
|
|
||||||
- `npm` or `yarn` or `pnpm` - A javaScript package manager
|
|
||||||
|
|
||||||
> Node.js is needed just for the development environment. The final output will run on a Rust server.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
The tuono `CLI` is hosted on [crates.io](https://crates.io/crates/tuono) to download and install it, just run it on a terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install tuono
|
|
||||||
```
|
|
||||||
|
|
||||||
To check that it is correctly installed, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tuono --version
|
|
||||||
```
|
|
||||||
|
|
||||||
Run `tuono -h` to see all the available commands.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
The React/Rust full-stack framework
|
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
dev Start the development environment
|
|
||||||
build Build the production assets
|
|
||||||
new Scaffold a new project
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version
|
|
||||||
```
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Routing"
|
|
||||||
canonical="https://tuono.dev/documentation/routing/intro"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
|
|
||||||
|
|
||||||
# Routing index
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Routing"
|
|
||||||
canonical="https://tuono.dev/documentation/routing"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Routing', href: '/documentation/routing' },
|
|
||||||
{ label: 'Project structure' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Project structure
|
|
||||||
|
|
||||||
Todo
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - API fetching"
|
|
||||||
canonical="https://tuono.dev/documentation/tutorial/api-fetching"
|
|
||||||
description="Learn how to fetch remote APIs on the backend"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
|
||||||
{ label: 'API fetching' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# API fetching
|
|
||||||
|
|
||||||
The goal is to use the [PokeAPI](https://pokeapi.co/docs/v2) to list all the Pokémon of the first generation (the best one, btw) and then reserve a dynamic page for each one separately.
|
|
||||||
|
|
||||||
## Fetch all the Pokémon
|
|
||||||
|
|
||||||
To start, let’s fetch all of them from the root page. Since we want to render them on the server side, we are going to need to implement the logic in the `index.rs` file.
|
|
||||||
|
|
||||||
Clear the `index.rs` file and paste:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/routes/index.rs
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tuono_lib::reqwest::Client;
|
|
||||||
use tuono_lib::{Props, Request, Response};
|
|
||||||
|
|
||||||
const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151";
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Pokemons {
|
|
||||||
results: Vec<Pokemon>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Pokemon {
|
|
||||||
name: String,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tuono_lib::handler]
|
|
||||||
async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
|
||||||
return match fetch.get(ALL_POKEMON).send().await {
|
|
||||||
Ok(res) => {
|
|
||||||
let data = res.json::<Pokemons>().await.unwrap();
|
|
||||||
Response::Props(Props::new(data))
|
|
||||||
}
|
|
||||||
Err(_err) => Response::Props(Props::new("{}")),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the Pokémon are correctly fetched and hydrated on the client side, so we can actually use them. Clear the `index.tsx` file and paste:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// src/routes/index.tsx
|
|
||||||
import type { TuonoProps } from 'tuono'
|
|
||||||
|
|
||||||
interface Pokemon {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IndexProps {
|
|
||||||
results: Pokemon[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function IndexPage({
|
|
||||||
data,
|
|
||||||
}: TuonoProps<IndexProps>): JSX.Element {
|
|
||||||
if (!data?.results) {
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header className="header">
|
|
||||||
<a href="https://crates.io/crates/tuono" target="_blank">
|
|
||||||
Crates
|
|
||||||
</a>
|
|
||||||
<a href="https://www.npmjs.com/package/tuono" target="_blank">
|
|
||||||
Npm
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
<div className="title-wrap">
|
|
||||||
<h1 className="title">
|
|
||||||
TU<span>O</span>NO
|
|
||||||
</h1>
|
|
||||||
<div className="logo">
|
|
||||||
<img src="rust.svg" className="rust" />
|
|
||||||
<img src="react.svg" className="react" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul style={{ flexWrap: 'wrap', display: 'flex', gap: 10 }}>
|
|
||||||
{data.results.map((pokemon) => {
|
|
||||||
return pokemon.name
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Refresh the browser now! A bit ugly, but all the Pokémon are finally printed on screen!
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
prev={{
|
|
||||||
title: 'Development setup',
|
|
||||||
href: '/documentation/tutorial/development-setup',
|
|
||||||
}}
|
|
||||||
next={{ title: 'Components', href: '/documentation/tutorial/components' }}
|
|
||||||
/>
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Components"
|
|
||||||
canonical="https://tuono.dev/documentation/tutorial/components"
|
|
||||||
description="Learn how to manage the components in a Tuono codebase"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
|
||||||
{ label: 'Components' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Components
|
|
||||||
|
|
||||||
## Creating a stand-alone component
|
|
||||||
|
|
||||||
Let’s then create the button needed for displaying the list of Pokémon.
|
|
||||||
|
|
||||||
Create the following file `src/components/PokemonLink.tsx` and fill the content with:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// src/components/PokemonLink.tsx
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
interface Pokemon {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PokemonLink({
|
|
||||||
pokemon,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
pokemon: Pokemon
|
|
||||||
id: number
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Link href={`/pokemons/${pokemon.name}`}>
|
|
||||||
{pokemon.name}
|
|
||||||
<img
|
|
||||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now that the link is done, let’s import it into the `index.tsx` file
|
|
||||||
|
|
||||||
```diff
|
|
||||||
// src/routes/index.tsx
|
|
||||||
|
|
||||||
++ import PokemonLink from '../components/PokemonLink'
|
|
||||||
|
|
||||||
// ...
|
|
||||||
<ul style={{ flexWrap: "wrap", display: "flex", gap: 10 }}>
|
|
||||||
-- {pokemons.map((pokemon) => {
|
|
||||||
-- return pokemon.name;
|
|
||||||
++ {pokemons.map((pokemon, i) => {
|
|
||||||
++ return <PokemonLink pokemon={pokemon} id={i + 1} key={i} />;
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the links work. Clicking on any of them, we get redirected to the 404 page because we haven’t yet implemented the `pokemons/[pokemon]` page.
|
|
||||||
As previously said, CSS modules are enabled out of the box, so let’s make those links a little bit nicer.
|
|
||||||
|
|
||||||
Create alongside the `PokemonLink.tsx` component the CSS module `PokemonLink.module.css` and copy the following content into it:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* src/components/PokemonLink.module.css */
|
|
||||||
|
|
||||||
.link {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 216px;
|
|
||||||
position: relative;
|
|
||||||
background: white;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: solid #f0f0f0 1px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
padding: 5px 5px 5px 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
transition: 0.2s;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link:hover {
|
|
||||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link img {
|
|
||||||
width: 70px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> 💡 SASS is supported out of the box. Just install the processor in the devDependencies `npm i -D sass` and run again `tuono dev`
|
|
||||||
|
|
||||||
Then import the styles into the `PokemonLink` component as following:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
// src/components/PokemonLink.tsx
|
|
||||||
import { Link } from "tuono";
|
|
||||||
import type { Pokemon } from "./../types/pokemon";
|
|
||||||
++ import styles from './PokemonLink.module.css'
|
|
||||||
|
|
||||||
export default function PokemonLink({
|
|
||||||
pokemon,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
pokemon: Pokemon;
|
|
||||||
id: number;
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
-- <Link href={`/pokemons/${pokemon.name}`}>
|
|
||||||
++ <Link className={styles.link} href={`/pokemons/${pokemon.name}`}>
|
|
||||||
{pokemon.name}
|
|
||||||
<img
|
|
||||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
prev={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
|
|
||||||
next={{
|
|
||||||
title: 'Dynamic routes',
|
|
||||||
href: '/documentation/tutorial/dynamic-routes',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Conclusion"
|
|
||||||
canonical="https://tuono.dev/documentation/tutorial/conclusion"
|
|
||||||
description="You just created a multi thread full stack application with Rust and React"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
|
||||||
{ label: 'Conclusion' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Conclusion
|
|
||||||
|
|
||||||
That’s it! You just created a multi thread full stack application with Rust and React.
|
|
||||||
|
|
||||||
The project is still under heavy development and many features are not ready yet, but
|
|
||||||
I hope you got the taste of what is like working with rust and react in the same stack.
|
|
||||||
|
|
||||||
As I mentioned in the introduction, I'd love to hear what you thought about the framework and the tutorial - feel free to reach me
|
|
||||||
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
|
|
||||||
|
|
||||||
Ciao
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
prev={{
|
|
||||||
title: 'Production build',
|
|
||||||
href: '/documentation/tutorial/production',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Development setup"
|
|
||||||
canonical="https://tuono.dev/documentation/tutorial/development-setup"
|
|
||||||
description="Learn how to setup a new Tuono project"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
|
||||||
{ label: 'Development setup' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Development setup
|
|
||||||
|
|
||||||
## Scaffold a new project
|
|
||||||
|
|
||||||
To set up a new fresh project you just need to run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ tuono new tuono-tutorial
|
|
||||||
```
|
|
||||||
|
|
||||||
Get into the project folder and install the dependencies with:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Open it with your favorite code editor.
|
|
||||||
|
|
||||||
The project will have the following structure:
|
|
||||||
|
|
||||||
```
|
|
||||||
├── package.json
|
|
||||||
├── public
|
|
||||||
├── src
|
|
||||||
│ ├── routes
|
|
||||||
│ └── styles
|
|
||||||
├── Cargo.toml
|
|
||||||
├── README.md
|
|
||||||
└── tsconfig.json
|
|
||||||
```
|
|
||||||
|
|
||||||
`public/`: put here all the files you want to be public
|
|
||||||
|
|
||||||
`src/routes/`: All the files in this folder are considered routes. All the routes are server side rendered out of the box.
|
|
||||||
To add server side capabilities, just create a Rust file with the same name as the route (i.e. `about.tsx` → `about.rs`).
|
|
||||||
|
|
||||||
`src/styles/`: In this folder there is the `global.css` file that stores all the global styles. For the rest of the project, you can use
|
|
||||||
CSS modules.
|
|
||||||
|
|
||||||
## Start the dev environment
|
|
||||||
|
|
||||||
To start the development environment, you just need to run the following command within the project folder:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ tuono dev
|
|
||||||
```
|
|
||||||
|
|
||||||
The first time might take a little bit because it will install all the Rust’s dependencies. All the other execution will be pretty quick!
|
|
||||||
|
|
||||||
> 💡 The tuono dev development script is currently under strong optimization improvements.
|
|
||||||
> In case you face any error delete the cache `.tuono` folder and run it again!
|
|
||||||
|
|
||||||
Then open [http://localhost:3000/](http://localhost:3000) on the browser.
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
prev={{ title: 'Tutorial', href: '/documentation/tutorial' }}
|
|
||||||
next={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
|
|
||||||
/>
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import MetaTags from '../../../components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Dynamic routes"
|
|
||||||
canonical="https://tuono.dev/documentation/tutorial/dynamic-routes"
|
|
||||||
description="Learn how to catch more endpoint with the same route loading dynamic data"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
|
||||||
{ label: 'Dynamic routes' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Dynamic routes
|
|
||||||
|
|
||||||
## Create the Pokémon route
|
|
||||||
|
|
||||||
The homepage is ready. We have the full list of Pokémon and they are all links. Great!
|
|
||||||
|
|
||||||
Now we want to make those links actually point to a real page. Let’s create the \_\_ route.
|
|
||||||
|
|
||||||
Create the folder `routes/pokemons` and then create the two files `[pokemon].tsx` and `[pokemon].rs`.
|
|
||||||
|
|
||||||
These two will handle every request that points to `http://localhost:3000/pokemons/bulbasaur..mew`.
|
|
||||||
|
|
||||||
Let’s first work on the server side file. Paste into the new `[pokemon].rs` file the following code:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/routes/pokemons/[pokemon].rs
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tuono_lib::reqwest::Client;
|
|
||||||
use tuono_lib::{Props, Request, Response};
|
|
||||||
|
|
||||||
const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Pokemon {
|
|
||||||
name: String,
|
|
||||||
id: u16,
|
|
||||||
weight: u16,
|
|
||||||
height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tuono_lib::handler]
|
|
||||||
async fn get_pokemon(req: Request, fetch: Client) -> Response {
|
|
||||||
// The param `pokemon` is defined in the route filename [pokemon].rs
|
|
||||||
let pokemon = req.params.get("pokemon").unwrap();
|
|
||||||
|
|
||||||
return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
|
|
||||||
Ok(res) => {
|
|
||||||
let data = res.json::<Pokemon>().await.unwrap();
|
|
||||||
Response::Props(Props::new(data))
|
|
||||||
}
|
|
||||||
Err(_err) => Response::Props(Props::new("{}"))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then let’s work on the frontend. Paste into the `[pokemon].tsx` file the following code:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { TuonoProps } from 'tuono'
|
|
||||||
import PokemonView from '../../components/PokemonView'
|
|
||||||
|
|
||||||
export default function Pokemon({ data }: TuonoProps): JSX.Element {
|
|
||||||
return <PokemonView pokemon={data} />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The browser should complain that the component `PokemonView` does not exist. Let’s create it then!
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// components/PokemonView.tsx
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
import styles from './PokemonView.module.css'
|
|
||||||
|
|
||||||
interface Pokemon {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
weight: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PokemonView({
|
|
||||||
pokemon,
|
|
||||||
}: {
|
|
||||||
pokemon?: Pokemon
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Link className={styles['back-btn']} href="/">
|
|
||||||
Back
|
|
||||||
</Link>
|
|
||||||
{pokemon?.name && (
|
|
||||||
<div className={styles.pokemon}>
|
|
||||||
<div>
|
|
||||||
<h1 className={styles.name}>{pokemon.name}</h1>
|
|
||||||
<dl className={styles.spec}>
|
|
||||||
<dt className={styles.label}>Weight:</dt>
|
|
||||||
<dd>{pokemon.weight}lbs</dd>
|
|
||||||
</dl>
|
|
||||||
<dl className={styles.spec}>
|
|
||||||
<dt className={styles.label}>Height:</dt>
|
|
||||||
<dd>{pokemon.height}ft</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${pokemon.id}.png`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* components/PokemonView.module.css */
|
|
||||||
.back-btn {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 7px 15px;
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
border: solid #f0f0f0 1px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn:hover {
|
|
||||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pokemon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
font-size: 50px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pokemon img {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spec {
|
|
||||||
display: flex;
|
|
||||||
font-size: 18px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
prev={{ title: 'Components', href: '/documentation/tutorial/components' }}
|
|
||||||
next={{
|
|
||||||
title: 'Error handling',
|
|
||||||
href: '/documentation/tutorial/error-handling',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||