diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 4d6c6979..ca289621 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -21,7 +21,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 10 run_install: false - name: Setup Node.js diff --git a/README.md b/README.md index 34a6e104..8f27a045 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ ### πŸ“… CalVer Versioning -Starting with version **26.4.0**, Dynamia Platform adopts **Calendar Versioning (CalVer)** with the format `YY.MM.MINOR`. This means: +Starting with version **26.4.1**, Dynamia Platform adopts **Calendar Versioning (CalVer)** with the format `YY.MM.MINOR`. This means: - **All modules share the same version**: Core, extensions, starters, themesβ€”everything is released together -- **26.4.0** = First release of February 2026 (Year 26, Month 02, Release 0) +- **26.4.1** = First release of February 2026 (Year 26, Month 02, Release 0) - **26.2.1** = Second release of February 2026 - **26.3.0** = First release of March 2026 - **Unified releases** ensure compatibility and simplify dependency management - No more version mismatches between platform components! **Examples**: -- `26.4.0` β†’ February 2026, first release +- `26.4.1` β†’ February 2026, first release - `26.2.1` β†’ February 2026, second release (hotfix or minor update) - `26.12.3` β†’ December 2026, fourth release @@ -230,19 +230,19 @@ Enterprise authentication and authorization: tools.dynamia tools.dynamia.app - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 ``` @@ -250,9 +250,9 @@ Enterprise authentication and authorization: **Gradle** (`build.gradle`) ```groovy dependencies { - implementation 'tools.dynamia:tools.dynamia.app:26.4.0' - implementation 'tools.dynamia:tools.dynamia.zk:26.4.0' - implementation 'tools.dynamia:tools.dynamia.domain.jpa:26.4.0' + implementation 'tools.dynamia:tools.dynamia.app:26.4.1' + implementation 'tools.dynamia:tools.dynamia.zk:26.4.1' + implementation 'tools.dynamia:tools.dynamia.domain.jpa:26.4.1' } ``` @@ -291,65 +291,65 @@ Enterprise authentication and authorization: ### Adding Extensions -To use any of the built-in extensions, simply add their dependencies. **All extensions now share the same version (26.4.0)** thanks to unified CalVer: +To use any of the built-in extensions, simply add their dependencies. **All extensions now share the same version (26.4.1)** thanks to unified CalVer: ```xml tools.dynamia.modules tools.dynamia.modules.saas - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.email - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.entityfiles - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.entityfiles.s3 - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.dashboard - 26.4.0 + 26.4.1 tools.dynamia.reports tools.dynamia.reports.core - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.fileimporter - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.security - 26.4.0 + 26.4.1 ``` -> **πŸ’‘ Pro Tip**: With CalVer, all Dynamia Platform components use the same version. Just use `26.4.0` for everything! +> **πŸ’‘ Pro Tip**: With CalVer, all Dynamia Platform components use the same version. Just use `26.4.1` for everything! > **Note**: All artifacts are available on [Maven Central](https://search.maven.org/search?q=tools.dynamia) @@ -472,7 +472,7 @@ Java 11+ and ecosystem update: - πŸš€ **Spring Boot 4** - Next-gen Spring ecosystem - 🎨 **ZK 10+** - Modern web UI capabilities - πŸ”„ **Synchronized Releases** - Core, extensions, starters, and themes share the same version -- 🎯 **Simplified Dependencies** - One version to rule them all (e.g., 26.4.0 for February 2026) +- 🎯 **Simplified Dependencies** - One version to rule them all (e.g., 26.4.1 for February 2026) - ⚑ **Enhanced Performance** - Optimized for modern JVM and cloud environments - πŸ›‘οΈ **Production Hardened** - Battle-tested in enterprise environments diff --git a/bump-version.sh b/bump-version.sh new file mode 100644 index 00000000..c9ed217b --- /dev/null +++ b/bump-version.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +echo "πŸ“¦ Monorepo Version Bump (pnpm + Maven)" + +# Ask for version +read -p "πŸ‘‰ Enter the new version (e.g. 26.4.0): " VERSION + +# Basic validation +if [ -z "$VERSION" ]; then + echo "❌ Version cannot be empty" + exit 1 +fi + +echo "πŸš€ Updating pnpm workspace packages to $VERSION..." + +pnpm -r exec npm version "$VERSION" --no-git-tag-version + +echo "πŸ”„ Syncing internal dependencies (optional)..." + +if command -v syncpack &> /dev/null +then + pnpm -r exec syncpack set-version "$VERSION" +else + echo "⚠️ syncpack not found, skipping dependency sync" +fi + +# --- Maven part --- +if [ -f "pom.xml" ]; then + echo "β˜• Updating Maven project version to $VERSION..." + + mvn versions:set -DnewVersion="$VERSION" -DgenerateBackupPoms=false + + echo "πŸ”§ Ensuring child modules are updated..." + mvn versions:update-child-modules +else + echo "⚠️ No pom.xml found at root, skipping Maven step" +fi + +echo "βœ… Done. Everything is now at version $VERSION" \ No newline at end of file diff --git a/examples/demo-zk-books/pom.xml b/examples/demo-zk-books/pom.xml index 4a633acb..463365ad 100644 --- a/examples/demo-zk-books/pom.xml +++ b/examples/demo-zk-books/pom.xml @@ -47,7 +47,7 @@ ${maven.build.timestamp} yyyyMMdd - 26.3.2 + 26.4.0 diff --git a/extensions/dashboard/sources/pom.xml b/extensions/dashboard/sources/pom.xml index 525f2c89..323aac1c 100644 --- a/extensions/dashboard/sources/pom.xml +++ b/extensions/dashboard/sources/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml @@ -38,12 +38,12 @@ tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.saas.api - 26.4.0 + 26.4.1 diff --git a/extensions/email-sms/sources/core/pom.xml b/extensions/email-sms/sources/core/pom.xml index f4c600af..2002f6a3 100644 --- a/extensions/email-sms/sources/core/pom.xml +++ b/extensions/email-sms/sources/core/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules.email.parent tools.dynamia.modules - 26.4.0 + 26.4.1 tools.dynamia.modules.email @@ -50,12 +50,12 @@ tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.templates - 26.4.0 + 26.4.1 org.springframework diff --git a/extensions/email-sms/sources/pom.xml b/extensions/email-sms/sources/pom.xml index 2a1c5fa0..3c7ba5a6 100644 --- a/extensions/email-sms/sources/pom.xml +++ b/extensions/email-sms/sources/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml @@ -85,7 +85,7 @@ tools.dynamia.modules tools.dynamia.modules.saas.jpa - 26.4.0 + 26.4.1 diff --git a/extensions/email-sms/sources/ui/pom.xml b/extensions/email-sms/sources/ui/pom.xml index f275a54b..3971de5d 100644 --- a/extensions/email-sms/sources/ui/pom.xml +++ b/extensions/email-sms/sources/ui/pom.xml @@ -22,7 +22,7 @@ tools.dynamia.modules.email.parent tools.dynamia.modules - 26.4.0 + 26.4.1 DynamiaModules - Email UI @@ -34,12 +34,12 @@ tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.email - 26.4.0 + 26.4.1 tools.dynamia.zk.addons diff --git a/extensions/entity-files/packages/files-sdk/package.json b/extensions/entity-files/packages/files-sdk/package.json index 6d1e181c..0c707dc0 100644 --- a/extensions/entity-files/packages/files-sdk/package.json +++ b/extensions/entity-files/packages/files-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dynamia-tools/files-sdk", - "version": "26.3.2", + "version": "26.4.1", "website": "https://dynamia.tools", "description": "TypeScript/JavaScript client SDK for the Dynamia Entity Files extension REST API", "keywords": [ @@ -73,4 +73,3 @@ "@vitest/coverage-v8": "^3.0.0" } } - diff --git a/extensions/entity-files/sources/core/pom.xml b/extensions/entity-files/sources/core/pom.xml index 8242f69b..1027a48c 100644 --- a/extensions/entity-files/sources/core/pom.xml +++ b/extensions/entity-files/sources/core/pom.xml @@ -22,7 +22,7 @@ tools.dynamia.modules.entityfiles.parent tools.dynamia.modules - 26.4.0 + 26.4.1 DynamiaModules - EntityFiles - Core tools.dynamia.modules.entityfiles @@ -54,20 +54,20 @@ tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 jar tools.dynamia tools.dynamia.io - 26.4.0 + 26.4.1 jar tools.dynamia tools.dynamia.web - 26.4.0 + 26.4.1 jar diff --git a/extensions/entity-files/sources/pom.xml b/extensions/entity-files/sources/pom.xml index c03f0453..0d5e48ae 100644 --- a/extensions/entity-files/sources/pom.xml +++ b/extensions/entity-files/sources/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/entity-files/sources/s3/pom.xml b/extensions/entity-files/sources/s3/pom.xml index fdd35844..01444438 100644 --- a/extensions/entity-files/sources/s3/pom.xml +++ b/extensions/entity-files/sources/s3/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.entityfiles.parent - 26.4.0 + 26.4.1 DynamiaModules - EntityFiles - S3 @@ -49,7 +49,7 @@ tools.dynamia.modules tools.dynamia.modules.entityfiles - 26.4.0 + 26.4.1 software.amazon.awssdk diff --git a/extensions/entity-files/sources/ui/pom.xml b/extensions/entity-files/sources/ui/pom.xml index a1669a7f..9b8399f0 100644 --- a/extensions/entity-files/sources/ui/pom.xml +++ b/extensions/entity-files/sources/ui/pom.xml @@ -22,7 +22,7 @@ tools.dynamia.modules.entityfiles.parent tools.dynamia.modules - 26.4.0 + 26.4.1 DynamiaModules - EntityFiles UI tools.dynamia.modules.entityfiles.ui @@ -48,12 +48,12 @@ tools.dynamia.modules tools.dynamia.modules.entityfiles - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 jar diff --git a/extensions/file-importer/sources/core/pom.xml b/extensions/file-importer/sources/core/pom.xml index 751ac78f..6948f2ff 100644 --- a/extensions/file-importer/sources/core/pom.xml +++ b/extensions/file-importer/sources/core/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules.importer.parent tools.dynamia.modules - 26.4.0 + 26.4.1 Dynamia Modules - Importer Core tools.dynamia.modules.importer @@ -56,7 +56,7 @@ tools.dynamia tools.dynamia.reports - 26.4.0 + 26.4.1 diff --git a/extensions/file-importer/sources/pom.xml b/extensions/file-importer/sources/pom.xml index 74d35983..1c45634b 100644 --- a/extensions/file-importer/sources/pom.xml +++ b/extensions/file-importer/sources/pom.xml @@ -26,7 +26,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/file-importer/sources/ui/pom.xml b/extensions/file-importer/sources/ui/pom.xml index 7b0c8303..f428ce04 100644 --- a/extensions/file-importer/sources/ui/pom.xml +++ b/extensions/file-importer/sources/ui/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules.importer.parent tools.dynamia.modules - 26.4.0 + 26.4.1 Dynamia Modules - Importer UI tools.dynamia.modules.importer.ui @@ -55,13 +55,13 @@ tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.importer - 26.4.0 + 26.4.1 diff --git a/extensions/finances/sources/api/pom.xml b/extensions/finances/sources/api/pom.xml index 4c0ee885..809fa308 100644 --- a/extensions/finances/sources/api/pom.xml +++ b/extensions/finances/sources/api/pom.xml @@ -26,7 +26,7 @@ tools.dynamia.modules tools.dynamia.modules.finances.parent - 26.4.0 + 26.4.1 Dynamia Modules - Finances API diff --git a/extensions/finances/sources/pom.xml b/extensions/finances/sources/pom.xml index 81a0c965..12307fd7 100644 --- a/extensions/finances/sources/pom.xml +++ b/extensions/finances/sources/pom.xml @@ -26,7 +26,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/pom.xml b/extensions/pom.xml index 03d89c7b..a5b92e00 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -6,7 +6,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../pom.xml diff --git a/extensions/reports/packages/reports-sdk/package.json b/extensions/reports/packages/reports-sdk/package.json index a7c1e94b..7bd5e313 100644 --- a/extensions/reports/packages/reports-sdk/package.json +++ b/extensions/reports/packages/reports-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dynamia-tools/reports-sdk", - "version": "26.3.2", + "version": "26.4.1", "website": "https://dynamia.tools", "description": "TypeScript/JavaScript client SDK for the Dynamia Reports extension REST API", "keywords": [ @@ -71,4 +71,3 @@ "@vitest/coverage-v8": "^3.0.0" } } - diff --git a/extensions/reports/sources/api/pom.xml b/extensions/reports/sources/api/pom.xml index 391a617f..4d2c4162 100644 --- a/extensions/reports/sources/api/pom.xml +++ b/extensions/reports/sources/api/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.reports.parent - 26.4.0 + 26.4.1 DynamiaModules - Reports API diff --git a/extensions/reports/sources/core/pom.xml b/extensions/reports/sources/core/pom.xml index d9109c32..388e29b8 100644 --- a/extensions/reports/sources/core/pom.xml +++ b/extensions/reports/sources/core/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.reports.parent - 26.4.0 + 26.4.1 DynamiaModules - Reports Core @@ -50,17 +50,17 @@ tools.dynamia.modules tools.dynamia.modules.reports.api - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.saas.jpa - 26.4.0 + 26.4.1 org.springframework @@ -69,12 +69,12 @@ tools.dynamia tools.dynamia.reports - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.templates - 26.4.0 + 26.4.1 compile diff --git a/extensions/reports/sources/pom.xml b/extensions/reports/sources/pom.xml index 5cc490e4..3d7a7dff 100644 --- a/extensions/reports/sources/pom.xml +++ b/extensions/reports/sources/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/reports/sources/ui/pom.xml b/extensions/reports/sources/ui/pom.xml index 041812c4..b3c0f1c1 100644 --- a/extensions/reports/sources/ui/pom.xml +++ b/extensions/reports/sources/ui/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.reports.parent - 26.4.0 + 26.4.1 DynamiaModules - Reports UI @@ -49,22 +49,22 @@ tools.dynamia.modules tools.dynamia.modules.reports.core - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.dashboard - 26.4.0 + 26.4.1 io.swagger.core.v3 swagger-annotations-jakarta - 2.2.42 + ${swagger.version} provided diff --git a/extensions/saas/packages/saas-sdk/package.json b/extensions/saas/packages/saas-sdk/package.json index d11eccdf..373b85c1 100644 --- a/extensions/saas/packages/saas-sdk/package.json +++ b/extensions/saas/packages/saas-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dynamia-tools/saas-sdk", - "version": "26.3.2", + "version": "26.4.1", "website": "https://dynamia.tools", "description": "TypeScript/JavaScript client SDK for the Dynamia SaaS extension REST API", "keywords": [ @@ -72,4 +72,3 @@ "@vitest/coverage-v8": "^3.0.0" } } - diff --git a/extensions/saas/sources/api/pom.xml b/extensions/saas/sources/api/pom.xml index 61c8e33c..8cb939f8 100644 --- a/extensions/saas/sources/api/pom.xml +++ b/extensions/saas/sources/api/pom.xml @@ -26,7 +26,7 @@ tools.dynamia.modules tools.dynamia.modules.saas.parent - 26.4.0 + 26.4.1 @@ -55,7 +55,7 @@ tools.dynamia tools.dynamia.actions - 26.4.0 + 26.4.1 org.springframework.boot diff --git a/extensions/saas/sources/core/pom.xml b/extensions/saas/sources/core/pom.xml index c4224331..dbca4eeb 100644 --- a/extensions/saas/sources/core/pom.xml +++ b/extensions/saas/sources/core/pom.xml @@ -22,7 +22,7 @@ tools.dynamia.modules tools.dynamia.modules.saas.parent - 26.4.0 + 26.4.1 DynamiaModules - SaaS Core @@ -49,18 +49,18 @@ tools.dynamia.modules tools.dynamia.modules.saas.api - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.saas.jpa - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 @@ -86,7 +86,7 @@ tools.dynamia.modules tools.dynamia.modules.entityfiles - 26.4.0 + 26.4.1 org.hibernate.orm diff --git a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/AccountPaymentProcessor.java b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/AccountPaymentProcessor.java index 4d44fca7..c604aaad 100644 --- a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/AccountPaymentProcessor.java +++ b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/AccountPaymentProcessor.java @@ -1,9 +1,5 @@ package tools.dynamia.modules.saas; -import tools.dynamia.modules.saas.domain.AccountPayment; - -import java.util.Map; - /** * Basic API to implement processing account payments. You should implement * your own payment processor and register it using spring annotations @@ -15,5 +11,20 @@ public interface AccountPaymentProcessor { String getName(); - Map processPayment(AccountPayment payment); + default String getExtra0Label() { + return "Extra 0"; + } + + default String getExtra1Label() { + return "Extra 1"; + } + + default String getExtra2Label() { + return "Extra 2"; + } + + default String getExtra3Label() { + return "Extra 3"; + } + } diff --git a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/Account.java b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/Account.java index 21179fd1..42fb2420 100644 --- a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/Account.java +++ b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/Account.java @@ -197,6 +197,9 @@ public class Account extends SimpleEntity implements Transferable { @ManyToOne private AccountSaleChannel channel; + @ManyToOne(fetch = FetchType.LAZY) + private AccountPaymentProvider customPaymentProvider; + public Account() { initLocale(); } @@ -948,4 +951,12 @@ public Date getResellerInvoiceDate() { public void setResellerInvoiceDate(Date resellerInvoiceDate) { this.resellerInvoiceDate = resellerInvoiceDate; } + + public AccountPaymentProvider getCustomPaymentProvider() { + return customPaymentProvider; + } + + public void setCustomPaymentProvider(AccountPaymentProvider customPaymentProvider) { + this.customPaymentProvider = customPaymentProvider; + } } diff --git a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/AccountPaymentProvider.java b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/AccountPaymentProvider.java index 33015ca2..33a4460e 100644 --- a/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/AccountPaymentProvider.java +++ b/extensions/saas/sources/core/src/main/java/tools/dynamia/modules/saas/domain/AccountPaymentProvider.java @@ -16,14 +16,25 @@ public class AccountPaymentProvider extends SimpleEntity { private String apiKey; @Column(length = 500) private String apiSecret; + + @Column(length = 500) + private String integritySecret; + + @Column(length = 500) + private String webCheckoutSecret; + @Column(length = 500) private String serviceURL; private String merchantId; private boolean testMode; - private boolean active = true; private String paymentProcessor; + private String extra0; + private String extra1; + private String extra2; + private String extra3; + public String getName() { return name; } @@ -80,6 +91,54 @@ public void setActive(boolean active) { this.active = active; } + public String getIntegritySecret() { + return integritySecret; + } + + public void setIntegritySecret(String integritySecret) { + this.integritySecret = integritySecret; + } + + public String getWebCheckoutSecret() { + return webCheckoutSecret; + } + + public void setWebCheckoutSecret(String webcheckoutSecret) { + this.webCheckoutSecret = webcheckoutSecret; + } + + public String getExtra0() { + return extra0; + } + + public void setExtra0(String extra0) { + this.extra0 = extra0; + } + + public String getExtra1() { + return extra1; + } + + public void setExtra1(String extra1) { + this.extra1 = extra1; + } + + public String getExtra2() { + return extra2; + } + + public void setExtra2(String extra2) { + this.extra2 = extra2; + } + + public String getExtra3() { + return extra3; + } + + public void setExtra3(String extra3) { + this.extra3 = extra3; + } + @Override public String toString() { return name; diff --git a/extensions/saas/sources/jpa/pom.xml b/extensions/saas/sources/jpa/pom.xml index a66d1997..d46b7378 100644 --- a/extensions/saas/sources/jpa/pom.xml +++ b/extensions/saas/sources/jpa/pom.xml @@ -24,7 +24,7 @@ tools.dynamia.modules.saas.parent tools.dynamia.modules - 26.4.0 + 26.4.1 DynamiaModules - SaaS JPA @@ -35,12 +35,12 @@ tools.dynamia.modules tools.dynamia.modules.saas.api - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 diff --git a/extensions/saas/sources/pom.xml b/extensions/saas/sources/pom.xml index 448251cd..5b9d53a7 100644 --- a/extensions/saas/sources/pom.xml +++ b/extensions/saas/sources/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/saas/sources/remote/pom.xml b/extensions/saas/sources/remote/pom.xml index 9e849cb0..c8aa1d0e 100644 --- a/extensions/saas/sources/remote/pom.xml +++ b/extensions/saas/sources/remote/pom.xml @@ -25,7 +25,7 @@ tools.dynamia.modules.saas.parent tools.dynamia.modules - 26.4.0 + 26.4.1 @@ -38,7 +38,7 @@ tools.dynamia.modules tools.dynamia.modules.saas.jpa - 26.4.0 + 26.4.1 diff --git a/extensions/saas/sources/ui/pom.xml b/extensions/saas/sources/ui/pom.xml index 6bde3bc2..ae813d8d 100644 --- a/extensions/saas/sources/ui/pom.xml +++ b/extensions/saas/sources/ui/pom.xml @@ -22,7 +22,7 @@ tools.dynamia.modules tools.dynamia.modules.saas.parent - 26.4.0 + 26.4.1 DynamiaModules - SaaS UI tools.dynamia.modules.saas.ui @@ -54,12 +54,12 @@ tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.saas - 26.4.0 + 26.4.1 @@ -70,7 +70,7 @@ tools.dynamia.modules tools.dynamia.modules.entityfiles.ui - 26.4.0 + 26.4.1 diff --git a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountForm.yml b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountForm.yml index aa632585..e692fac6 100644 --- a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountForm.yml +++ b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountForm.yml @@ -120,6 +120,8 @@ fields: multiline: true height: 80px + customPaymentProvider: + groups: contactInfo: @@ -130,7 +132,7 @@ groups: fields: [ reseller, resellerAgent, resellerInvoice, resellerInvoiceDate, resellerComments ] configuration: - fields: [ locale, timeZone,skin, maxUsers,statusDescription,uuid,instanceUuid,creationDate,remote,autoInit,requiredInstanceUuid,redirect,templateAccount ] + fields: [ locale, timeZone,skin, maxUsers,statusDescription,uuid,instanceUuid,creationDate,customPaymentProvider,remote,autoInit,requiredInstanceUuid,redirect,templateAccount, ] notification: fields: [ globalMessage,globalMessageType,showGlobalMessage ] diff --git a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderForm.yml b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderForm.yml index c67fae0d..ac1b35ba 100644 --- a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderForm.yml +++ b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderForm.yml @@ -14,9 +14,32 @@ fields: merchantId: apiKey: apiSecret: + webCheckoutSecret: + label: Webcheckout URL + integritySecret: + serviceURL: + label: Service URL testMode: active: + extra0: + label: Extra 0 + extra1: + label: Extra 1 + extra2: + label: Extra 2 + extra3: + label: Extra 3 + +groups: + extraProperties: + label: Extra Properties + fields: + - extra0 + - extra1 + - extra2 + - extra3 + layout: - columns: 1 \ No newline at end of file + columns: 2 \ No newline at end of file diff --git a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderTable.yml b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderTable.yml index f51f07e4..66e127b2 100644 --- a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderTable.yml +++ b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountPaymentProviderTable.yml @@ -7,6 +7,5 @@ fields: paymentProcessor: label: Processor merchantId: - serviceURL: testMode: active: diff --git a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionForm.yml b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionForm.yml index ff0b6bd0..6b766994 100644 --- a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionForm.yml +++ b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionForm.yml @@ -17,3 +17,6 @@ fields: component: localebox parameters: component: crudview + +layout: + columns: 4 \ No newline at end of file diff --git a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionTable.yml b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionTable.yml index 49acd02c..52ea9ee9 100644 --- a/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionTable.yml +++ b/extensions/saas/sources/ui/src/main/resources/META-INF/descriptors/AccountRegionTable.yml @@ -7,4 +7,5 @@ fields: code: currency: timeZone: - locale: \ No newline at end of file + locale: + paymentProvider: \ No newline at end of file diff --git a/extensions/security/sources/core/pom.xml b/extensions/security/sources/core/pom.xml index abd650b8..8230ac74 100644 --- a/extensions/security/sources/core/pom.xml +++ b/extensions/security/sources/core/pom.xml @@ -17,7 +17,7 @@ tools.dynamia.modules tools.dynamia.modules.security.parent - 26.4.0 + 26.4.1 4.0.0 @@ -32,34 +32,34 @@ tools.dynamia.modules tools.dynamia.modules.saas.api - 26.4.0 + 26.4.1 tools.dynamia.modules tools.dynamia.modules.entityfiles - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.web - 26.4.0 + 26.4.1 diff --git a/extensions/security/sources/pom.xml b/extensions/security/sources/pom.xml index debd67e0..c2b0bcbb 100644 --- a/extensions/security/sources/pom.xml +++ b/extensions/security/sources/pom.xml @@ -19,7 +19,7 @@ tools.dynamia.modules tools.dynamia.modules.parent - 26.4.0 + 26.4.1 ../../pom.xml diff --git a/extensions/security/sources/ui/pom.xml b/extensions/security/sources/ui/pom.xml index 6f6eb733..a023c6ac 100644 --- a/extensions/security/sources/ui/pom.xml +++ b/extensions/security/sources/ui/pom.xml @@ -17,7 +17,7 @@ tools.dynamia.modules tools.dynamia.modules.security.parent - 26.4.0 + 26.4.1 DynamiaModules - Security UI @@ -44,18 +44,18 @@ tools.dynamia.modules tools.dynamia.modules.security - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.app - 26.4.0 + 26.4.1 diff --git a/platform/app/pom.xml b/platform/app/pom.xml index 3a01b363..d5ab3d79 100644 --- a/platform/app/pom.xml +++ b/platform/app/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../pom.xml @@ -74,58 +74,58 @@ tools.dynamia tools.dynamia.actions - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.crud - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.io - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.navigation - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.reports - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.templates - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.viewers - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.web - 26.4.0 + 26.4.1 @@ -205,7 +205,7 @@ tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 test diff --git a/platform/app/src/main/java/tools/dynamia/app/DynamiaBaseConfiguration.java b/platform/app/src/main/java/tools/dynamia/app/DynamiaBaseConfiguration.java index 5c671355..3dd5447f 100644 --- a/platform/app/src/main/java/tools/dynamia/app/DynamiaBaseConfiguration.java +++ b/platform/app/src/main/java/tools/dynamia/app/DynamiaBaseConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import tools.dynamia.app.reports.JasperReportCompiler; +import tools.dynamia.commons.StringUtils; import tools.dynamia.domain.services.CrudService; import tools.dynamia.domain.services.impl.NoOpCrudService; import tools.dynamia.integration.ms.MessageService; @@ -31,6 +32,8 @@ import tools.dynamia.integration.search.NoOpSearchProvider; import tools.dynamia.integration.search.SearchResultProvider; import tools.dynamia.integration.search.SearchService; +import tools.dynamia.navigation.Module; +import tools.dynamia.navigation.ModuleProvider; import tools.dynamia.reports.ReportCompiler; import tools.dynamia.templates.TemplateEngine; import tools.dynamia.web.navigation.RestApiNavigationConfiguration; @@ -178,4 +181,16 @@ public TemplateEngine templateEngine() { + /** + * Provides an empty {@link ModuleProvider} bean if none is registered. + * + * @return a ModuleProvider that returns a dummy module with a random name and message + */ + @Bean + @ConditionalOnMissingBean(ModuleProvider.class) + public ModuleProvider emptyModuleProvider() { + return () -> new Module(StringUtils.randomString(), "No modules registered"); + } + + } diff --git a/platform/app/src/main/java/tools/dynamia/app/DynamiaToolsWebApplication.java b/platform/app/src/main/java/tools/dynamia/app/DynamiaToolsWebApplication.java index 0f200b87..1bbb0f8f 100644 --- a/platform/app/src/main/java/tools/dynamia/app/DynamiaToolsWebApplication.java +++ b/platform/app/src/main/java/tools/dynamia/app/DynamiaToolsWebApplication.java @@ -77,15 +77,5 @@ public PWAManifestController pwaManifestController(PWAManifest manifest) { return new PWAManifestController(manifest); } - /** - * Provides an empty {@link ModuleProvider} bean if none is registered. - * - * @return a ModuleProvider that returns a dummy module with a random name and message - */ - @Bean - @ConditionalOnMissingBean(ModuleProvider.class) - public ModuleProvider emptyModuleProvider() { - return () -> new Module(StringUtils.randomString(), "No modules registered"); - } } diff --git a/platform/core/actions/pom.xml b/platform/core/actions/pom.xml index 13e590b1..17b5079a 100644 --- a/platform/core/actions/pom.xml +++ b/platform/core/actions/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -65,12 +65,12 @@ tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 diff --git a/platform/core/commons/pom.xml b/platform/core/commons/pom.xml index dd6455de..51bbda17 100644 --- a/platform/core/commons/pom.xml +++ b/platform/core/commons/pom.xml @@ -25,7 +25,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml DynamiaTools - Commons diff --git a/platform/core/crud/pom.xml b/platform/core/crud/pom.xml index 1af5c40f..b077e403 100644 --- a/platform/core/crud/pom.xml +++ b/platform/core/crud/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -62,23 +62,23 @@ tools.dynamia tools.dynamia.actions - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.viewers - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.navigation - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 test diff --git a/platform/core/domain-jpa/pom.xml b/platform/core/domain-jpa/pom.xml index 1c088e30..15fba195 100644 --- a/platform/core/domain-jpa/pom.xml +++ b/platform/core/domain-jpa/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -65,7 +65,7 @@ tools.dynamia tools.dynamia.domain - 26.4.0 + 26.4.1 diff --git a/platform/core/domain/pom.xml b/platform/core/domain/pom.xml index 7dda4c97..aecb3486 100644 --- a/platform/core/domain/pom.xml +++ b/platform/core/domain/pom.xml @@ -26,7 +26,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml DynamiaTools - Domain diff --git a/platform/core/integration/pom.xml b/platform/core/integration/pom.xml index a6173d9f..7e38a862 100644 --- a/platform/core/integration/pom.xml +++ b/platform/core/integration/pom.xml @@ -27,7 +27,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -67,7 +67,7 @@ tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 provided diff --git a/platform/core/io/pom.xml b/platform/core/io/pom.xml index 464e6e78..0d665e42 100644 --- a/platform/core/io/pom.xml +++ b/platform/core/io/pom.xml @@ -28,7 +28,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml diff --git a/platform/core/navigation/pom.xml b/platform/core/navigation/pom.xml index 59438148..376bc029 100644 --- a/platform/core/navigation/pom.xml +++ b/platform/core/navigation/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -63,17 +63,17 @@ tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.actions - 26.4.0 + 26.4.1 diff --git a/platform/core/reports/pom.xml b/platform/core/reports/pom.xml index c62fe634..54d95537 100644 --- a/platform/core/reports/pom.xml +++ b/platform/core/reports/pom.xml @@ -26,7 +26,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml diff --git a/platform/core/templates/pom.xml b/platform/core/templates/pom.xml index cb2fff38..57d96bc1 100644 --- a/platform/core/templates/pom.xml +++ b/platform/core/templates/pom.xml @@ -23,7 +23,7 @@ tools.dynamia.parent tools.dynamia - 26.4.0 + 26.4.1 ../../../pom.xml @@ -64,12 +64,12 @@ tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 diff --git a/platform/core/viewers/pom.xml b/platform/core/viewers/pom.xml index 776ae47e..2708cea1 100644 --- a/platform/core/viewers/pom.xml +++ b/platform/core/viewers/pom.xml @@ -25,7 +25,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -67,27 +67,27 @@ tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.io - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.actions - 26.4.0 + 26.4.1 org.yaml diff --git a/platform/core/web/pom.xml b/platform/core/web/pom.xml index 47cdf49d..09183fae 100644 --- a/platform/core/web/pom.xml +++ b/platform/core/web/pom.xml @@ -29,7 +29,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -88,27 +88,27 @@ tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.navigation - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.viewers - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.crud - 26.4.0 + 26.4.1 org.springframework diff --git a/platform/packages/cli/README.md b/platform/packages/cli/README.md new file mode 100644 index 00000000..2f475d28 --- /dev/null +++ b/platform/packages/cli/README.md @@ -0,0 +1,128 @@ +# @dynamia-tools/cli + +> Scaffold new [Dynamia Platform](https://dynamia.tools) projects from the command line. + +--- + +## Installation + +### One-line bootstrap (Linux & macOS) + +Installs JDK 25, Node.js LTS, and the CLI: + +```bash +curl -fsSL https://get.dynamia.tools | bash +``` + +### npm (global) + +```bash +npm install -g @dynamia-tools/cli +``` + +### Requirements + +| Tool | Minimum version | +|---|--------------------------------| +| Node.js | 24 | +| git | any recent version (required) | +| Java | 25 (recommended) | + +--- + +## Usage + +```bash +dynamia new +``` + +The wizard guides you through: + +1. **Project name** β€” lowercase, letters/numbers/hyphens only +2. **Scaffold choice** β€” Backend + Frontend / Backend only / Frontend only +3. **Backend language** β€” Java, Kotlin, or Groovy +4. **Maven coordinates** β€” Group ID, Artifact ID, version, description +5. **Frontend framework** β€” Vue 3 or React +6. **Package manager** β€” pnpm, npm, or yarn +7. **Confirm** β€” shows a summary table before generating + +> Beta note: this CLI is in active beta. If a command or template is not ready yet, you will get a friendly "not available yet" message. + +Errors include short support codes such as `DT-TEMPLATE-002` to make issue reporting easier. + +--- + +## What gets generated + +``` +my-erp-app/ +β”œβ”€β”€ backend/ Spring Boot + Dynamia Tools (Java/Kotlin/Groovy) +└── frontend/ Vue 3 or React + Vite + @dynamia-tools/vue|sdk +``` + +### Backend + +- Cloned from a GitHub template repo (e.g. `dynamiatools/template-backend-java`) +- Template repository and branch are validated before cloning +- Placeholder package `com.example.demo` renamed to your `groupId.artifactId` +- All tokens replaced in `.java`, `.kt`, `.groovy`, `.xml`, `.yml`, `.properties`, `.md` +- `DemoApplication.java` renamed to `Application.java` + +### Frontend + +- Cloned from a GitHub template repo (e.g. `dynamiatools/template-frontend-vue`) +- Template repository and branch are validated before cloning +- Falls back to `npm create vite@latest` if template clone/validation fails +- `@dynamia-tools/sdk` and `@dynamia-tools/ui-core` installed automatically + +--- + +## Configuration + +All versions, URLs, and template repositories live in `cli.properties` β€” the single source of truth. TypeScript code never hardcodes versions or URLs. + +Key sections: + +| Section | Description | +|---|---| +| `dynamia.*` | Framework version and docs URL | +| `beta.*` | Beta-mode UX messages | +| `java.*` | JDK version and SDKMAN candidate | +| `template.backend..*` | Backend template repos (java, kotlin, groovy) | +| `template.frontend..*` | Frontend template repos (vue, react) | +| `template.*..enabled` | Enable/disable template options in the wizard | +| `template.*..availabilityMessage` | Message shown when a template is disabled | +| `token.*` | Placeholder tokens used inside template repos | +| `vite.*` | Vite fallback config | + +--- + +## Template author conventions + +When creating template repos (e.g. `dynamiatools/template-backend-java`): + +- Default Java source package: `com.example.demo` +- `pom.xml` groupId: `com.example`, artifactId: `demo` +- Main class: `src/main/java/com/example/demo/DemoApplication.java` +- Use these placeholders in files: + - `{{GROUP_ID}}` β€” user's group ID + - `{{ARTIFACT_ID}}` β€” user's artifact ID + - `{{BASE_PACKAGE}}` β€” computed base package + - `{{PROJECT_NAME}}` β€” project name + - `{{PROJECT_VERSION}}` β€” project version + - `{{DYNAMIA_VERSION}}` β€” Dynamia Tools version + - `{{SPRING_BOOT_VERSION}}` β€” Spring Boot version + +--- + +## Links + +- [Dynamia Tools docs](https://dynamia.tools/docs) +- [GitHub org](https://github.com/dynamiatools) +- [Framework repo](https://github.com/dynamiatools/framework) + +--- + +## License + +Apache-2.0 β€” Β© Dynamia Soluciones IT SAS diff --git a/platform/packages/cli/cli.properties b/platform/packages/cli/cli.properties new file mode 100644 index 00000000..e96ec625 --- /dev/null +++ b/platform/packages/cli/cli.properties @@ -0,0 +1,109 @@ +# Dynamia Tools CLI Configuration +# All versions, URLs, template repos, and token names live here. +# Updating any version or adding a new template never requires touching TypeScript. + +# --- +# dynamia.* +# --- +dynamia.version=26.4.1 +dynamia.docs.url=https://dynamia.tools/docs + +# --- +# beta.* +# --- +beta.enabled=true +beta.intro.message.enabled=true +beta.not.available.message.enabled=true + +# --- +# java.* +# --- +java.version=25 +java.sdkman.candidate=25-tem + +# --- +# node.* +# --- +node.minimum.version=24 + +# --- +# spring.* (kept as reference metadata, not used for generation) +# --- +spring.initializr.url=https://start.spring.io +spring.initializr.metadata.url=https://start.spring.io/metadata/client +spring.boot.version=4.0.5 + +# --- +# template.backend..* +# --- +template.backend.java.label=Java +template.backend.java.repo=https://github.com/dynamiatools/template-backend-java +template.backend.java.branch=main +template.backend.java.description=Spring Boot + Dynamia Tools (Java) +template.backend.java.enabled=true +template.backend.java.availabilityMessage=Backend Java template is not available yet. + +template.backend.kotlin.label=Kotlin +template.backend.kotlin.repo=https://github.com/dynamiatools/template-backend-kotlin +template.backend.kotlin.branch=main +template.backend.kotlin.description=Spring Boot + Dynamia Tools (Kotlin) +template.backend.kotlin.enabled=true +template.backend.kotlin.availabilityMessage=Backend Kotlin template is not available yet. + +template.backend.groovy.label=Groovy +template.backend.groovy.repo=https://github.com/dynamiatools/template-backend-groovy +template.backend.groovy.branch=main +template.backend.groovy.description=Spring Boot + Dynamia Tools (Groovy) +template.backend.groovy.enabled=true +template.backend.groovy.availabilityMessage=Backend Groovy template is not available yet. + +# --- +# template.frontend..* +# --- +template.frontend.vue.label=Vue 3 +template.frontend.vue.repo=https://github.com/dynamiatools/template-frontend-vue +template.frontend.vue.branch=main +template.frontend.vue.description=Vue 3 + Vite + @dynamia-tools/vue +template.frontend.vue.enabled=true +template.frontend.vue.availabilityMessage=Vue frontend template is not available yet. + +template.frontend.react.label=React +template.frontend.react.repo=https://github.com/dynamiatools/template-frontend-react +template.frontend.react.branch=main +template.frontend.react.description=React + Vite + @dynamia-tools/sdk +template.frontend.react.enabled=true +template.frontend.react.availabilityMessage=React frontend template is not available yet. + +# --- +# vite.* (fallback when git clone fails) +# --- +vite.fallback.enabled=true +vite.templates.vue=vue-ts +vite.templates.react=react-ts + +# --- +# npm.* (SDK package names and versions) +# --- +npm.sdk.package=@dynamia-tools/sdk +npm.sdk.version=26.4.1 +npm.ui-core.package=@dynamia-tools/ui-core +npm.ui-core.version=26.4.1 +npm.vue.package=@dynamia-tools/vue +npm.vue.version=26.4.1 + +# --- +# installer.* +# --- +installer.sdkman.url=https://get.sdkman.io +installer.fnm.url=https://fnm.vercel.app/install + +# --- +# token.* (placeholders used in template repos) +# --- +token.group.id={{GROUP_ID}} +token.artifact.id={{ARTIFACT_ID}} +token.base.package={{BASE_PACKAGE}} +token.project.name={{PROJECT_NAME}} +token.project.version={{PROJECT_VERSION}} +token.dynamia.version={{DYNAMIA_VERSION}} +token.spring.boot.version={{SPRING_BOOT_VERSION}} diff --git a/platform/packages/cli/install.sh b/platform/packages/cli/install.sh new file mode 100755 index 00000000..e0502229 --- /dev/null +++ b/platform/packages/cli/install.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +# Dynamia Tools β€” Full Toolchain Bootstrap +# Usage: curl -fsSL https://get.dynamia.tools | bash +# +# Installs: git, curl, zip/unzip, JDK 25 (via SDKMAN), Node.js LTS (via fnm), +# and the @dynamia-tools/cli npm package. +# +# Supported: Ubuntu/Debian, Fedora/RHEL/CentOS, Arch Linux, macOS (Homebrew) +# Not supported: Windows + +set -euo pipefail + +# --------------------------------------------------------------------------- +# ANSI colors (no external deps) +# --------------------------------------------------------------------------- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +RESET='\033[0m' + +info() { echo -e "${CYAN}β„Ή $*${RESET}"; } +success() { echo -e "${GREEN}βœ“ $*${RESET}"; } +warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } +error() { echo -e "${RED}βœ— $*${RESET}" >&2; exit 1; } +banner() { + echo -e "${CYAN}${BOLD}" + echo ' ____ _ _____ _ ' + echo ' | _ \ _ _ _ __ __ _ _ __ ___ (_) __ |_ _|__ ___ | |___ ' + echo ' | | | | | | | '"'"'_ \ / _` | '"'"'_ ` _ \| |/ _` || |/ _ \ / _ \| / __|' + echo ' | |_| | |_| | | | | (_| | | | | | | | (_| || | (_) | (_) | \__ \' + echo ' |____/ \__, |_| |_|\__,_|_| |_| |_|_|\__,_||_|\___/ \___/|_|___/' + echo ' |___/ ' + echo -e "${RESET}" + echo -e "${BOLD} Dynamia Tools β€” Toolchain Installer${RESET}" + echo "" +} + +# --------------------------------------------------------------------------- +# OS / package manager detection +# --------------------------------------------------------------------------- +OS="" +PM="" + +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + OS="macos" + PM="brew" + elif command -v apt-get &>/dev/null; then + OS="debian" + PM="apt-get" + elif command -v dnf &>/dev/null; then + OS="fedora" + PM="dnf" + elif command -v pacman &>/dev/null; then + OS="arch" + PM="pacman" + else + error "Unsupported OS. Please install dependencies manually and then run: npm install -g @dynamia-tools/cli" + fi + info "Detected OS: ${OS} (package manager: ${PM})" +} + +# --------------------------------------------------------------------------- +# Install a single prerequisite package if missing +# --------------------------------------------------------------------------- +install_pkg() { + local cmd="$1" + local pkg="${2:-$1}" + + if command -v "$cmd" &>/dev/null; then + success "$cmd already installed" + return + fi + + info "Installing $pkg..." + case "$PM" in + brew) brew install "$pkg" ;; + apt-get) sudo apt-get install -y "$pkg" ;; + dnf) sudo dnf install -y "$pkg" ;; + pacman) sudo pacman -S --noconfirm "$pkg" ;; + esac +} + +# --------------------------------------------------------------------------- +# Install prerequisites +# --------------------------------------------------------------------------- +install_prerequisites() { + info "Checking prerequisites..." + + install_pkg curl + install_pkg git + + # git is REQUIRED β€” hard stop if unavailable + if ! command -v git &>/dev/null; then + error "git could not be installed. git is required for template cloning. Please install git manually and re-run this script." + fi + + install_pkg zip + install_pkg unzip + + success "Prerequisites ready" +} + +# --------------------------------------------------------------------------- +# JDK 25 via SDKMAN +# --------------------------------------------------------------------------- +install_java() { + info "Checking JDK 25..." + + if java -version 2>&1 | grep -qE '^(openjdk|java) version "25'; then + success "JDK 25 already installed" + return + fi + + # Only bootstrap SDKMAN when JDK 25 is missing + if [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]]; then + # shellcheck source=/dev/null + source "$HOME/.sdkman/bin/sdkman-init.sh" + fi + + if ! command -v sdk &>/dev/null; then + info "Installing SDKMAN..." + curl -s "https://get.sdkman.io" | bash + # shellcheck source=/dev/null + source "$HOME/.sdkman/bin/sdkman-init.sh" + fi + + info "Installing JDK 25 via SDKMAN..." + sdk install java 25-tem + sdk default java 25-tem + success "JDK 25 installed" +} + +# --------------------------------------------------------------------------- +# Node.js LTS via fnm +# --------------------------------------------------------------------------- +install_node() { + info "Checking Node.js..." + if ! command -v fnm &>/dev/null; then + info "Installing fnm (Fast Node Manager)..." + curl -fsSL https://fnm.vercel.app/install | bash + export PATH="$HOME/.local/share/fnm:$PATH" + eval "$(fnm env --use-on-cd)" + fi + + if command -v node &>/dev/null; then + success "Node.js already installed ($(node --version))" + else + info "Installing Node.js LTS via fnm..." + fnm install --lts + fnm use lts-latest + fnm default lts-latest + success "Node.js LTS installed" + fi +} + +# --------------------------------------------------------------------------- +# Patch shell profile (SDKMAN + fnm init lines) +# --------------------------------------------------------------------------- +patch_shell_profile() { + local profile="" + + case "$SHELL" in + */zsh) profile="$HOME/.zshrc" ;; + */bash) profile="$HOME/.bashrc" ;; + *) profile="$HOME/.profile" ;; + esac + + info "Patching shell profile: $profile" + + # SDKMAN init + local sdkman_line='source "$HOME/.sdkman/bin/sdkman-init.sh"' + if ! grep -q "sdkman-init.sh" "$profile" 2>/dev/null; then + echo "" >> "$profile" + echo "# SDKMAN" >> "$profile" + echo 'export SDKMAN_DIR="$HOME/.sdkman"' >> "$profile" + echo "[[ -s \"\$HOME/.sdkman/bin/sdkman-init.sh\" ]] && $sdkman_line" >> "$profile" + success "Added SDKMAN init to $profile" + else + success "SDKMAN already configured in $profile" + fi + + # fnm init + local fnm_line='eval "$(fnm env --use-on-cd)"' + if ! grep -q "fnm env" "$profile" 2>/dev/null; then + echo "" >> "$profile" + echo "# fnm (Fast Node Manager)" >> "$profile" + echo 'export PATH="$HOME/.local/share/fnm:$PATH"' >> "$profile" + echo "$fnm_line" >> "$profile" + success "Added fnm init to $profile" + else + success "fnm already configured in $profile" + fi +} + +# --------------------------------------------------------------------------- +# Install CLI and launch +# --------------------------------------------------------------------------- +install_cli() { + info "Installing @dynamia-tools/cli..." + npm install -g @dynamia-tools/cli + success "@dynamia-tools/cli installed" + echo "" + info "Launching project wizard..." + dynamia new +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +main() { + banner + detect_os + install_prerequisites + install_java + install_node + patch_shell_profile + install_cli +} + +main "$@" diff --git a/platform/packages/cli/package-lock.json b/platform/packages/cli/package-lock.json new file mode 100644 index 00000000..8ba6d016 --- /dev/null +++ b/platform/packages/cli/package-lock.json @@ -0,0 +1,1057 @@ +{ + "name": "@dynamia-tools/cli", + "version": "26.4.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dynamia-tools/cli", + "version": "26.4.1", + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^7.0.0", + "chalk": "^5.0.0", + "execa": "^9.0.0", + "ora": "^8.0.0" + }, + "bin": { + "dynamia": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/platform/packages/cli/package.json b/platform/packages/cli/package.json new file mode 100644 index 00000000..b927182c --- /dev/null +++ b/platform/packages/cli/package.json @@ -0,0 +1,59 @@ +{ + "name": "@dynamia-tools/cli", + "version": "26.4.1", + "description": "Dynamia Tools CLI β€” Scaffold new Dynamia Platform projects", + "keywords": [ + "dynamia", + "cli", + "scaffold", + "spring-boot", + "vue", + "typescript" + ], + "homepage": "https://dynamia.tools", + "bugs": { + "url": "https://github.com/dynamiatools/framework/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/dynamiatools/framework.git", + "directory": "platform/packages/cli" + }, + "license": "Apache-2.0", + "author": "Dynamia Soluciones IT SAS", + "type": "module", + "bin": { + "dynamia": "./dist/index.js" + }, + "files": [ + "dist", + "cli.properties", + "install.sh", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "@inquirer/prompts": "^7.0.0", + "chalk": "^5.0.0", + "execa": "^9.0.0", + "ora": "^8.0.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.7.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "engines": { + "node": ">=24" + } +} diff --git a/platform/packages/cli/src/commands/new.ts b/platform/packages/cli/src/commands/new.ts new file mode 100644 index 00000000..bfdf9d5e --- /dev/null +++ b/platform/packages/cli/src/commands/new.ts @@ -0,0 +1,319 @@ +import { input, select, confirm } from '@inquirer/prompts' +import { join } from 'node:path' +import { existsSync } from 'node:fs' +import { execa } from 'execa' +import { loadConfig, type CliConfig } from '../utils/config.js' +import { runChecks } from '../utils/env.js' +import { banner, info, error, beta, errorMessage, errorWithCode, success, warn } from '../utils/logger.js' +import { generateBackend } from '../generators/backend.js' +import { generateFrontend } from '../generators/frontend.js' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Fetch the latest Spring Boot version from Spring Initializr metadata. */ +function normalizeSpringBootVersion(version: string): string { + // Initializr can return legacy values like "4.0.5.RELEASE"; Maven/Gradle expect "4.0.5". + return version.trim().replace(/\.RELEASE$/i, '') +} + +async function fetchSpringBootVersion(_config: CliConfig): Promise { + try { + const metaRes = await fetch('https://start.spring.io/metadata/client') + if (metaRes.ok) { + const meta = await metaRes.json() as Record + const bootVersion = (meta as { bootVersion?: { default?: string } }).bootVersion?.default + if (bootVersion) return normalizeSpringBootVersion(bootVersion) + } + } catch { + // ignore β€” use fallback + } + // Fallback from config (spring.boot.version kept as reference) + return '4.0.5' +} + +/** Print the final success message. */ +function printSuccessMessage(opts: { + projectName: string + generateBackend: boolean + generateFrontend: boolean + language: string + framework: string + packageManager: string + config: CliConfig + springBootVersion: string +}): void { + const { + projectName, + generateBackend: doBackend, + generateFrontend: doFrontend, + language, + framework, + packageManager: pm, + config, + springBootVersion, + } = opts + + const displaySpringBootVersion = normalizeSpringBootVersion(springBootVersion) + + console.log('') + console.log(`βœ“ Project "${projectName}" created successfully!`) + console.log('') + console.log(' πŸ“ Structure:') + console.log(` ${projectName}/`) + if (doBackend) { + console.log(` β”œβ”€β”€ backend/ (${language.charAt(0).toUpperCase() + language.slice(1)} Β· Spring Boot ${displaySpringBootVersion} Β· Dynamia Tools ${config.dynamia.version})`) + } + if (doFrontend) { + const frontendLabel = config.templates.frontend[framework]?.label ?? framework + const sdkVersion = config.npm['vue']?.version ?? config.npm['sdk']?.version ?? config.dynamia.version + const prefix = doBackend ? 'β””' : 'β”œ' + console.log(` ${prefix}── frontend/ (${frontendLabel} Β· Vite Β· @dynamia-tools/${framework === 'vue' ? 'vue' : 'sdk'} ${sdkVersion})`) + } + console.log('') + console.log(' πŸš€ Next steps:') + console.log('') + if (doBackend) { + console.log(' Backend:') + console.log(` cd ${projectName}/backend`) + console.log(' ./mvnw spring-boot:run') + console.log('') + } + if (doFrontend) { + const installCmd = pm === 'npm' ? 'npm install' : pm === 'yarn' ? 'yarn' : 'pnpm install' + const devCmd = pm === 'npm' ? 'npm run dev' : pm === 'yarn' ? 'yarn dev' : 'pnpm dev' + console.log(' Frontend:') + console.log(` cd ${projectName}/frontend`) + console.log(` ${installCmd} && ${devCmd}`) + console.log('') + } + console.log(` πŸ“– Docs: ${config.dynamia.docsUrl}`) + console.log('') +} + +// --------------------------------------------------------------------------- +// Command entry point +// --------------------------------------------------------------------------- + +export async function runNew(): Promise { + banner() + + // Load configuration from cli.properties + let config: CliConfig + try { + config = await loadConfig() + } catch (err) { + const reason = err instanceof Error ? err.message : 'Unknown configuration error.' + errorWithCode('DT-CONFIG-001', `Failed to load CLI configuration: ${reason}`) + } + + if (config.beta.enabled && config.beta.introMessageEnabled) { + beta('Dynamia Tools CLI is in beta. Some features may still be under active development.') + } + + // Environment checks (git missing = hard stop) + await runChecks() + + // --- Step 1: Project name --- + const projectName = await input({ + message: 'Project name:', + validate: (value: string) => { + if (!value.trim()) return 'Project name cannot be empty' + if (!/^[a-z0-9-]+$/.test(value.trim())) { + return 'Only lowercase letters, numbers, and hyphens are allowed' + } + return true + }, + }) + + // --- Step 2: What to generate --- + type ScaffoldChoice = 'both' | 'backend' | 'frontend' + const scaffoldChoice = await select({ + message: 'What do you want to scaffold?', + choices: [ + { name: 'Backend + Frontend', value: 'both' }, + { name: 'Backend only', value: 'backend' }, + { name: 'Frontend only', value: 'frontend' }, + ], + }) + + const doBackend = scaffoldChoice === 'both' || scaffoldChoice === 'backend' + const doFrontend = scaffoldChoice === 'both' || scaffoldChoice === 'frontend' + + // --- Step 3: Backend language --- + let language = 'java' + let groupId = 'com.example' + let artifactId = projectName + let version = '1.0.0-SNAPSHOT' + let description = '' + + if (doBackend) { + const backendTemplates = config.templates.backend + const languageChoices = Object.entries(backendTemplates).map(([id, entry]) => ({ + name: entry.label, + value: id, + description: entry.enabled ? entry.description : `${entry.description} β€” ${entry.availabilityMessage}`, + disabled: entry.enabled ? false : entry.availabilityMessage, + })) + + if (!languageChoices.some((choice) => !choice.disabled)) { + errorWithCode('DT-BACKEND-005', 'Backend templates are not available yet. Please try frontend only for now.') + } + + language = await select({ + message: 'Backend language:', + choices: languageChoices, + }) + + // --- Step 4: Maven coordinates --- + groupId = await input({ + message: 'Group ID:', + default: 'com.example', + }) + + artifactId = await input({ + message: 'Artifact ID:', + default: projectName, + }) + + version = await input({ + message: 'Version:', + default: '1.0.0-SNAPSHOT', + }) + + description = await input({ + message: 'Description (optional):', + default: '', + }) + } + + // --- Step 5: Frontend framework --- + let framework = 'vue' + let packageManager = 'pnpm' + + if (doFrontend) { + const frontendTemplates = config.templates.frontend + const frameworkChoices = Object.entries(frontendTemplates).map(([id, entry]) => ({ + name: entry.label, + value: id, + description: entry.enabled ? entry.description : `${entry.description} β€” ${entry.availabilityMessage}`, + disabled: entry.enabled ? false : entry.availabilityMessage, + })) + + if (!frameworkChoices.some((choice) => !choice.disabled)) { + errorWithCode('DT-FRONTEND-005', 'Frontend templates are not available yet. Please try again soon.') + } + + framework = await select({ + message: 'Frontend framework:', + choices: frameworkChoices, + }) + + // --- Step 6: Package manager --- + packageManager = await select({ + message: 'Package manager:', + choices: [ + { name: 'pnpm', value: 'pnpm' }, + { name: 'npm', value: 'npm' }, + { name: 'yarn', value: 'yarn' }, + ], + }) + } + + // --- Step 7: Resolve Spring Boot version for backend summary and generation --- + let springBootVersion = '' + if (doBackend) { + springBootVersion = normalizeSpringBootVersion(await fetchSpringBootVersion(config)) + } + + // --- Step 8: Confirm --- + console.log('') + console.log(' Summary:') + console.log(` Project: ${projectName}`) + if (doBackend) { + console.log(` Backend: ${language} | ${groupId}:${artifactId}:${version} | Spring Boot ${springBootVersion}`) + } + if (doFrontend) { + console.log(` Frontend: ${framework} | ${packageManager}`) + } + console.log('') + + const confirmed = await confirm({ + message: 'Generate project?', + default: true, + }) + + if (!confirmed) { + info('Cancelled.') + process.exit(0) + } + + // Target directory is CWD / projectName + const targetDir = join(process.cwd(), projectName) + + // Generate backend + try { + if (doBackend) { + await generateBackend({ + projectName, + language, + groupId, + artifactId, + version, + description, + targetDir: join(targetDir, 'backend'), + config, + springBootVersion, + }) + } + + // Generate frontend + if (doFrontend) { + await generateFrontend({ + projectName, + framework, + packageManager, + targetDir: join(targetDir, 'frontend'), + config, + }) + } + } catch (err) { + const reason = errorMessage(err, 'DT-RUN-001') + error(`${reason}\nIf this keeps happening, please open an issue at https://github.com/dynamiatools/framework/issues`) + } + + printSuccessMessage({ + projectName, + generateBackend: doBackend, + generateFrontend: doFrontend, + language, + framework, + packageManager, + config, + springBootVersion, + }) + + const initGit = await confirm({ + message: 'Initialize a Git repository in the project root?', + default: true, + }) + + if (!initGit) { + info('Git initialization skipped.') + return + } + + const gitDir = join(targetDir, '.git') + if (existsSync(gitDir)) { + info('Git repository is already initialized.') + return + } + + try { + await execa('git', ['init'], { cwd: targetDir, stdout: 'pipe', stderr: 'pipe' }) + success(`Git repository initialized at ${targetDir}`) + } catch (err) { + warn(`Project was created, but Git initialization failed: ${errorMessage(err, 'DT-RUN-001')}`) + } +} diff --git a/platform/packages/cli/src/generators/backend.ts b/platform/packages/cli/src/generators/backend.ts new file mode 100644 index 00000000..afd899dc --- /dev/null +++ b/platform/packages/cli/src/generators/backend.ts @@ -0,0 +1,113 @@ +import { join } from 'node:path' +import { execa } from 'execa' +import { rmSync, mkdirSync } from 'node:fs' +import { type CliConfig } from '../utils/config.js' +import { renameJavaPackages } from '../utils/replace.js' +import { spin, success, friendlyGitError, notAvailableYet, cliError, errorMessage } from '../utils/logger.js' +import { validateTemplateRepo } from '../utils/template-repo.js' + +export interface BackendOptions { + projectName: string + language: string + groupId: string + artifactId: string + version: string + description: string + targetDir: string + config: CliConfig + springBootVersion: string +} + +/** + * Clone a backend template from GitHub and rename packages/tokens. + */ +async function cloneBackendTemplate( + language: string, + targetDir: string, + config: CliConfig, +): Promise { + const template = config.templates.backend[language] + if (!template) { + throw cliError('DT-BACKEND-001', `Unknown backend language: ${language}.`) + } + if (!template.enabled) { + throw cliError('DT-BACKEND-002', template.availabilityMessage) + } + + const validation = await validateTemplateRepo({ + repo: template.repo, + branch: template.branch, + }) + if (!validation.ok) { + throw cliError( + 'DT-TEMPLATE-001', + `Backend template validation failed for ${template.repo}#${template.branch}. ${validation.reason ?? ''}`.trim(), + ) + } + + await execa('git', [ + 'clone', + '--depth=1', + '--branch', + template.branch, + template.repo, + targetDir, + ]) + + // Remove .git directory so the project starts fresh + rmSync(join(targetDir, '.git'), { recursive: true, force: true }) +} + +/** + * Generate the backend project by cloning a template and performing token replacement. + */ +export async function generateBackend(options: BackendOptions): Promise { + const { + projectName, + language, + groupId, + artifactId, + version, + description, + targetDir, + config, + springBootVersion, + } = options + + const spinner = spin(`Cloning backend template (${language})…`) + + try { + mkdirSync(targetDir, { recursive: true }) + await cloneBackendTemplate(language, targetDir, config) + spinner.succeed('Backend template cloned') + } catch (err) { + spinner.fail('Could not prepare backend template') + const reason = friendlyGitError(err) + if (config.beta.notAvailableMessageEnabled) { + notAvailableYet('Automatic backend generation for this template setup', 'DT-BACKEND-003') + } + throw cliError('DT-BACKEND-003', `${reason}\nTip: Please verify template.backend.${language}.* entries in cli.properties.`) + } + + const renameSpinner = spin('Renaming packages and replacing tokens…') + try { + await renameJavaPackages({ + projectDir: targetDir, + projectName, + groupId, + artifactId, + version, + description, + dynamiaVersion: config.dynamia.version, + springBootVersion, + language: language as 'java' | 'kotlin' | 'groovy', + tokens: config.tokens, + }) + renameSpinner.succeed('Packages renamed') + } catch (err) { + renameSpinner.fail('Failed to rename packages') + throw cliError('DT-BACKEND-004', `Backend token/package replacement failed: ${errorMessage(err)}`) + } + + success(`Backend (${language}) generated at ${targetDir}`) +} diff --git a/platform/packages/cli/src/generators/frontend.ts b/platform/packages/cli/src/generators/frontend.ts new file mode 100644 index 00000000..0eee68a4 --- /dev/null +++ b/platform/packages/cli/src/generators/frontend.ts @@ -0,0 +1,147 @@ +import { join } from 'node:path' +import { rmSync, mkdirSync } from 'node:fs' +import { execa } from 'execa' +import { type CliConfig } from '../utils/config.js' +import { replaceTokensInDir } from '../utils/replace.js' +import { spin, warn, success, friendlyGitError, cliError, errorMessage } from '../utils/logger.js' +import { validateTemplateRepo } from '../utils/template-repo.js' + +// Fallback package names used when cli.properties entries are unavailable +const DEFAULT_SDK_PACKAGE = '@dynamia-tools/sdk' +const DEFAULT_UI_CORE_PACKAGE = '@dynamia-tools/ui-core' + +export interface FrontendOptions { + projectName: string + framework: string + packageManager: string + targetDir: string + config: CliConfig +} + +/** + * Clone a frontend template from GitHub. + */ +async function cloneFrontendTemplate( + framework: string, + targetDir: string, + config: CliConfig, +): Promise { + const template = config.templates.frontend[framework] + if (!template) { + throw cliError('DT-FRONTEND-001', `Unknown frontend framework: ${framework}.`) + } + if (!template.enabled) { + throw cliError('DT-FRONTEND-002', template.availabilityMessage) + } + + const validation = await validateTemplateRepo({ + repo: template.repo, + branch: template.branch, + }) + if (!validation.ok) { + throw cliError( + 'DT-TEMPLATE-002', + `Frontend template validation failed for ${template.repo}#${template.branch}. ${validation.reason ?? ''}`.trim(), + ) + } + + await execa('git', [ + 'clone', + '--depth=1', + '--branch', + template.branch, + template.repo, + targetDir, + ]) + + rmSync(join(targetDir, '.git'), { recursive: true, force: true }) +} + +/** + * Fallback: scaffold frontend using Vite when the git clone fails. + */ +async function generateWithVite( + framework: string, + targetDir: string, + pm: string, + config: CliConfig, +): Promise { + const viteTemplate = config.vite.templates[framework] + if (!viteTemplate) { + throw cliError('DT-FRONTEND-003', `No Vite template configured for framework: ${framework}`) + } + + await execa(pm, ['create', 'vite@latest', targetDir, '--template', viteTemplate], { + stdio: 'inherit', + }) + + // Install Dynamia SDK packages + const sdkPkg = config.npm['sdk']?.package ?? DEFAULT_SDK_PACKAGE + const uiPkg = config.npm['ui-core']?.package ?? DEFAULT_UI_CORE_PACKAGE + await execa(pm, ['install', sdkPkg, uiPkg], { + cwd: targetDir, + stdio: 'inherit', + }) +} + +/** + * Generate the frontend project. + * Tries to clone the template first; falls back to Vite if clone fails (and fallback is enabled). + */ +export async function generateFrontend(options: FrontendOptions): Promise { + const { projectName, framework, packageManager, targetDir, config } = options + + const tokenMap: Record = { + '{{PROJECT_NAME}}': projectName, + } + + // Map standard tokens too + for (const [key, placeholder] of Object.entries(config.tokens)) { + if (key === 'projectName') { + tokenMap[placeholder] = projectName + } + } + + mkdirSync(targetDir, { recursive: true }) + + let cloneSuccess = false + const spinner = spin(`Cloning frontend template (${framework})…`) + + try { + await cloneFrontendTemplate(framework, targetDir, config) + spinner.succeed('Frontend template cloned') + cloneSuccess = true + } catch (err) { + spinner.warn(`Could not clone frontend template: ${friendlyGitError(err)}`) + } + + if (!cloneSuccess) { + if (!config.vite.fallbackEnabled) { + throw cliError( + 'DT-FRONTEND-003', + 'Frontend template is currently not available and Vite fallback is disabled. Please try again later or enable vite.fallback.enabled in cli.properties.', + ) + } + warn('Template is unavailable right now, so we will use Vite fallback.') + const viteSpinner = spin('Scaffolding with Vite…') + try { + await generateWithVite(framework, targetDir, packageManager, config) + viteSpinner.succeed('Vite scaffold complete') + } catch (err) { + viteSpinner.fail('Vite scaffold failed') + throw cliError('DT-FRONTEND-004', `Vite fallback failed: ${errorMessage(err)}`) + } + } else { + // Replace tokens in cloned template + const replaceSpinner = spin('Replacing project tokens…') + try { + replaceTokensInDir(targetDir, tokenMap) + replaceSpinner.succeed('Tokens replaced') + } catch (err) { + replaceSpinner.fail('Token replacement failed') + throw cliError('DT-FRONTEND-004', `Frontend token replacement failed: ${errorMessage(err)}`) + } + } + + success(`Frontend (${framework}) generated at ${targetDir}`) +} diff --git a/platform/packages/cli/src/index.ts b/platform/packages/cli/src/index.ts new file mode 100644 index 00000000..ad169ae4 --- /dev/null +++ b/platform/packages/cli/src/index.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import { runNew } from './commands/new.js' +import { errorWithCode, notAvailableYet } from './utils/logger.js' + +const [, , ...args] = process.argv +const command = args[0] ?? 'new' + +switch (command) { + case 'new': + await runNew() + break + default: + notAvailableYet(`Command "${command}"`, 'DT-COMMAND-001') + errorWithCode('DT-COMMAND-002', 'Usage: dynamia new') +} diff --git a/platform/packages/cli/src/utils/config.ts b/platform/packages/cli/src/utils/config.ts new file mode 100644 index 00000000..da9637fb --- /dev/null +++ b/platform/packages/cli/src/utils/config.ts @@ -0,0 +1,187 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface TemplateEntry { + label: string + repo: string + branch: string + description: string + enabled: boolean + availabilityMessage: string +} + +export interface CliConfig { + dynamia: { version: string; docsUrl: string } + beta: { + enabled: boolean + introMessageEnabled: boolean + notAvailableMessageEnabled: boolean + } + java: { version: string; sdkmanCandidate: string } + node: { minimumVersion: string } + installer: { sdkmanUrl: string; fnmUrl: string } + templates: { + backend: Record + frontend: Record + } + npm: Record + vite: { fallbackEnabled: boolean; templates: Record } + tokens: Record +} + +// --------------------------------------------------------------------------- +// Parser +// --------------------------------------------------------------------------- + +function parseProperties(content: string): Record { + const result: Record = {} + for (const raw of content.split('\n')) { + const line = raw.trim() + if (!line || line.startsWith('#')) continue + const eqIdx = line.indexOf('=') + if (eqIdx === -1) continue + const key = line.slice(0, eqIdx).trim() + const value = line.slice(eqIdx + 1).trim() + result[key] = value + } + return result +} + +// --------------------------------------------------------------------------- +// Loader +// --------------------------------------------------------------------------- + +export async function loadConfig(): Promise { + // Locate cli.properties relative to this compiled file (dist/utils/config.js) + const __filename = fileURLToPath(import.meta.url) + const __dirname = dirname(__filename) + const propsPath = resolve(__dirname, '../../cli.properties') + + const content = readFileSync(propsPath, 'utf-8') + const props = parseProperties(content) + + // --- helpers --- + const get = (key: string, fallback = ''): string => props[key] ?? fallback + + // --- templates --- + const backendTemplates: Record = {} + const frontendTemplates: Record = {} + + const createTemplate = (): TemplateEntry => ({ + label: '', + repo: '', + branch: 'main', + description: '', + enabled: true, + availabilityMessage: 'Not available yet in this beta release.', + }) + + for (const [key, value] of Object.entries(props)) { + const backMatch = key.match(/^template\.backend\.(\w+)\.(label|repo|branch|description|enabled|availabilityMessage)$/) + if (backMatch) { + const id = backMatch[1]! + const field = backMatch[2]! + if (!backendTemplates[id]) { + backendTemplates[id] = createTemplate() + } + if (field === 'enabled') { + backendTemplates[id]!.enabled = value !== 'false' + } else if (field === 'availabilityMessage') { + backendTemplates[id]!.availabilityMessage = value + } else { + backendTemplates[id]![field as 'label' | 'repo' | 'branch' | 'description'] = value + } + continue + } + + const frontMatch = key.match(/^template\.frontend\.(\w+)\.(label|repo|branch|description|enabled|availabilityMessage)$/) + if (frontMatch) { + const id = frontMatch[1]! + const field = frontMatch[2]! + if (!frontendTemplates[id]) { + frontendTemplates[id] = createTemplate() + } + if (field === 'enabled') { + frontendTemplates[id]!.enabled = value !== 'false' + } else if (field === 'availabilityMessage') { + frontendTemplates[id]!.availabilityMessage = value + } else { + frontendTemplates[id]![field as 'label' | 'repo' | 'branch' | 'description'] = value + } + } + } + + // --- npm packages --- + const npmPackages: Record = {} + for (const [key, value] of Object.entries(props)) { + const m = key.match(/^npm\.(\w[\w-]*)\.(\w+)$/) + if (m) { + const id = m[1]! + const field = m[2]! + if (!npmPackages[id]) npmPackages[id] = { package: '', version: '' } + if (field === 'package' || field === 'version') { + npmPackages[id]![field] = value + } + } + } + + // --- vite fallback templates --- + const viteTemplates: Record = {} + for (const [key, value] of Object.entries(props)) { + const m = key.match(/^vite\.templates\.(\w+)$/) + if (m) { + viteTemplates[m[1]!] = value + } + } + + // --- tokens --- + const tokens: Record = {} + for (const [key, value] of Object.entries(props)) { + if (key.startsWith('token.')) { + // Convert "token.group.id" β†’ "groupId" camelCase key for easy lookup + const parts = key.slice('token.'.length).split('.').filter((p) => p.length > 0) + const camel = parts + .map((p, i) => (i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1))) + .join('') + tokens[camel] = value + } + } + + return { + dynamia: { + version: get('dynamia.version'), + docsUrl: get('dynamia.docs.url'), + }, + beta: { + enabled: get('beta.enabled', 'true') === 'true', + introMessageEnabled: get('beta.intro.message.enabled', 'true') === 'true', + notAvailableMessageEnabled: get('beta.not.available.message.enabled', 'true') === 'true', + }, + java: { + version: get('java.version'), + sdkmanCandidate: get('java.sdkman.candidate'), + }, + node: { + minimumVersion: get('node.minimum.version'), + }, + installer: { + sdkmanUrl: get('installer.sdkman.url'), + fnmUrl: get('installer.fnm.url'), + }, + templates: { + backend: backendTemplates, + frontend: frontendTemplates, + }, + npm: npmPackages, + vite: { + fallbackEnabled: get('vite.fallback.enabled') === 'true', + templates: viteTemplates, + }, + tokens, + } +} diff --git a/platform/packages/cli/src/utils/env.ts b/platform/packages/cli/src/utils/env.ts new file mode 100644 index 00000000..75b744ca --- /dev/null +++ b/platform/packages/cli/src/utils/env.ts @@ -0,0 +1,76 @@ +import { execa } from 'execa' +import { warn, error, info } from './logger.js' + +/** + * Check that JDK 25 is available. + * Returns true if found. Logs a warning (not an error) if a different version is present. + */ +export async function checkJava(): Promise { + try { + const result = await execa('java', ['-version'], { stderr: 'pipe', stdout: 'pipe' }) + // java -version writes to stderr + const output = result.stderr + result.stdout + if (output.includes('25')) { + return true + } + warn('JDK 25 not detected. A different Java version is installed. Some features may not work as expected.') + return true // not a hard stop β€” just warn + } catch { + warn('Java is not installed. Install JDK 25 via SDKMAN: sdk install java 25-tem') + return false + } +} + +/** + * Check that Node.js >= 22 is available. + * Returns true if the requirement is satisfied. + */ +export async function checkNode(): Promise { + try { + const result = await execa('node', ['--version'], { stdout: 'pipe' }) + const version = result.stdout.trim().replace(/^v/, '') + const majorStr = version.split('.')[0] + const major = majorStr && majorStr.length > 0 ? parseInt(majorStr, 10) : 0 + if (!isNaN(major) && major >= 22) { + return true + } + warn(`Node.js ${version} found but version >=22 is required. Please upgrade Node.js.`) + return false + } catch { + warn('Node.js is not installed. Install it via fnm: curl -fsSL https://fnm.vercel.app/install | bash') + return false + } +} + +/** + * Check that git is available. + * Returns true if found. Exits the process if git is missing β€” it is REQUIRED. + */ +export async function checkGit(): Promise { + try { + await execa('git', ['--version'], { stdout: 'pipe' }) + return true + } catch { + error( + 'git is not installed. git is required for template cloning.\n' + + ' Install it with your package manager:\n' + + ' Ubuntu/Debian: sudo apt-get install git\n' + + ' macOS: brew install git\n' + + ' Fedora: sudo dnf install git\n' + + ' Arch: sudo pacman -S git', + ) + // error() calls process.exit(1) β€” this line is never reached + return false + } +} + +/** + * Run all environment checks at startup. + * git missing = hard stop; other warnings are non-fatal. + */ +export async function runChecks(): Promise { + info('Checking environment...') + await checkGit() // exits if missing + await checkJava() // warning only + await checkNode() // warning only +} diff --git a/platform/packages/cli/src/utils/logger.ts b/platform/packages/cli/src/utils/logger.ts new file mode 100644 index 00000000..7ab449fc --- /dev/null +++ b/platform/packages/cli/src/utils/logger.ts @@ -0,0 +1,128 @@ +import chalk from 'chalk' +import ora, { type Ora } from 'ora' + +export type CliErrorCode = + | 'DT-CONFIG-001' + | 'DT-COMMAND-001' + | 'DT-COMMAND-002' + | 'DT-RUN-001' + | 'DT-BACKEND-001' + | 'DT-BACKEND-002' + | 'DT-BACKEND-003' + | 'DT-BACKEND-004' + | 'DT-BACKEND-005' + | 'DT-FRONTEND-001' + | 'DT-FRONTEND-002' + | 'DT-FRONTEND-003' + | 'DT-FRONTEND-004' + | 'DT-FRONTEND-005' + | 'DT-TEMPLATE-001' + | 'DT-TEMPLATE-002' + | 'DT-UNKNOWN-001' + +export class CliError extends Error { + constructor( + public readonly code: CliErrorCode, + message: string, + ) { + super(message) + this.name = 'CliError' + } +} + +export function cliError(code: CliErrorCode, message: string): CliError { + return new CliError(code, message) +} + +export function withErrorCode(code: CliErrorCode, message: string): string { + return `[${code}] ${message}` +} + +/** Start a spinner and return the Ora instance so callers can stop it. */ +export function spin(text: string): Ora { + return ora(text).start() +} + +/** Print a green success message. */ +export function success(msg: string): void { + console.log(chalk.green(`βœ“ ${msg}`)) +} + +/** Print a yellow warning message. */ +export function warn(msg: string): void { + console.warn(chalk.yellow(`⚠ ${msg}`)) +} + +/** Print a red error message and exit with code 1. */ +export function error(msg: string): never { + console.error(chalk.red(`βœ— ${msg}`)) + process.exit(1) +} + +export function errorWithCode(code: CliErrorCode, message: string): never { + return error(withErrorCode(code, message)) +} + +/** Print a cyan informational message. */ +export function info(msg: string): void { + console.log(chalk.cyan(`β„Ή ${msg}`)) +} + +/** Print a beta notice in a friendly tone. */ +export function beta(msg: string): void { + console.log(chalk.magenta(`Ξ² ${msg}`)) +} + +/** Print a standard message for features not available in beta yet. */ +export function notAvailableYet(feature: string, code?: CliErrorCode): void { + const message = `${feature} is not available yet in this beta release. We're actively working on it.` + warn(code ? withErrorCode(code, message) : message) +} + +/** Convert unknown errors into user-friendly text. */ +export function errorMessage(err: unknown, fallbackCode: CliErrorCode = 'DT-UNKNOWN-001'): string { + if (err instanceof CliError) { + return withErrorCode(err.code, err.message) + } + if (err instanceof Error) { + const maybeShort = err.message.trim() + if (maybeShort.length > 0) return withErrorCode(fallbackCode, maybeShort) + return withErrorCode(fallbackCode, 'Unexpected error.') + } + return withErrorCode(fallbackCode, 'Unexpected error.') +} + +/** Improve common git-related raw errors for end users. */ +export function friendlyGitError(err: unknown): string { + const raw = errorMessage(err) + const normalized = raw.toLowerCase() + + if (normalized.includes('repository not found')) { + return 'Template repository not found. Please verify the template repository URL in cli.properties.' + } + if (normalized.includes('could not resolve host')) { + return 'Could not reach the template repository host. Please check your internet connection and DNS settings.' + } + if (normalized.includes('remote branch') && normalized.includes('not found')) { + return 'Template branch was not found in the repository. Please verify the configured branch in cli.properties.' + } + if (normalized.includes('authentication failed') || normalized.includes('permission denied')) { + return 'Could not access the template repository. Check repository visibility and access permissions.' + } + + return raw +} + +/** Print the Dynamia Tools ASCII banner. */ +export function banner(): void { + const teal = chalk.hex('#00BCD4') + console.log(teal(' ____ _ _____ _ ')) + console.log(teal(' | _ \\ _ _ _ __ __ _ _ __ ___ (_) __ |_ _|__ ___ | |___ ')) + console.log(teal(' | | | | | | | \'_ \\ / _` | \'_ ` _ \\| |/ _` || |/ _ \\ / _ \\| / __|')) + console.log(teal(' | |_| | |_| | | | | (_| | | | | | | | (_| || | (_) | (_) | \\__ \\')) + console.log(teal(' |____/ \\__, |_| |_|\\__,_|_| |_| |_|_|\\__,_||_|\\___/ \\___/|_|___/')) + console.log(teal(' |___/ ')) + console.log('') + console.log(chalk.bold(' Dynamia Tools CLI')) + console.log('') +} diff --git a/platform/packages/cli/src/utils/replace.ts b/platform/packages/cli/src/utils/replace.ts new file mode 100644 index 00000000..ba539eff --- /dev/null +++ b/platform/packages/cli/src/utils/replace.ts @@ -0,0 +1,270 @@ +import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, renameSync, rmdirSync } from 'node:fs' +import { join, extname } from 'node:path' + +// File extensions that will have token replacement applied +const TEXT_EXTENSIONS = new Set([ + '.java', '.kt', '.groovy', + '.xml', '.yml', '.yaml', '.properties', + '.md', '.txt', '.json', '.gradle', '.kts', +]) + +/** + * Replace all token occurrences in a single text file. + * Non-text files (by extension) are silently skipped. + */ +function replaceInFile(filePath: string, tokenMap: Record): void { + const ext = extname(filePath).toLowerCase() + if (!TEXT_EXTENSIONS.has(ext)) return + + let content: string + try { + content = readFileSync(filePath, 'utf-8') + } catch { + return // skip unreadable files + } + + let updated = content + for (const [token, replacement] of Object.entries(tokenMap)) { + updated = updated.split(token).join(replacement) + } + + if (updated !== content) { + writeFileSync(filePath, updated, 'utf-8') + } +} + +/** + * Recursively walk a directory and apply token replacement to all text files. + */ +export function replaceTokensInDir( + dir: string, + tokenMap: Record, +): void { + const entries = readdirSync(dir) + for (const entry of entries) { + const fullPath = join(dir, entry) + const stat = statSync(fullPath) + if (stat.isDirectory()) { + replaceTokensInDir(fullPath, tokenMap) + } else { + replaceInFile(fullPath, tokenMap) + } + } +} + +export interface RenameOptions { + projectDir: string + projectName: string + groupId: string + artifactId: string + version: string + description: string + dynamiaVersion: string + springBootVersion: string + language: 'java' | 'kotlin' | 'groovy' + /** Token map from cli.properties (e.g. { groupId: '{{GROUP_ID}}', ... }) */ + tokens: Record +} + +/** + * Perform full package renaming and token replacement after cloning a backend template. + * + * Steps: + * 1. Compute the base package from groupId + artifactId + * 2. Rename the source/test directories to match the new package + * 3. Replace all tokens in all text files + * 4. Rename the main Application class file + */ +export async function renameJavaPackages(options: RenameOptions): Promise { + const { + projectDir, + projectName, + groupId, + artifactId, + version, + description, + dynamiaVersion, + springBootVersion, + language, + tokens, + } = options + + // 1. Compute base package: "com.mycompany" + "my-erp" β†’ "com.mycompany.my.erp" + const basePackage = `${groupId}.${artifactId.replace(/-/g, '.')}` + const basePackagePath = basePackage.replace(/\./g, '/') + + // 2. Rename source directories + const srcExtension = language === 'kotlin' ? 'kotlin' : language === 'groovy' ? 'groovy' : 'java' + const oldPackagePath = 'com/example/demo' + const srcDirs = [ + join(projectDir, 'src', 'main', srcExtension), + join(projectDir, 'src', 'test', srcExtension), + ] + + for (const srcBase of srcDirs) { + const oldDir = join(srcBase, oldPackagePath) + const newDir = join(srcBase, basePackagePath) + + try { + statSync(oldDir) + } catch { + continue // directory doesn't exist β€” skip + } + + mkdirSync(newDir, { recursive: true }) + + // Move all files from oldDir to newDir recursively + moveDirectoryContents(oldDir, newDir) + + // Clean up old (now empty) package directories + removeEmptyDirs(join(srcBase, 'com')) + } + + // 3. Build token map: map cli.properties token values to user values + const replacements: Record = {} + + for (const [key, placeholder] of Object.entries(tokens)) { + switch (key) { + case 'groupId': replacements[placeholder] = groupId; break + case 'artifactId': replacements[placeholder] = artifactId; break + case 'basePackage': replacements[placeholder] = basePackage; break + case 'projectName': replacements[placeholder] = projectName; break + case 'projectVersion': replacements[placeholder] = version; break + case 'projectDescription': replacements[placeholder] = description; break + case 'dynamiaVersion': replacements[placeholder] = dynamiaVersion; break + case 'springBootVersion': replacements[placeholder] = springBootVersion; break + } + } + + // Fallbacks for templates that still use Spring Initializr defaults instead of placeholders. + replacements['com.example'] = `${groupId}` + replacements['demo'] = `${artifactId}` + replacements['0.0.1-SNAPSHOT'] = `${version}` + replacements['DynamiaTools App Backend'] = `${projectName}` + replacements['26.4.1'] = `${dynamiaVersion}` + replacements['4.0.5'] = `${springBootVersion}` + replacements['4.0.5.RELEASE'] = `${springBootVersion}` + + // Always replace the placeholder package string itself in source files + replacements['com.example.demo'] = basePackage + replacements['com/example/demo'] = basePackagePath + + // 4. Replace tokens in all text files + replaceTokensInDir(projectDir, replacements) + + // Ensure a user-provided description updates pom.xml even when template has multiline defaults. + replacePomDescription(projectDir, description) + + // 5. Rename the main Application class file + renameApplicationClass(projectDir, artifactId, basePackagePath, srcExtension) +} + +/** + * Move all files and subdirectories from src into dest. + */ +function moveDirectoryContents(src: string, dest: string): void { + const entries = readdirSync(src) + for (const entry of entries) { + const srcPath = join(src, entry) + const destPath = join(dest, entry) + const stat = statSync(srcPath) + if (stat.isDirectory()) { + mkdirSync(destPath, { recursive: true }) + moveDirectoryContents(srcPath, destPath) + try { rmdirSync(srcPath) } catch { /* not empty β€” skip */ } + } else { + renameSync(srcPath, destPath) + } + } +} + +/** + * Remove empty directories recursively (best-effort). + */ +function removeEmptyDirs(dir: string): void { + try { + const entries = readdirSync(dir) + for (const entry of entries) { + const full = join(dir, entry) + if (statSync(full).isDirectory()) { + removeEmptyDirs(full) + } + } + try { rmdirSync(dir) } catch { /* not empty or doesn't exist */ } + } catch { /* ignore */ } +} + +/** + * Update pom.xml description content when template uses a hardcoded multiline block. + */ +function replacePomDescription(projectDir: string, description: string): void { + if (!description) return + + const pomPath = join(projectDir, 'pom.xml') + let pomContent: string + try { + pomContent = readFileSync(pomPath, 'utf-8') + } catch { + return + } + + const normalized = description.trim().replace(/[\r\n]+/g, ' ') + const updated = pomContent.replace(/[\s\S]*?<\/description>/, `${normalized}`) + + if (updated !== pomContent) { + writeFileSync(pomPath, updated, 'utf-8') + } +} + +/** + * Rename DemoApplication.java β†’ MyErpApplication.java (or .kt / .groovy). + */ +function renameApplicationClass( + projectDir: string, + artifactId: string, + basePackagePath: string, + srcExtension: string, +): void { + // Build new class name: "my-erp" β†’ "MyErp" β†’ "MyErpApplication" + const newClassName = + artifactId + .split('-') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + 'Application' + + const fileExtMap: Record = { + kotlin: '.kt', + groovy: '.groovy', + java: '.java', + } + const fileExt = fileExtMap[srcExtension] ?? '.java' + const oldFileName = `DemoApplication${fileExt}` + const newFileName = `${newClassName}${fileExt}` + const oldClassName = 'DemoApplication' + + const searchDir = join(projectDir, 'src', 'main', srcExtension, basePackagePath) + try { + const files = readdirSync(searchDir) + for (const file of files) { + if (file === oldFileName) { + const oldFilePath = join(searchDir, file) + const newFilePath = join(searchDir, newFileName) + + try { + const content = readFileSync(oldFilePath, 'utf-8') + const updated = content.split(oldClassName).join(newClassName) + if (updated !== content) { + writeFileSync(oldFilePath, updated, 'utf-8') + } + } catch { + // If class-content replacement fails, continue with file rename attempt. + } + + renameSync(oldFilePath, newFilePath) + break + } + } + } catch { + // Directory doesn't exist or file not found β€” skip silently + } +} diff --git a/platform/packages/cli/src/utils/template-repo.ts b/platform/packages/cli/src/utils/template-repo.ts new file mode 100644 index 00000000..3afec14a --- /dev/null +++ b/platform/packages/cli/src/utils/template-repo.ts @@ -0,0 +1,42 @@ +import { execa } from 'execa' + +export interface TemplateRef { + repo: string + branch: string +} + +export interface ValidationResult { + ok: boolean + reason?: string +} + +/** + * Validate that a git repository is reachable and that a branch exists before cloning. + */ +export async function validateTemplateRepo(template: TemplateRef): Promise { + try { + // Fast check for repo access. + await execa('git', ['ls-remote', '--heads', template.repo], { stdout: 'pipe', stderr: 'pipe' }) + } catch (err) { + const message = err instanceof Error ? err.message : 'Repository is not reachable.' + return { ok: false, reason: message } + } + + try { + const result = await execa( + 'git', + ['ls-remote', '--heads', template.repo, template.branch], + { stdout: 'pipe', stderr: 'pipe' }, + ) + + if (!result.stdout.trim()) { + return { ok: false, reason: `Branch "${template.branch}" was not found.` } + } + + return { ok: true } + } catch (err) { + const message = err instanceof Error ? err.message : 'Branch validation failed.' + return { ok: false, reason: message } + } +} + diff --git a/platform/packages/cli/tsconfig.json b/platform/packages/cli/tsconfig.json new file mode 100644 index 00000000..4174b878 --- /dev/null +++ b/platform/packages/cli/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/platform/packages/package.json b/platform/packages/package.json index dd63a183..4d9898df 100644 --- a/platform/packages/package.json +++ b/platform/packages/package.json @@ -1,6 +1,6 @@ { "name": "@dynamia-tools/platform-packages", - "version": "26.3.2", + "version": "26.4.0", "private": true, "description": "Platform packages workspace sub-directory (not a workspace root β€” see framework/package.json)", "license": "Apache-2.0" diff --git a/platform/packages/sdk/package-lock.json b/platform/packages/sdk/package-lock.json index 89357dfe..4e95bbdd 100644 --- a/platform/packages/sdk/package-lock.json +++ b/platform/packages/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dynamia-tools/sdk", - "version": "26.3.1", + "version": "26.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dynamia-tools/sdk", - "version": "26.3.1", + "version": "26.4.1", "license": "Apache-2.0", "devDependencies": { "@types/node": "^22.0.0", diff --git a/platform/packages/sdk/package.json b/platform/packages/sdk/package.json index 9c0bd382..3ed457c3 100644 --- a/platform/packages/sdk/package.json +++ b/platform/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dynamia-tools/sdk", - "version": "26.3.2", + "version": "26.4.1", "website": "https://dynamia.tools", "description": "Official JavaScript / TypeScript client SDK for Dynamia Platform REST APIs", "keywords": [ diff --git a/platform/packages/ui-core/package.json b/platform/packages/ui-core/package.json index c2df2d2c..a317d131 100644 --- a/platform/packages/ui-core/package.json +++ b/platform/packages/ui-core/package.json @@ -1,10 +1,21 @@ { "name": "@dynamia-tools/ui-core", - "version": "26.3.2", + "version": "26.4.1", "description": "Framework-agnostic view/viewer/renderer core for Dynamia Platform", - "keywords": ["dynamia", "ui-core", "viewer", "view", "renderer", "typescript"], + "keywords": [ + "dynamia", + "ui-core", + "viewer", + "view", + "renderer", + "typescript" + ], "homepage": "https://dynamia.tools", - "repository": {"type": "git", "url": "https://github.com/dynamiatools/framework.git", "directory": "platform/packages/ui-core"}, + "repository": { + "type": "git", + "url": "https://github.com/dynamiatools/framework.git", + "directory": "platform/packages/ui-core" + }, "license": "Apache-2.0", "author": "Dynamia Soluciones IT SAS", "type": "module", @@ -14,11 +25,21 @@ "exports": { ".": { "development": "./src/index.ts", - "import": {"types": "./dist/index.d.ts", "default": "./dist/index.js"}, - "require": {"types": "./dist/index.d.cts", "default": "./dist/index.cjs"} + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } } }, - "files": ["dist", "README.md", "LICENSE"], + "files": [ + "dist", + "README.md", + "LICENSE" + ], "scripts": { "build": "vite build", "test": "vitest run", @@ -26,7 +47,9 @@ "typecheck": "tsc --noEmit", "clean": "rm -rf dist" }, - "dependencies": {"@dynamia-tools/sdk": "workspace:*"}, + "dependencies": { + "@dynamia-tools/sdk": "workspace:*" + }, "devDependencies": { "@dynamia-tools/sdk": "workspace:*", "@types/node": "^22.0.0", @@ -35,5 +58,8 @@ "vite-plugin-dts": "^4.5.0", "vitest": "^3.0.0" }, - "publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"} + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } } diff --git a/platform/packages/vue/package.json b/platform/packages/vue/package.json index 95139bc1..833195ef 100644 --- a/platform/packages/vue/package.json +++ b/platform/packages/vue/package.json @@ -1,10 +1,22 @@ { "name": "@dynamia-tools/vue", - "version": "26.3.2", + "version": "26.4.1", "description": "Vue 3 adapter for Dynamia Platform UI", - "keywords": ["dynamia", "vue", "viewer", "form", "table", "crud", "typescript"], + "keywords": [ + "dynamia", + "vue", + "viewer", + "form", + "table", + "crud", + "typescript" + ], "homepage": "https://dynamia.tools", - "repository": {"type": "git", "url": "https://github.com/dynamiatools/framework.git", "directory": "platform/packages/vue"}, + "repository": { + "type": "git", + "url": "https://github.com/dynamiatools/framework.git", + "directory": "platform/packages/vue" + }, "license": "Apache-2.0", "author": "Dynamia Soluciones IT SAS", "type": "module", @@ -14,11 +26,21 @@ "exports": { ".": { "development": "./src/index.ts", - "import": {"types": "./dist/index.d.ts", "default": "./dist/index.js"}, - "require": {"types": "./dist/index.d.cts", "default": "./dist/index.cjs"} + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } } }, - "files": ["dist", "README.md", "LICENSE"], + "files": [ + "dist", + "README.md", + "LICENSE" + ], "scripts": { "build": "vite build", "typecheck": "vue-tsc --noEmit", @@ -42,5 +64,8 @@ "vue": "^3.4.0", "vue-tsc": "^2.0.0" }, - "publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"} + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } } diff --git a/platform/starters/zk-starter/pom.xml b/platform/starters/zk-starter/pom.xml index 6bffe59d..30d2feed 100644 --- a/platform/starters/zk-starter/pom.xml +++ b/platform/starters/zk-starter/pom.xml @@ -4,7 +4,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -28,22 +28,22 @@ tools.dynamia tools.dynamia.app - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain.jpa - 26.4.0 + 26.4.1 org.hibernate.validator diff --git a/platform/ui/ui-shared/pom.xml b/platform/ui/ui-shared/pom.xml index 78c7c6a7..c8a03fec 100644 --- a/platform/ui/ui-shared/pom.xml +++ b/platform/ui/ui-shared/pom.xml @@ -23,7 +23,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../../../pom.xml @@ -64,17 +64,17 @@ tools.dynamia tools.dynamia.integration - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.commons - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.io - 26.4.0 + 26.4.1 diff --git a/platform/ui/zk/pom.xml b/platform/ui/zk/pom.xml index 8608a648..87b6df56 100644 --- a/platform/ui/zk/pom.xml +++ b/platform/ui/zk/pom.xml @@ -21,7 +21,7 @@ tools.dynamia.parent tools.dynamia - 26.4.0 + 26.4.1 ../../../pom.xml 4.0.0 @@ -99,31 +99,31 @@ tools.dynamia tools.dynamia.web - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.navigation - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.ui - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.domain - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.viewers - 26.4.0 + 26.4.1 org.yaml @@ -134,19 +134,19 @@ tools.dynamia tools.dynamia.crud - 26.4.0 + 26.4.1 tools.dynamia tools.dynamia.reports - 26.4.0 + 26.4.1 compile tools.dynamia tools.dynamia.templates - 26.4.0 + 26.4.1 compile diff --git a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/ProviderPickerBox.java b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/ProviderPickerBox.java index 4d521f1c..81e43ee9 100644 --- a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/ProviderPickerBox.java +++ b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/ProviderPickerBox.java @@ -22,22 +22,48 @@ import tools.dynamia.commons.BeanSorter; import tools.dynamia.commons.ObjectOperations; import tools.dynamia.commons.StringUtils; -import tools.dynamia.commons.reflect.ReflectionException; import tools.dynamia.integration.Containers; import tools.dynamia.zk.BindingComponentIndex; import tools.dynamia.zk.ComponentAliasIndex; import tools.dynamia.zk.util.ZKUtil; +import java.io.Serial; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; +import java.util.Objects; +/** + * A ZK Combobox component that discovers and lists implementations of a given + * provider interface or base class from the application's IoC container. + * + *

