diff --git a/tutorials/ai/prompt-scaffolding/README.md b/tutorials/ai/prompt-scaffolding/README.md new file mode 100644 index 0000000..1d18804 --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/README.md @@ -0,0 +1,55 @@ +This setup allows you to scaffold a Java desktop app with JxBrowser for the +webview, a React UI (with shadcn/ui), and Protobuf/gRPC for communication +between Java and JavaScript. + +## Prerequisites + +- Java 17 or higher +- [Node.js](https://nodejs.org/en/download) 20.11.0 or higher +- [Cursor](https://cursor.com/) +- Claude 4 Sonnet + +## How to use + +Download the [`docs`](docs) and [`template`](template) +directories and put them into an empty project. + +Add the [`architecture-overview.md`](docs/architecture-overview.md) +document to the context for a new project in Cursor, and ask the LLM to +generate the app using the prompt below: + +![Paste the prompt in Cursor](img/paste-prompt-in-cursor.png) + +```markdown +Please read and analyse the architecture in architecture-overview.md we've used +in our previous project, and let's create a new demo project from it. + +Let's create a simple desktop application with the functionality of a basic +filesystem viewer. The front-end part will be responsible for displaying the +file system structure, like folders and files. The backend part will access the +Java filesystem API and provide the data to the front-end. +``` + +## How to run + +When all the project files are generated, you can start the app by first running +the Vite dev server: + +```bash +cd web-app +npm run dev +``` + +Then, in another terminal, launch the application itself: + +```bash +./gradlew run +``` + +You should see the desktop app window with shadcn/ui (React) that allows viewing +local files/folders and navigating through them: + +![FileViewer](img/file-viewer.png) + +After the application successfully started, you can remove the [`docs`](docs) +and [`template`](template) directories. diff --git a/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md b/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md new file mode 100644 index 0000000..508f2d3 --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md @@ -0,0 +1,1269 @@ +# Desktop Web App Architecture Overview + +This document describes the reusable architecture pattern for creating cross-platform desktop applications using JxBrowser with Java backend and modern web frontend technologies. + +## ⚠️ CRITICAL PREREQUISITES FOR LLMs ⚠️ + +### JxBrowser License Requirement +- **COMMERCIAL PRODUCT** requiring license from [TeamDev JxBrowser](https://www.teamdev.com/jxbrowser) +- **MUST SET** environment variable: `JXBROWSER_LICENSE_KEY=your-license-key` +- **USE IN CODE**: `System.getenv("JXBROWSER_LICENSE_KEY")` + +### πŸ“¦ Required Gradle Imports +**MANDATORY**: Always include these imports at the top of `build.gradle.kts`: +```kotlin +// MANDATORY at top of build.gradle.kts +import com.google.protobuf.gradle.id +import org.gradle.api.JavaVersion.VERSION_17 +``` + +### SwingUtilities Threading Rule +- ❌ NEVER wrap entire application in `SwingUtilities.invokeLater()` +- βœ… Only wrap Swing component creation + +## High-Level Architecture + +Hybrid desktop architecture combining: +- **Java Backend**: Business logic, gRPC services, data persistence +- **Web Frontend**: React + TypeScript + Tailwind CSS +- **JxBrowser**: Chromium engine bridging Java and JavaScript +- **gRPC Communication**: Protocol Buffers for type-safe communication + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Desktop Application β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Java Backend │◄──►│ JxBrowser Engine β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Business Logicβ”‚ β”‚ β€’ Chromium Browser β”‚ β”‚ +β”‚ β”‚ β€’ gRPC Services β”‚ β”‚ β€’ JavaScript-Java Bridge β”‚ β”‚ +β”‚ β”‚ β€’ Data Models β”‚ β”‚ β€’ Custom URL Schemes β”‚ β”‚ +β”‚ β”‚ β€’ File I/O β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Web Frontend β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ React + TypeScript β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ shadcn/ui + Tailwind β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β€’ gRPC-Web Client β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Project Structure + +``` +desktop-web-app/ +β”œβ”€β”€ build.gradle.kts # Main build configuration +β”œβ”€β”€ proto/ # Protocol Buffer definitions +β”œβ”€β”€ src/main/java/ # Java backend +β”‚ β”œβ”€β”€ App.java # Entry point +β”‚ β”œβ”€β”€ AppInitializer.java # Bootstrap & configuration +β”‚ β”œβ”€β”€ AppDetails.java # App metadata & paths +β”‚ β”œβ”€β”€ [domain]/ # Business domains +β”‚ β”‚ β”œβ”€β”€ *Service.java # gRPC implementations +β”‚ β”‚ └── *.java # Data models +β”‚ └── production/ # Production utilities +β”‚ β”œβ”€β”€ MimeTypes.java +β”‚ └── UrlRequestInterceptor.java +└── web-app/ # Frontend application + β”œβ”€β”€ package.json + β”œβ”€β”€ vite.config.ts + β”œβ”€β”€ buf.gen.yaml # Protobuf generation + └── src/ + β”œβ”€β”€ main.tsx # Entry point + β”œβ”€β”€ gen/ # Generated protobuf types + β”œβ”€β”€ rpc/ # gRPC clients + └── components/ # React components +``` + +### Frontend Structure +``` +web-app/ +β”œβ”€β”€ package.json # NPM dependencies and scripts +β”œβ”€β”€ vite.config.ts # Build tool configuration +β”œβ”€β”€ tailwind.config.js # Styling configuration +β”œβ”€β”€ buf.gen.yaml # Protobuf code generation config +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ main.tsx # Application entry point +β”‚ β”œβ”€β”€ App.tsx # Main React component +β”‚ β”œβ”€β”€ gen/ # Generated protobuf types (auto-generated) +β”‚ β”œβ”€β”€ rpc/ # gRPC client configuration +β”‚ β”‚ β”œβ”€β”€ client.ts # gRPC transport setup +β”‚ β”‚ └── *-client.ts # Service-specific clients +β”‚ β”œβ”€β”€ components/ # React components +β”‚ β”‚ β”œβ”€β”€ ui/ # Reusable UI components (shadcn/ui) +β”‚ β”‚ └── *.tsx # Feature-specific components +β”‚ β”œβ”€β”€ hooks/ # Custom React hooks +β”‚ β”œβ”€β”€ lib/ # Utility functions +β”‚ β”œβ”€β”€ context/ # React context providers +β”‚ β”œβ”€β”€ converter/ # Data conversion utilities +β”‚ └── storage/ # Client-side storage utilities +└── dist/ # Built assets (auto-generated) +``` + +## Build Configuration + +### Complete build.gradle.kts Template + +```kotlin +// ⚠️ MANDATORY IMPORTS - Always include at the top +import com.google.protobuf.gradle.id +import org.gradle.api.JavaVersion.VERSION_17 + +plugins { + java + id("com.google.protobuf") version "0.9.1" + id("application") + + // ⚠️ CRITICAL: Official JxBrowser Gradle plugin + id("com.teamdev.jxbrowser") version "1.2.1" +} + +java { + sourceCompatibility = VERSION_17 + targetCompatibility = VERSION_17 +} + +jxbrowser { + version = "8.9.4" // Set JxBrowser version +} + +dependencies { + // ⚠️ CRITICAL: JxBrowser dependencies using official plugin + implementation(jxbrowser.currentPlatform) // Auto-detects platform + implementation(jxbrowser.swing) // For Swing integration + + // gRPC & Protocol Buffers + implementation("com.linecorp.armeria:armeria-grpc:1.30.1") + implementation("com.linecorp.armeria:armeria:1.30.1") + implementation("com.linecorp.armeria:armeria-grpc-protocol:1.30.1") + implementation("com.google.protobuf:protobuf-java:3.21.12") + + // Utilities + implementation("com.google.code.gson:gson:2.10.1") +} + +application { + applicationDefaultJvmArgs = listOf("-Dapp.dev.mode=true") + mainClass.set("com.teamdev.jxbrowser.examples.App") +} +sourceSets { + main { + proto { + srcDir("proto") + include("*.proto") + } + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.21.12" + plugins { + // βœ… CORRECT - requires import above + id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.67.1" } + } + generateProtoTasks { + all().forEach { task -> + task.plugins { + // βœ… CORRECT - requires import above + id("grpc") {} + } + } + } + } +} + +// Build pipeline tasks +tasks.register("buildWeb") { + dependsOn("installNpmPackages", "generateJsProto") + // Builds frontend assets into web-app/dist/ +} + +tasks.jar { + archiveFileName.set(mainJar) // Explicitly set JAR name + dependsOn(tasks.named("buildWeb")) + manifest { + attributes["Main-Class"] = application.mainClass.get() + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from({ + configurations.runtimeClasspath.get().map { + if (it.isDirectory) it else zipTree(it) + } + }) + + doLast { + copy { + from("build/libs/$mainJar") + into("build/dist") + } + } +} +``` + +### 2. βœ… JxBrowser License Usage +```java +// βœ… CORRECT - Always use environment variable directly +var optionsBuilder = EngineOptions.newBuilder(HARDWARE_ACCELERATED) + .licenseKey(System.getenv("JXBROWSER_LICENSE_KEY")); // Direct env var access +``` + +### 3. βœ… Required Environment Variable +```bash +# Set this before running the application +export JXBROWSER_LICENSE_KEY=your-license-key-from-teamdev +``` + +### 4. ⚠️ CRITICAL SwingUtilities Usage +```java +// ❌ WRONG - Never wrap entire application +SwingUtilities.invokeLater(() -> { + // All application code here - BLOCKS AWT thread! +}); + +// βœ… CORRECT - Only wrap UI creation +public void initialize() throws InterruptedException { + setupLogging(); // Main thread + var engine = createEngine(); // Main thread + var browser = engine.newBrowser(); // Main thread + setupUI(engine, browser); // Uses invokeLater internally for UI only + initializeRpc(browser); // Main thread - server.blockUntilShutdown() OK +} +``` + +### 6. πŸ› οΈ Enable DevTools in Development Mode +```java +// βœ… CORRECT - Always enable devtools in development mode +private static void setupBrowserCallbacks(Browser browser) { + browser.set(InjectJsCallback.class, params -> { + JsObject window = params.frame().executeJavaScript("window"); + if (window != null) { + window.putProperty("rpcPort", RPC_PORT); + } + return InjectJsCallback.Response.proceed(); + }); + + if (!isProductionMode()) { + browser.devTools().show(); // βœ… Enable devtools in dev mode + } +} +``` + +### Frontend Dependencies (package.json) + +```json +{ + "dependencies": { + // Core React ecosystem + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^6.22.1", + + // gRPC communication (ConnectRPC codegen v2) + "@connectrpc/connect": "2.0.0-rc.2", + "@connectrpc/connect-web": "2.0.0-rc.2", + "@bufbuild/protobuf": "2.2.1", + + // UI framework + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-switch": "^1.1.3", + "tailwindcss": "^4.1.1", // Tailwind 4 + "@tailwindcss/postcss": "^4.1.1", + "lucide-react": "^0.507.0", // Icons + "clsx": "^2.1.0", + "tailwind-merge": "^3.1.0", + + // Development tools + "vite": "^5.1.0", // Build tool + "typescript": "^5.2.2" // Type safety + }, + "devDependencies": { + // Protocol Buffer generation (codegen v2) + "@bufbuild/buf": "1.46.0", + "@bufbuild/protoc-gen-es": "2.2.1" + } +} +``` + +### Frontend Development Guidelines + +#### 🎨 Use shadcn/ui Components (NOT Custom Components) + +**CRITICAL**: Always use shadcn/ui components instead of building components from scratch. + +**βœ… CORRECT - Install and use shadcn/ui components:** +```bash +# Install components as needed +npx shadcn@latest add button +npx shadcn@latest add input +npx shadcn@latest add card +npx shadcn@latest add dialog +npx shadcn@latest add dropdown-menu +npx shadcn@latest add form +npx shadcn@latest add table +npx shadcn@latest add tabs +npx shadcn@latest add select +npx shadcn@latest add checkbox +``` + +**Then use in React components:** +```tsx +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +export function MyComponent() { + return ( + + + Settings + + + + + + + ) +} +``` + +**TypeScript/Frontend Issues:** +- **"Cannot mix BigInt and other types"**: This is fixed by protobuf utility functions that handle BigInt conversion + +**❌ WRONG - Don't build basic components from scratch:** +```tsx +// Don't do this - use shadcn/ui instead +export function CustomButton({ children, onClick }) { + return ( + + ) +} +``` + +**Available shadcn/ui Components:** +- `button`, `input`, `textarea`, `select`, `checkbox`, `radio-group` +- `card`, `dialog`, `popover`, `tooltip`, `sheet` +- `table`, `form`, `tabs`, `accordion`, `collapsible` +- `dropdown-menu`, `navigation-menu`, `breadcrumb` +- `alert`, `badge`, `progress`, `skeleton`, `spinner` +- And many more - check [shadcn/ui docs](https://ui.shadcn.com) + +## Data Model Architecture + +### Protocol Buffer Definitions +Data models are defined once in `.proto` files and code is generated for both Java and TypeScript: + +```protobuf +// Example: prefs.proto +syntax = "proto3"; +package com.teamdev.jxbrowser.prefs; + +// Service definition +service PrefsService { + rpc GetAccount(google.protobuf.Empty) returns (Account); + rpc SetAccount(Account) returns (google.protobuf.Empty); + // ... other methods +} + +// Data models +message Account { + string email = 1; + string full_name = 2; + TwoFactorAuthentication two_factor_authentication = 3; + bool biometric_authentication = 4; +} + +enum TwoFactorAuthentication { + EMAIL = 0; + SMS = 1; + PASS_KEY = 2; +} +``` + +### Code Generation Process +1. **Java**: Gradle plugin generates classes in `build/generated/source/proto/` +2. **TypeScript**: Buf CLI generates types in `web-app/src/gen/` using **codegen v2** + +### Proto Generation Configuration +The proto files should be located in the **root `proto/` directory**, and the `buf.gen.yaml` should reference it: + +```yaml +# web-app/buf.gen.yaml +version: v2 +inputs: + - directory: ../proto # Points to root proto directory +plugins: + - local: protoc-gen-es + opt: target=ts + out: src/gen +``` + +## Communication Layer + +### gRPC Service (Java) +```java +public final class PrefsService extends PrefsServiceImplBase { + @Override + public void getAccount(Empty request, StreamObserver responseObserver) { + responseObserver.onNext(appPrefs.account()); + responseObserver.onCompleted(); + } +} +``` + +### gRPC Client (TypeScript) +```typescript +import { createGrpcWebTransport } from "@connectrpc/connect-web"; +import { createClient } from "@connectrpc/connect"; +import { PrefsService } from "@/gen/prefs_pb.ts"; + +declare const rpcPort: Number; // Injected by JxBrowser + +const transport = createGrpcWebTransport({ + baseUrl: `http://localhost:${rpcPort}`, +}); + +const prefsClient = createClient(PrefsService, transport); +``` + +### JavaScript-Java Bridge +JxBrowser injects the gRPC port into the JavaScript context. **The RPC port should be the same for both web UI and Java app (e.g., 9090 or 50051)**: + +```java +// Java side (AppInitializer.java) +private static final int RPC_PORT = 50051; // Same port for both frontend and backend + +browser.set(InjectJsCallback.class, params -> { + JsObject window = params.frame().executeJavaScript("window"); + if (window != null) { + window.putProperty("rpcPort", RPC_PORT); + } + return InjectJsCallback.Response.proceed(); +}); +``` + +### CORS Configuration for Armeria Server +The gRPC server must be configured with proper CORS support. This requires the `armeria-grpc-protocol` dependency: + +```java +// AppInitializer.java - initializeRpc method +private static void initializeRpc(Browser browser) throws InterruptedException { + var serverBuilder = Server.builder().http(RPC_PORT); + var corsBuilder = CorsService.builder(appUrl()) + .allowRequestMethods(HttpMethod.POST) + .allowRequestHeaders( + HttpHeaderNames.CONTENT_TYPE, + HttpHeaderNames.of("x-grpc-web"), + HttpHeaderNames.of("x-user-agent")) + .exposeHeaders(GrpcHeaderNames.GRPC_STATUS, + GrpcHeaderNames.GRPC_MESSAGE, + GrpcHeaderNames.ARMERIA_GRPC_THROWABLEPROTO_BIN); + + serverBuilder.service(GrpcService.builder() + .addService(new YourService()) + .build(), + corsBuilder.newDecorator(), + LoggingService.newDecorator()); + + try (var server = serverBuilder.build()) { + server.start(); + browser.navigation().loadUrl(appUrl()); + server.blockUntilShutdown(); + } +} +``` + +### Frontend Build Pipeline +1. **NPM Install**: Install Node.js dependencies +2. **Generate Protobuf**: Create TypeScript types from `.proto` files +3. **Vite Build**: Compile and bundle React application +4. **Asset Integration**: Include built assets in Java JAR + +## URL Request Interception + +### Custom Scheme Handling +To properly set up URL request interception, you must register a custom scheme as shown in [JxBrowser documentation](https://teamdev.com/jxbrowser/docs/guides/network/#registering-custom-scheme). In production mode, the application uses a custom URL scheme (`jxbrowser://`) to serve frontend assets from JAR resources instead of external files. + +### URL Request Interceptor Implementation + +**Complete implementation with proper imports and Java 17+ syntax:** + +```java +// Required imports - use these exact imports +import com.teamdev.jxbrowser.net.HttpHeader; +import com.teamdev.jxbrowser.net.HttpStatus; +import com.teamdev.jxbrowser.net.UrlRequestJob; +import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; + +public final class UrlRequestInterceptor implements InterceptUrlRequestCallback { + private static final String CONTENT_ROOT = "/web"; + private static final String INDEX_HTML = "/index.html"; + private static final String CONTENT_TYPE = "Content-Type"; + + @Override + public Response on(Params params) { + var url = params.urlRequest().url(); + + // Check if URL matches our custom scheme + if (url.contains(AppDetails.appUrl())) { + var uri = URI.create(url); + var fileName = uri.getPath().equals("/") ? INDEX_HTML : uri.getPath(); + + // Create URL request job and serve file + var job = urlRequestJob(params, fileName); + readFile(fileName, job); + return InterceptUrlRequestCallback.Response.intercept(job); + } + + // Let other requests proceed normally + return InterceptUrlRequestCallback.Response.proceed(); + } + + private static UrlRequestJob urlRequestJob(InterceptUrlRequestCallback.Params params, String file) { + var builder = UrlRequestJob.Options.newBuilder(HttpStatus.OK); + builder.addHttpHeader(contentType(file)); + return params.newUrlRequestJob(builder.build()); + } + + private static void readFile(String fileName, UrlRequestJob job) { + try (var stream = UrlRequestInterceptor.class.getResourceAsStream(CONTENT_ROOT + fileName)) { + if (stream == null) { + throw new FileNotFoundException(CONTENT_ROOT + fileName); + } + job.write(stream.readAllBytes()); + job.complete(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static HttpHeader contentType(String file) { + return HttpHeader.of(CONTENT_TYPE, MimeTypes.mimeType(file).value()); + } +} +``` + +### Resource Serving Pipeline + +1. **URL Matching**: Interceptor checks if request URL matches the custom scheme +2. **Path Resolution**: Maps URL path to resource file (defaults to `index.html` for root) +3. **Resource Loading**: Reads file from JAR classpath (`/web/` directory) +4. **MIME Type Detection**: Determines appropriate Content-Type header +5. **Response Creation**: Creates HTTP response with proper headers and content + +### MIME Type Handling + +The application includes a comprehensive MIME type mapping system: + +```java +import com.teamdev.jxbrowser.net.MimeType; +import java.util.Map; +import java.util.HashMap; +import static java.util.Locale.ENGLISH; +import static com.teamdev.jxbrowser.net.MimeType.OCTET_STREAM; + +public final class MimeTypes { + private static final Map MIME_TYPES = Map.of( + "html", MimeType.of("text/html"), + "css", MimeType.of("text/css"), + "js", MimeType.of("application/javascript"), + "json", MimeType.of("application/json"), + "png", MimeType.of("image/png"), + "jpg", MimeType.of("image/jpeg"), + "jpeg", MimeType.of("image/jpeg"), + "gif", MimeType.of("image/gif"), + "svg", MimeType.of("image/svg+xml"), + "ico", MimeType.of("image/x-icon"), + "woff", MimeType.of("font/woff"), + "woff2", MimeType.of("font/woff2"), + "ttf", MimeType.of("font/ttf") + ); + + public static MimeType mimeType(String fileName) { + var fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1); + return MIME_TYPES.getOrDefault(fileExtension.toLowerCase(ENGLISH), OCTET_STREAM); + } +} +``` + +**Supported File Types**: +- **Web Assets**: `.html`, `.css`, `.js`, `.json` +- **Images**: `.png`, `.jpg`, `.gif`, `.svg`, `.ico`, `.webp` +- **Fonts**: `.woff`, `.woff2`, `.ttf` +- **Media**: `.mp3`, `.mp4`, `.wav`, `.webm` +- **Documents**: `.pdf`, `.txt`, `.xml` + +### Engine Configuration +```java +private static Engine createEngine() { + var optionsBuilder = EngineOptions.newBuilder(HARDWARE_ACCELERATED) + .userDataDir(AppDetails.INSTANCE.chromiumUserDataDir()) + .licenseKey(System.getenv("JXBROWSER_LICENSE_KEY")); // βœ… CORRECT - Direct env var access + + // Register custom scheme only in production mode + if (isProductionMode()) { + // Use Scheme type, not String + private static final Scheme SCHEME = of("jxbrowser"); + optionsBuilder.addScheme(SCHEME, new UrlRequestInterceptor()); + } + + return Engine.newInstance(optionsBuilder.build()); +} +``` + +### Application Details Configuration + +The `AppDetails` class provides application-specific configuration: + +```java +import java.nio.file.Path; +import java.nio.file.Paths; + +public class AppDetails { + public static final AppDetails INSTANCE = new AppDetails(); + + private static final String APP_NAME = "YourApp"; + private static final String CUSTOM_HOST = "my-app.com"; + + public Path chromiumUserDataDir() { + return userDataDir().resolve("chromium"); + } + + public Path appResourcesDir() { + return userDataDir(); + } + + private Path userDataDir() { + String os = System.getProperty("os.name").toLowerCase(); + String userHome = System.getProperty("user.home"); + + if (os.contains("win")) { + return Paths.get(System.getenv("LOCALAPPDATA"), APP_NAME); + } else if (os.contains("mac")) { + return Paths.get(userHome, "Library", "Application Support", APP_NAME); + } else { + return Paths.get(userHome, ".local", "share", APP_NAME); + } + } + + public static String appUrl() { + return isProductionMode() ? + CUSTOM_SCHEME + "://" + CUSTOM_HOST : + "http://localhost:5173"; + } + + public static boolean isProductionMode() { + return !Boolean.getBoolean("app.dev.mode"); + } + + public static String getAppIconFileName() { + String os = System.getProperty("os.name").toLowerCase(); + return os.contains("mac") ? "app.icns" : "app.ico"; + } +} +``` + +### Development vs Production Modes + +**Development Mode** (set via JVM property in build.gradle.kts): +```kotlin +application { + applicationDefaultJvmArgs = listOf("-Dapp.dev.mode=true") + mainClass.set("com.teamdev.jxbrowser.examples.App") +} +``` + +- Frontend served by Vite dev server (HMR enabled) +- JxBrowser loads `http://localhost:5173` +- No URL interception needed +- DevTools enabled +- Live reload for both frontend and backend changes +- gRPC server runs alongside the main Java application (not as separate Gradle task) + +**Production Mode**: +- Frontend assets bundled into JAR resources +- JxBrowser loads `jxbrowser://my-app.com` (custom scheme) +- URL interceptor serves assets from classpath +- Optimized builds +- Single executable JAR + +### Resource Directory Structure + +Frontend build assets are included in the JAR under `/web/`: + +``` +JAR resources/ +β”œβ”€β”€ web/ # Frontend build output +β”‚ β”œβ”€β”€ index.html # Main HTML file +β”‚ β”œβ”€β”€ assets/ # Vite-generated assets +β”‚ β”‚ β”œβ”€β”€ *.js # JavaScript bundles +β”‚ β”‚ β”œβ”€β”€ *.css # Stylesheets +β”‚ β”‚ └── *.woff2 # Font files +β”‚ └── *.ico # Favicon and icons +β”œβ”€β”€ mime-types.properties # MIME type mappings +└── *.png # Application icons +``` + +### Implementation Guidelines for New Projects + +// Only wrap UI code in invokeLater +SwingUtilities.invokeLater(() -> { +// Only Swing component creation here +}); + +// Main thread handles blocking operations +initializeRpc(browser); // server.blockUntilShutdown() OK here +``` + +3. **Set Up Resource Path**: + - Ensure frontend builds to correct output directory + - Configure Gradle to include assets in JAR + - Update interceptor's `CONTENT_ROOT` path + +4. **Handle MIME Types**: + - Copy `mime-types.properties` or create custom mappings + - Implement appropriate Content-Type header logic + +5. **Test Both Modes**: + - Development: Direct Vite server access + - Production: Custom scheme with intercepted requests + +This URL interception pattern enables seamless packaging of web assets within the desktop application while maintaining development workflow efficiency. + +## Data Persistence + +### Java-side Persistence +- **JSON Files**: Using Gson for preferences (`preferences.json`) +- **Binary Files**: Using Protocol Buffers binary format for complex data +- **Platform-specific Locations**: + - Windows: `%LOCALAPPDATA%/AppName/` + - macOS: `~/Library/Application Support/AppName/` + +### Frontend State Management +- React hooks for local state +- gRPC clients for server communication +- Local storage for temporary data + +## Packaging and Distribution + +### Native Installers + +The build system includes tasks to create native installers using Java's `jpackage` tool. These tasks are defined in `build.gradle.kts`: + +#### macOS DMG Installer +```bash +# Build macOS DMG installer +./gradlew packageDmg +``` + +**Generated Gradle Task:** +```kotlin +tasks.register("packageDmg") { + dependsOn(tasks.build) + + commandLine( + "jpackage", + "--input", "./build/dist", // Input directory with JAR + "--main-jar", mainJar, // Main JAR file name + "--name", applicationName, // Application name + "--app-version", version, // Version from build.gradle.kts + "--type", "dmg", // macOS DMG format + "--main-class", application.mainClass.get(), + "--dest", "./build/installer", // Output directory + "--icon", "src/main/resources/app.icns", // macOS icon file + ) +} +``` + +#### Windows EXE Installer +```bash +# Build Windows EXE installer +./gradlew packageExe +``` + +**Generated Gradle Task:** +```kotlin +tasks.register("packageExe") { + dependsOn(tasks.build) + + commandLine( + "jpackage", + "--input", "./build/dist", // Input directory with JAR + "--main-jar", mainJar, // Main JAR file name + "--name", applicationName, // Application name + "--app-version", version, // Version from build.gradle.kts + "--type", "exe", // Windows EXE format + "--main-class", application.mainClass.get(), + "--dest", "./build/installer", // Output directory + "--win-dir-chooser", // Allow user to choose install directory + "--win-menu", // Add to Windows Start Menu + "--win-shortcut-prompt", // Prompt to create desktop shortcut + "--icon", "src/main/resources/app.ico", // Windows icon file + ) +} +``` + +#### Required Configuration Variables +```kotlin +// In build.gradle.kts +group = "com.teamdev.jxbrowser.gallery" +version = "1.0" + +val applicationName = "JxBrowserWebApp" +val mainJar = "$applicationName-$version.jar" + +application { + mainClass.set("com.teamdev.jxbrowser.examples.App") +} +``` + +#### Prerequisites for Native Packaging + +**For macOS DMG:** +- macOS development environment +- Xcode Command Line Tools installed +- Valid Apple Developer ID (for code signing) + +**For Windows EXE:** +- Windows development environment +- [WiX Toolset](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm) 3.14.1 or higher +- Valid code signing certificate (optional but recommended) + +#### Generated Output +- **macOS**: `./build/installer/YourApp-1.0.dmg` +- **Windows**: `./build/installer/YourApp-1.0.exe` + +### JAR Distribution +```bash +# Fat JAR with all dependencies (platform-independent) +./gradlew build +# Generates: ./build/dist/YourApp-1.0.jar +``` + +**Important**: The JAR task is configured to use an explicit filename and create a fat JAR: + +```kotlin +tasks.jar { + archiveFileName.set(mainJar) // Uses custom name instead of project name + dependsOn(tasks.named("buildWeb")) + manifest { + attributes["Main-Class"] = application.mainClass.get() + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from({ + configurations.runtimeClasspath.get().map { + if (it.isDirectory) it else zipTree(it) + } + }) + + doLast { + copy { + from("build/libs/$mainJar") // Copy from libs to dist + into("build/dist") // Native installers expect it here + } + } +} +``` + +This configuration ensures: +- **Custom JAR Name**: Uses `applicationName-version.jar` instead of project directory name +- **Fat JAR**: Includes all dependencies for standalone execution +- **Proper Location**: Copies JAR to `build/dist/` where native installer tasks expect it +- **Main Class**: Sets manifest for direct execution via `java -jar` + +### Complete Build Pipeline +```bash +# Full build with web assets and JAR +./gradlew clean build + +# Build native installers (platform-specific) +./gradlew packageDmg # On macOS +./gradlew packageExe # On Windows +``` + +## Frontend Framework Requirements + +### Tailwind CSS 4 Setup +This architecture uses Tailwind CSS 4. Here's the complete setup: + +**1. Install Dependencies**: +```json +// package.json +{ + "devDependencies": { + "tailwindcss": "^4.1.1", + "@tailwindcss/postcss": "^4.1.1", + "postcss": "^8.4.35" + } +} +``` + +**2. Tailwind Configuration**: +```javascript +// tailwind.config.js +export default { + darkMode: ["class"], + content: [ + './index.html', + './src/**/*.{js,ts,jsx,tsx}', + ], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, + }, + plugins: [], +} +``` + +**3. PostCSS Configuration**: +```javascript +// postcss.config.js +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +} +``` + +**4. CSS Setup**: +```css +/* src/index.css */ +@import "tailwindcss"; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96%; + --secondary-foreground: 222.2 84% 4.9%; + --muted: 210 40% 96%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96%; + --accent-foreground: 222.2 84% 4.9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} +``` + +## Reusability Guidelines + +### For New Projects + +1. **Copy Core Structure**: + - `build.gradle.kts` (update app name, main class, set development mode) + - `AppInitializer.java` (core bootstrap logic, CORS configuration) + - `AppDetails.java` (app-specific configuration) + - `UrlRequestInterceptor.java` (for custom scheme handling) + +2. **Define Data Models**: + - Create `.proto` files in **root `proto/` directory** + - Define services and messages for your domain + - Ensure `buf.gen.yaml` points to `../proto` + +3. **Implement Services**: + - Extend `*ServiceImplBase` for each service + - Implement business logic and data persistence + - Configure CORS with `armeria-grpc-protocol` dependency + +4. **Build Frontend**: + - Use `web-app/` structure as template + - Set up Tailwind CSS 4 configuration + - Use **codegen v2** for TypeScript proto generation + - Leverage generated TypeScript types with ConnectRPC + +5. **Configure Build and Environment**: + - Set up development mode with `-Dapp.dev.mode=true` + - Configure `JXBROWSER_LICENSE_KEY` environment variable + - Update `package.json` with project-specific dependencies + - Ensure gRPC server runs in same process (not separate Gradle task) + - Use consistent RPC port for frontend and backend + +### Recommended Dependency Versions + +**Java Dependencies:** +```kotlin +// JxBrowser 8.9.4 (latest version) +implementation(jxbrowser.macArm) +implementation(jxbrowser.swing) + +// Armeria (gRPC server) +implementation("com.linecorp.armeria:armeria-grpc:1.30.1") +implementation("com.linecorp.armeria:armeria:1.30.1") +implementation("com.linecorp.armeria:armeria-grpc-protocol:1.30.1") // Required for CORS + +// Protocol Buffers +implementation("com.google.protobuf:protobuf-java:3.21.12") + +// Utilities +implementation("com.google.code.gson:gson:2.10.1") +implementation("javax.annotation:javax.annotation-api:1.3.2") +``` + +**Frontend Dependencies:** +```json +{ + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^6.22.1", + "@connectrpc/connect": "2.0.0-rc.2", + "@connectrpc/connect-web": "2.0.0-rc.2", + "@bufbuild/protobuf": "2.2.1", + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-switch": "^1.1.3", + "tailwindcss": "^4.1.1", + "lucide-react": "^0.507.0", + "vite": "^5.1.0", + "typescript": "^5.2.2" + }, + "devDependencies": { + "@bufbuild/buf": "1.46.0", + "@bufbuild/protoc-gen-es": "2.2.1" + } +} +``` + +## Gradle Build Configuration + +The [template](../template) directory contains a minimal Gradle setup that serves as a +foundation for creating demos and examples. This directory is specifically designed to +help LLMs (Language Learning Models) and developers quickly bootstrap new Gradle +projects. + +**Purpose:** +- Provides a clean Gradle environment for testing and demonstrations +- Allows running `gradlew --version` and future Gradle tasks +- Serves as a starting point for creating new components or features + +**Structure:** +``` +../template/ +β”œβ”€β”€ gradlew # Gradle wrapper script (Unix/macOS) +β”œβ”€β”€ gradlew.bat # Gradle wrapper script (Windows) +β”œβ”€β”€ settings.gradle.kts # Basic Gradle settings +└── gradle/ +└── wrapper/ +β”œβ”€β”€ gradle-wrapper.jar # Gradle wrapper executable +└── gradle-wrapper.properties # Gradle wrapper configuration + +**Usage:** +```bash +cd ../template +./gradlew --version # Verify Gradle installation +./gradlew tasks # List available tasks (when build.gradle.kts is added) +``` + +### JxBrowser License Configuration + +**CRITICAL: JxBrowser license must be provided via environment variable** + +JxBrowser is a **commercial library** that requires a valid license. The license is NOT available in Maven Central and must be obtained from TeamDev. + +**License Setup:** +1. **Get License**: Obtain JxBrowser license from [TeamDev](https://www.teamdev.com/jxbrowser) +2. **Set Environment Variable**: `JXBROWSER_LICENSE_KEY=your-license-key-here` +3. **Use in Code**: Always read from environment variable + +**Important Notes:** +- JxBrowser binaries are NOT in Maven Central +- License is required for both development and production +- TeamDev provides evaluation licenses for testing +- Contact TeamDev for commercial licensing + +## Java Development Guidelines + +### Java 17+ Syntax Requirements + +This project uses **Java 17** or higher. LLMs and developers should use modern Java syntax including: + +### ❌ Common Mistakes + +```java +// WRONG - Don't use String for scheme +optionsBuilder.addScheme("jxbrowser", interceptor); + +// WRONG - Missing protobuf import causes this error +create("grpc") { ... } // Should be id("grpc") + +// WRONG - Blocks AWT thread +SwingUtilities.invokeLater(() -> { + // All application code - BLOCKS when server.blockUntilShutdown() +}); +``` + +**βœ… CORRECT - Only wrap Swing UI code:** +```java +public void initialize() throws InterruptedException { + // βœ… CORRECT - Non-UI code runs on main thread + setupLogging(); + var engine = createEngine(); + var browser = engine.newBrowser(); + setupUI(engine, browser); // This method uses invokeLater internally + setupBrowserCallbacks(browser); + initializeRpc(browser); // This calls server.blockUntilShutdown() - OK on main thread +} + +private static void setupUI(Engine engine, Browser browser) { + // βœ… CORRECT - Only UI creation wrapped in invokeLater + SwingUtilities.invokeLater(() -> { + var frame = new JFrame("Dashboard"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + engine.close(); + } + }); + + setAppIcon(frame); + frame.add(BrowserView.newInstance(browser), BorderLayout.CENTER); + frame.setSize(1200, 800); + frame.setMinimumSize(new Dimension(640, 560)); + frame.setMaximumSize(new Dimension(1280, 900)); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); +} +``` + +## Frontend Guidelines + +### Use shadcn/ui Components +```bash +# Install components instead of building custom ones +npx shadcn@latest add button input card dialog +``` + +```tsx +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +export function MyComponent() { + return ( + + + Settings + + + + + + ) +} +``` + +## Data Persistence + +- **JSON Files**: Gson for preferences (platform-specific paths) +- **Binary Files**: Protocol Buffers for complex data +- **Locations**: + - Windows: `%LOCALAPPDATA%/AppName/` + - macOS: `~/Library/Application Support/AppName/` + +## Reusability for New Projects + +1. **Copy Structure**: `build.gradle.kts`, `AppInitializer.java`, `AppDetails.java` +2. **Define Models**: Create `.proto` files in root `proto/` directory +3. **Implement Services**: Extend `*ServiceImplBase` classes +4. **Build Frontend**: Use shadcn/ui, Tailwind CSS 4, TypeScript +5. **Configure Environment**: Set `JXBROWSER_LICENSE_KEY` + +### Key Benefits +- **Type Safety**: Protocol Buffers across Java/TypeScript +- **Modern UI**: React ecosystem with professional components +- **Cross-platform**: Single codebase for Windows/macOS/Linux +- **Developer Experience**: Hot reload, TypeScript, modern tooling +- **Performance**: Native Java with optimized web rendering + +This architecture provides a solid foundation for building professional desktop applications combining Java's power with modern web technologies. \ No newline at end of file diff --git a/tutorials/ai/prompt-scaffolding/img/file-viewer.png b/tutorials/ai/prompt-scaffolding/img/file-viewer.png new file mode 100644 index 0000000..28c6e27 Binary files /dev/null and b/tutorials/ai/prompt-scaffolding/img/file-viewer.png differ diff --git a/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png b/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png new file mode 100644 index 0000000..db1fb64 Binary files /dev/null and b/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png differ diff --git a/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties b/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ccc1a9b --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/tutorials/ai/prompt-scaffolding/template/gradlew b/tutorials/ai/prompt-scaffolding/template/gradlew new file mode 100755 index 0000000..77f8035 --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/template/gradlew @@ -0,0 +1,223 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Gradle template using the template +# variables listed below. + +# Project Name +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="-Xmx64m -Xms64m" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if [ "$darwin" = "true" ]; then + GRADLE_OPTS="$GRADLE_OPTS -Xdock:name=$APP_NAME -Xdock:icon=$APP_HOME/media/gradle.icns" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain $APP_ARGS + +exec "$JAVACMD" "$@" \ No newline at end of file diff --git a/tutorials/ai/prompt-scaffolding/template/gradlew.bat b/tutorials/ai/prompt-scaffolding/template/gradlew.bat new file mode 100644 index 0000000..43fe16c --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/template/gradlew.bat @@ -0,0 +1,97 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd_ return code. This script also supports the following command line options: +rem +rem -Dorg.gradle.jvmargs=... JVM arguments +rem -Dorg.gradle.parallel=... Enable/disable parallel execution +rem -Dorg.gradle.workers.max=... Maximum number of workers +rem -Dorg.gradle.daemon=... Enable/disable the Gradle daemon +rem -Dorg.gradle.debug=... Enable/disable Gradle debugging +rem +rem These options and variables can also be set in gradle.properties or in an ~/.gradle/gradle.properties file. + +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega \ No newline at end of file diff --git a/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts b/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts new file mode 100644 index 0000000..da80992 --- /dev/null +++ b/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "demo-sketch" \ No newline at end of file