diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index dfdd0e3..48cfaa8 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -28,7 +28,7 @@ jobs:
TEMPLATE_NAME=$(basename "$dir")
COMPOSE_FILE="$dir/docker-compose.yml"
- TEMPLATE_FILE="$dir/template.yml"
+ TEMPLATE_FILE="$dir/template.toml"
if [ ! -f "$COMPOSE_FILE" ]; then
echo "โ Missing docker-compose.yml in $TEMPLATE_NAME"
@@ -36,7 +36,7 @@ jobs:
fi
if [ ! -f "$TEMPLATE_FILE" ]; then
- echo "โ Missing template.yml in $TEMPLATE_NAME"
+ echo "โ Missing template.toml in $TEMPLATE_NAME"
ERROR=1
fi
fi
@@ -49,6 +49,82 @@ jobs:
echo "โ
Blueprint folders validated successfully."
fi
+ - name: Validate meta.json structure and required fields
+ run: |
+ echo "๐ Validating meta.json structure and required fields..."
+
+ # First check if meta.json exists and is valid JSON
+ if [ ! -f "meta.json" ]; then
+ echo "โ meta.json file not found"
+ exit 1
+ fi
+
+ if ! jq empty meta.json 2>/dev/null; then
+ echo "โ meta.json is not a valid JSON file"
+ exit 1
+ fi
+
+ ERROR=0
+ ERRORS_FOUND=""
+
+ # Debug: Show total number of entries
+ TOTAL_ENTRIES=$(jq '. | length' meta.json)
+ echo "๐ Total entries in meta.json: $TOTAL_ENTRIES"
+
+ # Get all entries at once and process them
+ TOTAL_INDEX=$(($TOTAL_ENTRIES - 1))
+
+ for i in $(seq 0 $TOTAL_INDEX); do
+ entry=$(jq -c ".[$i]" meta.json)
+ INDEX=$((i + 1))
+
+ echo "-------------------------------------------"
+ echo "๐ Checking entry #$INDEX..."
+
+ # Get the ID for better error reporting
+ ID=$(echo "$entry" | jq -r '.id // "UNKNOWN"')
+ echo "๐ Processing entry with ID: $ID"
+
+ # Validate required top-level fields
+ for field in "id" "name" "version" "description" "logo" "links" "tags"; do
+ if [ "$(echo "$entry" | jq "has(\"$field\")")" != "true" ]; then
+ ERROR_MSG="โ Entry #$INDEX (ID: $ID) is missing required field: $field"
+ echo "$ERROR_MSG"
+ ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
+ ERROR=1
+ fi
+ done
+
+ # Validate links object required fields
+ if [ "$(echo "$entry" | jq 'has("links")')" == "true" ]; then
+ for link_field in "github" "website" "docs"; do
+ if [ "$(echo "$entry" | jq ".links | has(\"$link_field\")")" != "true" ]; then
+ ERROR_MSG="โ Entry #$INDEX (ID: $ID): links object is missing required field: $link_field"
+ echo "$ERROR_MSG"
+ ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
+ ERROR=1
+ fi
+ done
+ fi
+
+ # Validate tags array is not empty
+ if [ "$(echo "$entry" | jq '.tags | length')" -eq 0 ]; then
+ ERROR_MSG="โ Entry #$INDEX (ID: $ID): tags array cannot be empty"
+ echo "$ERROR_MSG"
+ ERRORS_FOUND="${ERRORS_FOUND}${ERROR_MSG}\n"
+ ERROR=1
+ fi
+ done
+
+ echo "-------------------------------------------"
+ if [ $ERROR -eq 1 ]; then
+ echo "โ meta.json structure validation failed."
+ echo -e "Summary of all errors found:${ERRORS_FOUND}"
+ exit 1
+ else
+ echo "โ
meta.json structure validated successfully."
+ fi
+
- name: Validate meta.json matches blueprint folders and logo files
run: |
echo "๐ Validating meta.json against blueprint folders and logos..."
diff --git a/README.md b/README.md
index 10ecec7..fcb2886 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ This is the official repository for the Dokploy Open Source Templates.
1. Fork the repository
2. Create a new branch
-3. Add the template to the `blueprints` folder (docker-compose.yml, template.yml)
+3. Add the template to the `blueprints` folder (`docker-compose.yml`, `template.toml`)
4. Add the template metadata (name, description, version, logo, links, tags) to the `meta.json` file
5. Add the logo to the template folder
6. Commit and push your changes
@@ -45,19 +45,22 @@ services:
volumes:
grafana-storage: {}
```
-3. Add the `template.yml` file to the folder, this is where we specify the domains, mounts and env variables, to understand more the structure of `template.yml` you can read here [Template.yml structure](#templateyml-structure)
+3. Add the `template.toml` file to the folder, this is where we specify the domains, mounts and env variables, to understand more the structure of `template.toml` you can read here [Template.toml structure](#template.toml-structure)
-```yaml
-variables:
- main_domain: ${domain}
+```toml
+[variables]
+main_domain = "${domain}"
-config:
- domains:
- - serviceName: grafana
- port: 3000
- host: ${main_domain}
- env: []
- mounts: []
+[config]
+[[config.domains]]
+serviceName = "grafana"
+port = 3000
+host = "${main_domain}"
+
+
+[config.env]
+
+[[config.mounts]]
```
4. Add meta information to the `meta.json` file in the root folder
@@ -82,51 +85,53 @@ config:
6. Commit and push your changes
7. Create a pull request
-### Template.yml structure
-
-Dokploy use a defined structure for the `template.yml` file, we have 4 sections available:
+### Template.toml structure
+Dokploy use a defined structure for the `template.toml` file, we have 4 sections available:
1. `variables`: This is where we define the variables that will be used in the `domains`, `env` and `mounts` sections.
2. `domains`: This is where we define the configuration for the template.
3. `env`: This is where we define the environment variables for the template.
4. `mounts`: This is where we define the mounts for the template.
-
- The `variables(Optional)` structure is the following:
-```yaml
-variables:
- main_domain: ${domain}
- my_domain: https://my-domain.com
- my_password: ${password:32}
- any_helper: ${you-can-use-any-helper}
+```toml
+[variables]
+main_domain = "${domain}"
+my_domain = "https://my-domain.com"
+my_password = "${password:32}"
+any_helper = "${you-can-use-any-helper}"
```
- The `config` structure is the following:
-```yaml
-config:
- domains: # Optional
- - serviceName: grafana # Required
- port: 3000 # Required
- host: ${main_domain} # Required
- path: / # Optional
+```toml
+[config]
+# Optional sections below
- env: # Optional
- - AP_HOST=${main_domain}
- - AP_API_KEY=${api_key}
- - AP_ENCRYPTION_KEY=${encryption_key}
- - AP_JWT_SECRET=${jwt_secret}
- - AP_POSTGRES_PASSWORD=${postgres_password}
+[[config.domains]]
+serviceName = "grafana" # Required
+port = 3000 # Required
+host = "${main_domain}" # Required
+path = "/" # Optional
- mounts: # Optional or []
- - filePath: /content/file.txt
- content: |
- My content
+env = [
+ "AP_HOST=${main_domain}",
+ "AP_API_KEY=${api_key}",
+ "AP_ENCRYPTION_KEY=${encryption_key}",
+ "AP_JWT_SECRET=${jwt_secret}",
+ "AP_POSTGRES_PASSWORD=${postgres_password}"
+]
+
+[[config.mounts]]
+filePath = "/content/file.txt"
+content = """
+My content
+"""
```
-Important: you can reference any variable in the `domains`, `env` and `mounts` sections. just use the `${variable_name}` syntax, in the case you don't want to define a variable, you can use the `domain`, `base64`, `password`, `hash`, `uuid`, `randomPort` or `timestamp` helpers.
+Important: you can reference any variable in the `domains`, `env` and `mounts` sections. just use the `${variable_name}` syntax, in the case you don't want to define a variable, you can use the `domain`, `base64`, `password`, `hash`, `uuid`, `randomPort`, `timestamp`, `jwt`, `email`, or `username` helpers.
### Helpers
@@ -140,6 +145,8 @@ We have a few helpers that are very common when creating a template, these are:
- `randomPort`: This is a helper that will generate a random port for the template.
- `timestamp`: This is a helper that will generate a timestamp.
- `jwt or jwt:length`: This is a helper that will generate a jwt for the template.
+- `email`: This is a helper that will generate a random email for the template.
+- `username`: This is a helper that will generate a random username in lowercase for the template.
diff --git a/app/package.json b/app/package.json
index a0f490f..49503ad 100644
--- a/app/package.json
+++ b/app/package.json
@@ -16,6 +16,7 @@
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "6.4.0",
"@codemirror/view": "6.29.0",
+ "@iarna/toml": "^2.2.5",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
@@ -33,11 +34,13 @@
"next-themes": "^0.4.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-router-dom": "^7.4.1",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.12",
"tailwindcss-animate": "^1.0.7",
"vite-plugin-static-copy": "2.3.0",
+ "yaml": "2.7.1",
"zustand": "^5.0.3"
},
"devDependencies": {
diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml
index 3669f77..9fec5d4 100644
--- a/app/pnpm-lock.yaml
+++ b/app/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@codemirror/view':
specifier: 6.29.0
version: 6.29.0
+ '@iarna/toml':
+ specifier: ^2.2.5
+ version: 2.2.5
'@radix-ui/react-dialog':
specifier: ^1.1.6
version: 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -46,7 +49,7 @@ importers:
version: 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tailwindcss/vite':
specifier: ^4.0.12
- version: 4.0.12(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2))
+ version: 4.0.12(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1))
'@uiw/codemirror-theme-github':
specifier: ^4.22.1
version: 4.23.10(@codemirror/language@6.10.8)(@codemirror/state@6.5.2)(@codemirror/view@6.29.0)
@@ -77,6 +80,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
+ react-router-dom:
+ specifier: ^7.4.1
+ version: 7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
sonner:
specifier: ^2.0.1
version: 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -91,7 +97,10 @@ importers:
version: 1.0.7(tailwindcss@4.0.12)
vite-plugin-static-copy:
specifier: 2.3.0
- version: 2.3.0(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2))
+ version: 2.3.0(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1))
+ yaml:
+ specifier: 2.7.1
+ version: 2.7.1
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@19.0.10)(react@19.0.0)
@@ -107,7 +116,7 @@ importers:
version: 19.0.4(@types/react@19.0.10)
'@vitejs/plugin-react':
specifier: ^4.3.4
- version: 4.3.4(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2))
+ version: 4.3.4(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1))
globals:
specifier: ^15.15.0
version: 15.15.0
@@ -116,7 +125,7 @@ importers:
version: 5.7.3
vite:
specifier: ^6.2.0
- version: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)
+ version: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1)
packages:
@@ -408,6 +417,9 @@ packages:
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+ '@iarna/toml@2.2.5':
+ resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
+
'@jridgewell/gen-mapping@0.3.8':
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
@@ -1112,6 +1124,9 @@ packages:
'@types/babel__traverse@7.20.6':
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
+ '@types/cookie@0.6.0':
+ resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
@@ -1211,6 +1226,10 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@@ -1489,6 +1508,23 @@ packages:
'@types/react':
optional: true
+ react-router-dom@7.4.1:
+ resolution: {integrity: sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.4.1:
+ resolution: {integrity: sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -1529,6 +1565,9 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
+ set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+
sonner@2.0.1:
resolution: {integrity: sha512-FRBphaehZ5tLdLcQ8g2WOIRE+Y7BCfWi5Zyd8bCvBjiW8TxxAyoWZIxS661Yz6TGPqFQ4VLzOF89WEYhfynSFQ==}
peerDependencies:
@@ -1567,6 +1606,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ turbo-stream@2.4.0:
+ resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
+
typescript@5.7.3:
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'}
@@ -1657,6 +1699,11 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yaml@2.7.1:
+ resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
+ engines: {node: '>= 14'}
+ hasBin: true
+
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
@@ -1965,6 +2012,8 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
+ '@iarna/toml@2.2.5': {}
+
'@jridgewell/gen-mapping@0.3.8':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -2581,13 +2630,13 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.0.12
'@tailwindcss/oxide-win32-x64-msvc': 4.0.12
- '@tailwindcss/vite@4.0.12(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2))':
+ '@tailwindcss/vite@4.0.12(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1))':
dependencies:
'@tailwindcss/node': 4.0.12
'@tailwindcss/oxide': 4.0.12
lightningcss: 1.29.2
tailwindcss: 4.0.12
- vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)
+ vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1)
'@types/babel__core@7.20.5':
dependencies:
@@ -2610,6 +2659,8 @@ snapshots:
dependencies:
'@babel/types': 7.26.9
+ '@types/cookie@0.6.0': {}
+
'@types/estree@1.0.6': {}
'@types/node@20.17.24':
@@ -2665,14 +2716,14 @@ snapshots:
- '@codemirror/lint'
- '@codemirror/search'
- '@vitejs/plugin-react@4.3.4(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2))':
+ '@vitejs/plugin-react@4.3.4(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.26.9
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
- vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)
+ vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1)
transitivePeerDependencies:
- supports-color
@@ -2740,6 +2791,8 @@ snapshots:
convert-source-map@2.0.0: {}
+ cookie@1.0.2: {}
+
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
@@ -2982,6 +3035,22 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.10
+ react-router-dom@7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ react-router: 7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+
+ react-router@7.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@types/cookie': 0.6.0
+ cookie: 1.0.2
+ react: 19.0.0
+ set-cookie-parser: 2.7.1
+ turbo-stream: 2.4.0
+ optionalDependencies:
+ react-dom: 19.0.0(react@19.0.0)
+
react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0):
dependencies:
get-nonce: 1.0.1
@@ -3033,6 +3102,8 @@ snapshots:
semver@6.3.1: {}
+ set-cookie-parser@2.7.1: {}
+
sonner@2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
react: 19.0.0
@@ -3060,6 +3131,8 @@ snapshots:
tslib@2.8.1: {}
+ turbo-stream@2.4.0: {}
+
typescript@5.7.3: {}
undici-types@6.19.8: {}
@@ -3087,16 +3160,16 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.10
- vite-plugin-static-copy@2.3.0(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)):
+ vite-plugin-static-copy@2.3.0(vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1)):
dependencies:
chokidar: 3.6.0
fast-glob: 3.3.3
fs-extra: 11.3.0
p-map: 7.0.3
picocolors: 1.1.1
- vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)
+ vite: 6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1)
- vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2):
+ vite@6.2.1(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.7.1):
dependencies:
esbuild: 0.25.1
postcss: 8.5.3
@@ -3106,11 +3179,14 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.29.2
+ yaml: 2.7.1
w3c-keyname@2.2.8: {}
yallist@3.1.1: {}
+ yaml@2.7.1: {}
+
zustand@5.0.3(@types/react@19.0.10)(react@19.0.0):
optionalDependencies:
'@types/react': 19.0.10
diff --git a/app/script.js b/app/script.js
new file mode 100644
index 0000000..fd64000
--- /dev/null
+++ b/app/script.js
@@ -0,0 +1,36 @@
+import yaml from "yaml";
+import toml from "@iarna/toml";
+import fs from "fs";
+import path from "path";
+import { fileURLToPath } from "url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+function convertYamlToToml(yamlContent) {
+ const parsedYaml = yaml.parse(yamlContent);
+ return toml.stringify(parsedYaml);
+}
+
+function processDirectory(dirPath) {
+ const files = fs.readdirSync(dirPath);
+
+ files.forEach((file) => {
+ const filePath = path.join(dirPath, file);
+ const stat = fs.statSync(filePath);
+
+ if (stat.isDirectory()) {
+ processDirectory(filePath);
+ } else if (file === "template.yml") {
+ console.log(`Converting ${filePath}`);
+ const yamlContent = fs.readFileSync(filePath, "utf8");
+ const tomlContent = convertYamlToToml(yamlContent);
+ const tomlPath = path.join(dirPath, "template.toml");
+ fs.writeFileSync(tomlPath, tomlContent);
+ }
+ });
+}
+
+// Ruta al directorio blueprints relativa al script
+const blueprintsPath = path.join(__dirname, "..", "blueprints");
+processDirectory(blueprintsPath);
diff --git a/app/src/App.tsx b/app/src/App.tsx
index f6c69ec..9864c07 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -3,16 +3,19 @@ import Navigation from "./components/Navigation";
import Search from "./components/Search";
import { useStore } from "@/store";
import "./App.css";
+import { BrowserRouter } from "react-router-dom";
function App() {
const view = useStore((state) => state.view);
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/app/src/components/Search.tsx b/app/src/components/Search.tsx
index a114c19..a810923 100644
--- a/app/src/components/Search.tsx
+++ b/app/src/components/Search.tsx
@@ -16,9 +16,10 @@ import { Check, ChevronsUpDown } from "lucide-react";
import React from "react";
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
import SelectedTags from "./SelectedTags";
+import { useSearchParams } from "react-router-dom";
const Search = () => {
- const { templates, searchQuery, setSearchQuery, setView, templatesCount } =
+ const { templates, searchQuery, setSearchQuery, setView, templatesCount, setFilteredTemplates, setTemplatesCount } =
useStore();
const selectedTags = useStore((state) => state.selectedTags);
const addSelectedTag = useStore((state) => state.addSelectedTag);
@@ -26,6 +27,7 @@ const Search = () => {
const [open, setOpen] = React.useState(false);
const [tagSearch, setTagSearch] = React.useState("");
const view = useStore((state) => state.view);
+ const [searchParams, setSearchParams] = useSearchParams();
// Get all unique tags, safely handle empty templates
const uniqueTags = React.useMemo(() => {
@@ -43,6 +45,53 @@ const Search = () => {
);
}, [uniqueTags, tagSearch]);
+ // Initialize search query from URL params and apply filters
+ React.useEffect(() => {
+ const queryFromUrl = searchParams.get("q") || "";
+ if (queryFromUrl !== searchQuery) {
+ setSearchQuery(queryFromUrl);
+ }
+
+ // Apply filters whenever templates, search query or selected tags change
+ if (templates) {
+ const filtered = templates.filter((template) => {
+ // Filter by search query
+ const matchesSearch = template.name
+ .toLowerCase()
+ .includes(queryFromUrl.toLowerCase());
+
+ // Filter by selected tags
+ const matchesTags =
+ selectedTags.length === 0 ||
+ selectedTags.every((tag) => template.tags.includes(tag));
+
+ return matchesSearch && matchesTags;
+ });
+
+ setFilteredTemplates(filtered);
+ setTemplatesCount(filtered.length);
+ }
+ }, [searchParams, templates, selectedTags, setSearchQuery, setFilteredTemplates, setTemplatesCount]);
+
+ // Update URL params when search query changes
+ const handleSearchChange = (e: React.ChangeEvent) => {
+ const newQuery = e.target.value;
+ setSearchQuery(newQuery);
+ if (newQuery) {
+ setSearchParams({ q: newQuery });
+ } else {
+ searchParams.delete("q");
+ setSearchParams(searchParams);
+ }
+ };
+
+ // Clear search and URL params
+ const handleClearSearch = () => {
+ setSearchQuery("");
+ searchParams.delete("q");
+ setSearchParams(searchParams);
+ };
+
return (
{/*
@@ -63,11 +112,11 @@ const Search = () => {
type="text"
placeholder="Search templates..."
value={searchQuery}
- onChange={(e) => setSearchQuery(e.target.value)}
+ onChange={handleSearchChange}
className="w-full p-6"
/>
{searchQuery.length > 0 ? (
- setSearchQuery("")}>
+
) : (
diff --git a/app/src/components/TemplateDialog.tsx b/app/src/components/TemplateDialog.tsx
index af7fd03..89e0893 100644
--- a/app/src/components/TemplateDialog.tsx
+++ b/app/src/components/TemplateDialog.tsx
@@ -228,7 +228,7 @@ const TemplateDialog: React.FC
= ({