The component populates its items by querying {@link Containers#get()} + * for all objects assignable to the configured provider class. Each item is + * rendered using configurable field names for the identifier, display name, + * and optional icon. By default the fields are {@code id}, {@code name} and + * {@code icon} respectively.

+ * + *

The combobox is configured as read-only and will show the provider's + * display name while the underlying value stored for selection is the provider + * identifier (as resolved by {@link #idField}). The component registers the + * alias {@code providerpickerbox} for use in ZUL files and supports binding + * via the {@code selected} property.

+ * + *

Example ZUL usage

+ *
{@code
+ * 
+ * }
+ */ public class ProviderPickerBox extends Combobox { /** - * + * Serialization id. */ + @Serial private static final long serialVersionUID = 4710970528102748639L; static { @@ -45,38 +71,59 @@ public class ProviderPickerBox extends Combobox { BindingComponentIndex.getInstance().put("selected", ProviderPickerBox.class); } + /** + * The currently selected provider identifier. This value corresponds to the + * field defined by {@link #idField} for the selected provider instance. + */ private String selected; + + /** + * Fully-qualified class name of the provider interface or base class used to + * discover implementations in the IoC container. + */ private String className; + + /** + * Name of the provider field used as unique identifier. Defaults to {@code "id"}. + */ private String idField = "id"; + + /** + * Name of the provider field used for display label. Defaults to {@code "name"}. + */ private String nameField = "name"; + /** + * Name of the provider field used as an icon CSS class. Defaults to {@code "icon"}. + */ private String iconField = "icon"; + + /** + * Resolved provider {@link Class} corresponding to {@link #className}. May be {@code null} + * until {@link #setClassName(String)} is called with a valid class name. + */ private Class providerClass; + /** + * Constructs a new ProviderPickerBox. + * + *

The combobox is set to read-only and an item renderer is installed that + * resolves the item's id, name and icon using the configured field names. + * If the id cannot be resolved for an item an {@link UiException} is thrown + * during rendering.

+ */ public ProviderPickerBox() { setReadonly(true); setItemRenderer((item, data, index) -> { - String id, name, icon; - - try { - id = ObjectOperations.invokeGetMethod(data, idField).toString(); - } catch (ReflectionException | NullPointerException e) { - throw new UiException("Error loading ID field for " + data, e); - } - - try { - name = ObjectOperations.invokeGetMethod(data, nameField).toString(); - } catch (ReflectionException | NullPointerException e) { - name = id; + String id = getDataId(data); + if (id == null) { + throw new UiException(item + " has no id field named [" + idField + "]"); } - try { - icon = ObjectOperations.invokeGetMethod(data, iconField).toString(); - } catch (ReflectionException | NullPointerException e) { - icon = null; - } + String name = getDataName(data); + String icon = getDataIcon(data); if (name == null) { @@ -92,6 +139,15 @@ public ProviderPickerBox() { }); } + /** + * Initializes or refreshes the combobox model by locating all provider + * implementations from the IoC container and filling the component model. + * + *

The list is attempted to be sorted by {@link #nameField} using + * {@link BeanSorter}; if sorting fails the raw collection is used.

+ * + * @throws UiException if the lookup or model population fails + */ private void initModel() { if (providerClass != null) { try { @@ -112,10 +168,23 @@ private void initModel() { } + /** + * Returns the fully-qualified class name configured to discover provider + * implementations. + * + * @return the configured provider class name, or {@code null} if none set + */ public String getClassName() { return className; } + /** + * Sets the fully-qualified provider class name and immediately attempts to + * resolve the class and initialize the component model. + * + * @param className fully-qualified provider interface or base class name + * @throws UiException if the class cannot be found on the classpath + */ public void setClassName(String className) { this.className = className; try { @@ -126,33 +195,72 @@ public void setClassName(String className) { } } + /** + * Returns the name of the field used as the provider identifier. + * + * @return the id field name + */ public String getIdField() { return idField; } + /** + * Sets the name of the field used as the provider identifier and refreshes + * the component model. + * + * @param idField the identifier field name (getter must be available on provider objects) + */ public void setIdField(String idField) { this.idField = idField; initModel(); } + /** + * Returns the name of the field used as the provider display label. + * + * @return the display name field + */ public String getNameField() { return nameField; } + /** + * Sets the name of the field used as the provider display label and + * refreshes the component model. + * + * @param nameField the display name field + */ public void setNameField(String nameField) { this.nameField = nameField; initModel(); } + /** + * Returns the name of the field used as the provider icon CSS class. + * + * @return the icon field name + */ public String getIconField() { return iconField; } + /** + * Sets the name of the field used as the provider icon CSS class and + * refreshes the component model. + * + * @param iconField the icon field name + */ public void setIconField(String iconField) { this.iconField = iconField; initModel(); } + /** + * Returns the identifier of the currently selected provider item, or + * {@code null} if nothing is selected. + * + * @return selected provider id or {@code null} + */ public String getSelected() { selected = null; if (getSelectedItem() != null) { @@ -161,22 +269,79 @@ public String getSelected() { return selected; } + /** + * Programmatically selects the item whose identifier matches the provided + * {@code selected} value. If a matching item is found it is added to the + * selection of the underlying {@link ListModelList}. + * + * @param selected provider identifier to select + */ + @SuppressWarnings({"unchecked", "rawtypes"}) public void setSelected(String selected) { - if (selected != this.selected) { + if (!Objects.equals(selected, this.selected)) { this.selected = selected; - try { - Optional provider = Containers.get().findObjects(providerClass) - .stream() - .filter(p -> selected.equals(ObjectOperations.invokeGetMethod(p, idField))) - .findFirst(); - if (provider.isPresent()) { - ListModelList model = (ListModelList) getModel(); - //noinspection unchecked - model.addToSelection(provider.get()); - } - } catch (Exception ignored) { + if (getModel() instanceof ListModelList model) { + model.stream().filter(item -> Objects.equals(getDataId(item), selected)) + .findFirst() + .ifPresent(model::addToSelection); + } + } + } + + /** + * Returns the provider item's identifier by reading the configured {@code field} + * from the given {@code item} using reflection utilities. + * + * @param data provider instance to inspect + * @return identifier as string or {@code null} when it cannot be resolved + */ + protected String getDataId(Object data) { + return getDataField(data, idField); + } + /** + * Returns the provider item's display name by reading the configured + * {@link #nameField} from the given {@code item}. + * + * @param data provider instance to inspect + * @return display name as string or {@code null} when it cannot be resolved + */ + protected String getDataName(Object data) { + return getDataField(data, nameField); + } + + /** + * Returns the provider item's icon CSS class by reading the configured + * {@link #iconField} from the given {@code item}. + * + * @param data provider instance to inspect + * @return icon css class as string or {@code null} when it cannot be resolved + */ + protected String getDataIcon(Object data) { + return getDataField(data, iconField); + } + + /** + * Generic helper that reads a named field value from an object using + * {@link ObjectOperations#invokeGetMethod(Object, String)} and returns + * its string representation when present. + * + * @param data object to inspect + * @param field field name to read (getter must exist) + * @return field value as string or {@code null} if not found or not accessible + */ + protected String getDataField(Object data, String field) { + try { + if (data != null) { + Object fieldValue = ObjectOperations.invokeGetMethod(data, field); + if (fieldValue != null) { + return fieldValue.toString(); + } + } else { + return null; } + } catch (Exception _) { } + return null; } } diff --git a/pom.xml b/pom.xml index 63fa03e9..476c74a6 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 4.0.0 tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 pom Dynamia Soluciones IT SAS @@ -57,14 +57,14 @@ ${project.baseUri} - 4.0.3 - 2.2.38 + 4.0.5 + 2.2.46 1 - 10.1.0-jakarta + 10.2.1-jakarta 1.1.0 - 2.41.10 + 2.42.30 6.21.4 diff --git a/themes/pom.xml b/themes/pom.xml index 679b0d7d..ffead089 100644 --- a/themes/pom.xml +++ b/themes/pom.xml @@ -6,7 +6,7 @@ tools.dynamia tools.dynamia.parent - 26.4.0 + 26.4.1 ../pom.xml diff --git a/themes/theme-dynamical/sources/pom.xml b/themes/theme-dynamical/sources/pom.xml index aee9a242..46dd9562 100644 --- a/themes/theme-dynamical/sources/pom.xml +++ b/themes/theme-dynamical/sources/pom.xml @@ -24,7 +24,7 @@ tools.dynamia.themes tools.dynamia.themes.parent - 26.4.0 + 26.4.1 ../../pom.xml @@ -102,7 +102,7 @@ tools.dynamia tools.dynamia.zk - 26.4.0 + 26.4.1 provided