diff --git a/example_projects/Weather/.xcodebuildmcp/config.yaml b/example_projects/Weather/.xcodebuildmcp/config.yaml index 6663899fa..be6aaedf2 100644 --- a/example_projects/Weather/.xcodebuildmcp/config.yaml +++ b/example_projects/Weather/.xcodebuildmcp/config.yaml @@ -5,7 +5,7 @@ enabledWorkflows: debug: false sentryDisabled: false sessionDefaults: - projectPath: Weather.xcodeproj + projectPath: app/Weather.xcodeproj scheme: Weather simulatorName: iPhone 17 Pro setupPreferences: diff --git a/example_projects/Weather/README.md b/example_projects/Weather/README.md index 8becf8103..f0c45d960 100644 --- a/example_projects/Weather/README.md +++ b/example_projects/Weather/README.md @@ -1,101 +1,72 @@ # Atmos Weather -Atmos Weather is a native SwiftUI weather app prototype for iOS. +Atmos Weather is a native SwiftUI weather app with a Node.js backend API. -## Launch with mock weather data +## Project structure -Build and run the app with XcodeBuildMCP first: - -```bash -../../build/cli.js simulator build-and-run ``` - -Then relaunch the installed app with the mock API argument: - -```bash -../../build/cli.js simulator launch-app \ - --bundle-id com.sentry.weather.Weather \ - --args=--mock-weather-api +Weather/ + app/ iOS app (Xcode project) + backend/ API server (Hono + Node.js) ``` -## JSON fixtures +## Backend -Fixture JSON files live in: +Start the API server: -```text -WeatherTests/Fixtures/ +```bash +cd backend +npm install +npm run dev ``` -Current fixtures: - -- `WeatherTests/Fixtures/default-locations.json` -- `WeatherTests/Fixtures/search-locations.json` -- `WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json` +The server runs on `http://localhost:3001` by default. Set `PORT` to change it. -## API schemas +## iOS app -OpenAI-compatible API schema files live in: +Build and run with XcodeBuildMCP from the `app/` directory: -```text -Schemas/ +```bash +cd app +../../../build/cli.js simulator build-and-run ``` -Current schemas: - -- `Schemas/default-locations.schema.json` -- `Schemas/search-locations.schema.json` -- `Schemas/weather-report.schema.json` - -These schemas describe the JSON response shape expected by the DTO layer. - -## Expected API endpoints +### Mock mode -The production client is `URLSessionWeatherAPIClient`. It currently expects a JSON API rooted at: +Relaunch with mock data (no backend required): -```text -https://api.atmosweather.example/v1 +```bash +../../../build/cli.js simulator launch-app \ + --bundle-id com.sentry.weather.Weather \ + --args=--mock-weather-api ``` -All endpoints are `GET` requests. - -| Purpose | Method | Path | Request shape | Schema | -| --- | --- | --- | --- | --- | -| Default saved locations | `GET` | `/locations/default` | No path params, query params, or body. | `Schemas/default-locations.schema.json` | -| Search locations | `GET` | `/locations/search` | Query string: `query=` | `Schemas/search-locations.schema.json` | -| Weather report for a location | `GET` | `/weather/{locationID}` | Path param: `locationID=` | `Schemas/weather-report.schema.json` | - -### Request examples - -Default locations: +### Tests -```http -GET /v1/locations/default +```bash +../../../build/cli.js simulator test ``` -Search locations: - -```http -GET /v1/locations/search?query=San%20Francisco -``` +UI tests inject `--mock-weather-api` so they do not depend on the backend. -Weather report: +## API endpoints -```http -GET /v1/weather/loc-current-san-francisco -``` +The backend serves three `GET` endpoints under `/v1`: -### Response expectations +| Purpose | Path | Params | +| --- | --- | --- | +| Default locations | `/v1/locations/default` | None | +| Search locations | `/v1/locations/search` | `?query=` | +| Weather report | `/v1/weather/:locationID` | Path param | -- Responses must be JSON. -- Successful responses should use a `2xx` HTTP status code. -- Non-`2xx` responses are treated as API failures. +### JSON schemas -## Tests +Schema files in `app/Schemas/` describe the expected response shapes: -Run the app test suite through XcodeBuildMCP: +- `default-locations.schema.json` +- `search-locations.schema.json` +- `weather-report.schema.json` -```bash -../../build/cli.js simulator test -``` +### Test fixtures -UI tests inject `--mock-weather-api` themselves so they do not depend on the production API endpoint. \ No newline at end of file +Fixture JSON files in `app/WeatherTests/Fixtures/` are used by unit tests. diff --git a/example_projects/Weather/app/AGENTS.md b/example_projects/Weather/app/AGENTS.md new file mode 100644 index 000000000..1a9b266e1 --- /dev/null +++ b/example_projects/Weather/app/AGENTS.md @@ -0,0 +1,3 @@ +# AGENTS.md + +- If using XcodeBuildMCP, use the installed XcodeBuildMCP skill before calling XcodeBuildMCP tools. diff --git a/example_projects/Weather/app/Info.plist b/example_projects/Weather/app/Info.plist new file mode 100644 index 000000000..fc0f79845 --- /dev/null +++ b/example_projects/Weather/app/Info.plist @@ -0,0 +1,11 @@ + + + + + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + diff --git a/example_projects/Weather/Schemas/default-locations.schema.json b/example_projects/Weather/app/Schemas/default-locations.schema.json similarity index 100% rename from example_projects/Weather/Schemas/default-locations.schema.json rename to example_projects/Weather/app/Schemas/default-locations.schema.json diff --git a/example_projects/Weather/Schemas/search-locations.schema.json b/example_projects/Weather/app/Schemas/search-locations.schema.json similarity index 100% rename from example_projects/Weather/Schemas/search-locations.schema.json rename to example_projects/Weather/app/Schemas/search-locations.schema.json diff --git a/example_projects/Weather/Schemas/weather-report.schema.json b/example_projects/Weather/app/Schemas/weather-report.schema.json similarity index 100% rename from example_projects/Weather/Schemas/weather-report.schema.json rename to example_projects/Weather/app/Schemas/weather-report.schema.json diff --git a/example_projects/Weather/Weather.xcodeproj/project.pbxproj b/example_projects/Weather/app/Weather.xcodeproj/project.pbxproj similarity index 96% rename from example_projects/Weather/Weather.xcodeproj/project.pbxproj rename to example_projects/Weather/app/Weather.xcodeproj/project.pbxproj index 3a05234dc..8631a371c 100644 --- a/example_projects/Weather/Weather.xcodeproj/project.pbxproj +++ b/example_projects/Weather/app/Weather.xcodeproj/project.pbxproj @@ -400,6 +400,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -410,10 +411,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 26.3; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.Weather; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -427,7 +428,7 @@ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -444,6 +445,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Info.plist; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -454,10 +456,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 26.3; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.Weather; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -471,7 +473,7 @@ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Release; }; @@ -483,8 +485,8 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; - MACOSX_DEPLOYMENT_TARGET = 26.3; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -497,7 +499,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Weather.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Weather"; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -509,8 +511,8 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; - MACOSX_DEPLOYMENT_TARGET = 26.3; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -523,7 +525,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Weather.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Weather"; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Release; }; @@ -534,8 +536,8 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; - MACOSX_DEPLOYMENT_TARGET = 26.3; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -548,7 +550,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_TARGET_NAME = Weather; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Debug; }; @@ -559,8 +561,8 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.4; - MACOSX_DEPLOYMENT_TARGET = 26.3; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -573,7 +575,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; TEST_TARGET_NAME = Weather; - XROS_DEPLOYMENT_TARGET = 26.4; + XROS_DEPLOYMENT_TARGET = 26.0; }; name = Release; }; diff --git a/example_projects/Weather/Weather.xcodeproj/xcshareddata/xcschemes/Weather.xcscheme b/example_projects/Weather/app/Weather.xcodeproj/xcshareddata/xcschemes/Weather.xcscheme similarity index 100% rename from example_projects/Weather/Weather.xcodeproj/xcshareddata/xcschemes/Weather.xcscheme rename to example_projects/Weather/app/Weather.xcodeproj/xcshareddata/xcschemes/Weather.xcscheme diff --git a/example_projects/Weather/Weather/AppLogger.swift b/example_projects/Weather/app/Weather/AppLogger.swift similarity index 100% rename from example_projects/Weather/Weather/AppLogger.swift rename to example_projects/Weather/app/Weather/AppLogger.swift diff --git a/example_projects/Weather/Weather/Assets.xcassets/AccentColor.colorset/Contents.json b/example_projects/Weather/app/Weather/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from example_projects/Weather/Weather/Assets.xcassets/AccentColor.colorset/Contents.json rename to example_projects/Weather/app/Weather/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/example_projects/Weather/Weather/Assets.xcassets/AppIcon.appiconset/Contents.json b/example_projects/Weather/app/Weather/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example_projects/Weather/Weather/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example_projects/Weather/app/Weather/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example_projects/Weather/Weather/Assets.xcassets/Contents.json b/example_projects/Weather/app/Weather/Assets.xcassets/Contents.json similarity index 100% rename from example_projects/Weather/Weather/Assets.xcassets/Contents.json rename to example_projects/Weather/app/Weather/Assets.xcassets/Contents.json diff --git a/example_projects/Weather/Weather/ContentView.swift b/example_projects/Weather/app/Weather/ContentView.swift similarity index 100% rename from example_projects/Weather/Weather/ContentView.swift rename to example_projects/Weather/app/Weather/ContentView.swift diff --git a/example_projects/Weather/Weather/Models/WeatherModels.swift b/example_projects/Weather/app/Weather/Models/WeatherModels.swift similarity index 100% rename from example_projects/Weather/Weather/Models/WeatherModels.swift rename to example_projects/Weather/app/Weather/Models/WeatherModels.swift diff --git a/example_projects/Weather/Weather/Services/MockWeatherAPIClient.swift b/example_projects/Weather/app/Weather/Services/MockWeatherAPIClient.swift similarity index 100% rename from example_projects/Weather/Weather/Services/MockWeatherAPIClient.swift rename to example_projects/Weather/app/Weather/Services/MockWeatherAPIClient.swift diff --git a/example_projects/Weather/Weather/Services/MockWeatherDTOFactories.swift b/example_projects/Weather/app/Weather/Services/MockWeatherDTOFactories.swift similarity index 100% rename from example_projects/Weather/Weather/Services/MockWeatherDTOFactories.swift rename to example_projects/Weather/app/Weather/Services/MockWeatherDTOFactories.swift diff --git a/example_projects/Weather/Weather/Services/WeatherAPIClient.swift b/example_projects/Weather/app/Weather/Services/WeatherAPIClient.swift similarity index 97% rename from example_projects/Weather/Weather/Services/WeatherAPIClient.swift rename to example_projects/Weather/app/Weather/Services/WeatherAPIClient.swift index 172e0c795..9de0f1707 100644 --- a/example_projects/Weather/Weather/Services/WeatherAPIClient.swift +++ b/example_projects/Weather/app/Weather/Services/WeatherAPIClient.swift @@ -10,7 +10,7 @@ struct WeatherAPIConfiguration: Sendable { let baseURL: URL static let production = WeatherAPIConfiguration( - baseURL: URL(string: "https://api.atmosweather.example/v1")! + baseURL: URL(string: "http://localhost:3001/v1")! ) } diff --git a/example_projects/Weather/Weather/Services/WeatherClientDTOs.swift b/example_projects/Weather/app/Weather/Services/WeatherClientDTOs.swift similarity index 100% rename from example_projects/Weather/Weather/Services/WeatherClientDTOs.swift rename to example_projects/Weather/app/Weather/Services/WeatherClientDTOs.swift diff --git a/example_projects/Weather/Weather/Services/WeatherService.swift b/example_projects/Weather/app/Weather/Services/WeatherService.swift similarity index 100% rename from example_projects/Weather/Weather/Services/WeatherService.swift rename to example_projects/Weather/app/Weather/Services/WeatherService.swift diff --git a/example_projects/Weather/Weather/Services/WeatherUnitFormatter.swift b/example_projects/Weather/app/Weather/Services/WeatherUnitFormatter.swift similarity index 100% rename from example_projects/Weather/Weather/Services/WeatherUnitFormatter.swift rename to example_projects/Weather/app/Weather/Services/WeatherUnitFormatter.swift diff --git a/example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift b/example_projects/Weather/app/Weather/Views/AtmosWeatherScreen.swift similarity index 100% rename from example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift rename to example_projects/Weather/app/Weather/Views/AtmosWeatherScreen.swift diff --git a/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift b/example_projects/Weather/app/Weather/Views/Backgrounds/AtmosBackground.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift rename to example_projects/Weather/app/Weather/Views/Backgrounds/AtmosBackground.swift diff --git a/example_projects/Weather/Weather/Views/Chrome/WeatherTopBar.swift b/example_projects/Weather/app/Weather/Views/Chrome/WeatherTopBar.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Chrome/WeatherTopBar.swift rename to example_projects/Weather/app/Weather/Views/Chrome/WeatherTopBar.swift diff --git a/example_projects/Weather/Weather/Views/Overlays/LocationPickerView.swift b/example_projects/Weather/app/Weather/Views/Overlays/LocationPickerView.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Overlays/LocationPickerView.swift rename to example_projects/Weather/app/Weather/Views/Overlays/LocationPickerView.swift diff --git a/example_projects/Weather/Weather/Views/Overlays/LocationRows.swift b/example_projects/Weather/app/Weather/Views/Overlays/LocationRows.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Overlays/LocationRows.swift rename to example_projects/Weather/app/Weather/Views/Overlays/LocationRows.swift diff --git a/example_projects/Weather/Weather/Views/Overlays/PrecipitationDetailView.swift b/example_projects/Weather/app/Weather/Views/Overlays/PrecipitationDetailView.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Overlays/PrecipitationDetailView.swift rename to example_projects/Weather/app/Weather/Views/Overlays/PrecipitationDetailView.swift diff --git a/example_projects/Weather/Weather/Views/Overlays/SettingsSheetView.swift b/example_projects/Weather/app/Weather/Views/Overlays/SettingsSheetView.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Overlays/SettingsSheetView.swift rename to example_projects/Weather/app/Weather/Views/Overlays/SettingsSheetView.swift diff --git a/example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift b/example_projects/Weather/app/Weather/Views/Sections/ConditionGrid.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift rename to example_projects/Weather/app/Weather/Views/Sections/ConditionGrid.swift diff --git a/example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift b/example_projects/Weather/app/Weather/Views/Sections/DailyForecastCard.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift rename to example_projects/Weather/app/Weather/Views/Sections/DailyForecastCard.swift diff --git a/example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift b/example_projects/Weather/app/Weather/Views/Sections/HourlyForecastCard.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift rename to example_projects/Weather/app/Weather/Views/Sections/HourlyForecastCard.swift diff --git a/example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift b/example_projects/Weather/app/Weather/Views/Sections/SunMiniCard.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift rename to example_projects/Weather/app/Weather/Views/Sections/SunMiniCard.swift diff --git a/example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift b/example_projects/Weather/app/Weather/Views/Sections/WeatherHeroView.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift rename to example_projects/Weather/app/Weather/Views/Sections/WeatherHeroView.swift diff --git a/example_projects/Weather/Weather/Views/Sections/WindCard.swift b/example_projects/Weather/app/Weather/Views/Sections/WindCard.swift similarity index 95% rename from example_projects/Weather/Weather/Views/Sections/WindCard.swift rename to example_projects/Weather/app/Weather/Views/Sections/WindCard.swift index 7a5fa1282..aa00f8274 100644 --- a/example_projects/Weather/Weather/Views/Sections/WindCard.swift +++ b/example_projects/Weather/app/Weather/Views/Sections/WindCard.swift @@ -124,8 +124,8 @@ private struct WindCompass: View { let opacity = cardinal ? 0.55 : intercardinal ? 0.32 : 0.16 let radians = (Double(angle) - 90) * .pi / 180 var path = Path() - path.move(to: CGPoint(x: center.x + cos(radians) * (outer - length), y: center.y + sin(radians) * (outer - length))) - path.addLine(to: CGPoint(x: center.x + cos(radians) * outer, y: center.y + sin(radians) * outer)) + path.move(to: CGPoint(x: center.x + CGFloat(cos(radians)) * (outer - length), y: center.y + CGFloat(sin(radians)) * (outer - length))) + path.addLine(to: CGPoint(x: center.x + CGFloat(cos(radians)) * outer, y: center.y + CGFloat(sin(radians)) * outer)) context.stroke(path, with: .color(current.theme.foreground.opacity(opacity)), style: StrokeStyle(lineWidth: width, lineCap: .round)) } } @@ -158,7 +158,7 @@ private struct WindCompass: View { private func point(center: CGPoint, radius: CGFloat, degrees: Double) -> CGPoint { let radians = (degrees - 90) * .pi / 180 - return CGPoint(x: center.x + cos(radians) * radius, y: center.y + sin(radians) * radius) + return CGPoint(x: center.x + CGFloat(cos(radians)) * radius, y: center.y + CGFloat(sin(radians)) * radius) } } diff --git a/example_projects/Weather/Weather/Views/Shared/AtmosGlass.swift b/example_projects/Weather/app/Weather/Views/Shared/AtmosGlass.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Shared/AtmosGlass.swift rename to example_projects/Weather/app/Weather/Views/Shared/AtmosGlass.swift diff --git a/example_projects/Weather/Weather/Views/Shared/MetricHelpers.swift b/example_projects/Weather/app/Weather/Views/Shared/MetricHelpers.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Shared/MetricHelpers.swift rename to example_projects/Weather/app/Weather/Views/Shared/MetricHelpers.swift diff --git a/example_projects/Weather/Weather/Views/Shared/WeatherIconView.swift b/example_projects/Weather/app/Weather/Views/Shared/WeatherIconView.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Shared/WeatherIconView.swift rename to example_projects/Weather/app/Weather/Views/Shared/WeatherIconView.swift diff --git a/example_projects/Weather/Weather/Views/Shared/WeatherPresentation.swift b/example_projects/Weather/app/Weather/Views/Shared/WeatherPresentation.swift similarity index 100% rename from example_projects/Weather/Weather/Views/Shared/WeatherPresentation.swift rename to example_projects/Weather/app/Weather/Views/Shared/WeatherPresentation.swift diff --git a/example_projects/Weather/Weather/WeatherApp.swift b/example_projects/Weather/app/Weather/WeatherApp.swift similarity index 100% rename from example_projects/Weather/Weather/WeatherApp.swift rename to example_projects/Weather/app/Weather/WeatherApp.swift diff --git a/example_projects/Weather/WeatherTests/Fixtures/default-locations.json b/example_projects/Weather/app/WeatherTests/Fixtures/default-locations.json similarity index 100% rename from example_projects/Weather/WeatherTests/Fixtures/default-locations.json rename to example_projects/Weather/app/WeatherTests/Fixtures/default-locations.json diff --git a/example_projects/Weather/WeatherTests/Fixtures/search-locations.json b/example_projects/Weather/app/WeatherTests/Fixtures/search-locations.json similarity index 100% rename from example_projects/Weather/WeatherTests/Fixtures/search-locations.json rename to example_projects/Weather/app/WeatherTests/Fixtures/search-locations.json diff --git a/example_projects/Weather/WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json b/example_projects/Weather/app/WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json similarity index 100% rename from example_projects/Weather/WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json rename to example_projects/Weather/app/WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json diff --git a/example_projects/Weather/WeatherTests/WeatherTests.swift b/example_projects/Weather/app/WeatherTests/WeatherTests.swift similarity index 100% rename from example_projects/Weather/WeatherTests/WeatherTests.swift rename to example_projects/Weather/app/WeatherTests/WeatherTests.swift diff --git a/example_projects/Weather/WeatherUITests/WeatherUITests.swift b/example_projects/Weather/app/WeatherUITests/WeatherUITests.swift similarity index 100% rename from example_projects/Weather/WeatherUITests/WeatherUITests.swift rename to example_projects/Weather/app/WeatherUITests/WeatherUITests.swift diff --git a/example_projects/Weather/WeatherUITests/WeatherUITestsLaunchTests.swift b/example_projects/Weather/app/WeatherUITests/WeatherUITestsLaunchTests.swift similarity index 100% rename from example_projects/Weather/WeatherUITests/WeatherUITestsLaunchTests.swift rename to example_projects/Weather/app/WeatherUITests/WeatherUITestsLaunchTests.swift diff --git a/example_projects/Weather/backend/.gitignore b/example_projects/Weather/backend/.gitignore new file mode 100644 index 000000000..c2658d7d1 --- /dev/null +++ b/example_projects/Weather/backend/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/example_projects/Weather/backend/package-lock.json b/example_projects/Weather/backend/package-lock.json new file mode 100644 index 000000000..e134f94f7 --- /dev/null +++ b/example_projects/Weather/backend/package-lock.json @@ -0,0 +1,600 @@ +{ + "name": "atmos-weather-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "atmos-weather-api", + "version": "1.0.0", + "dependencies": { + "@hono/node-server": "^1.14.1", + "hono": "^4.12.18" + }, + "devDependencies": { + "@types/node": "^22.15.17", + "tsx": "^4.19.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/example_projects/Weather/backend/package.json b/example_projects/Weather/backend/package.json new file mode 100644 index 000000000..43a22edb8 --- /dev/null +++ b/example_projects/Weather/backend/package.json @@ -0,0 +1,18 @@ +{ + "name": "atmos-weather-api", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "start": "tsx src/index.ts" + }, + "dependencies": { + "hono": "^4.12.18", + "@hono/node-server": "^1.14.1" + }, + "devDependencies": { + "tsx": "^4.19.4", + "@types/node": "^22.15.17" + } +} diff --git a/example_projects/Weather/backend/src/index.ts b/example_projects/Weather/backend/src/index.ts new file mode 100644 index 000000000..f8bf94299 --- /dev/null +++ b/example_projects/Weather/backend/src/index.ts @@ -0,0 +1,381 @@ +import { Hono } from "hono"; +import { logger } from "hono/logger"; +import { serve } from "@hono/node-server"; + +const app = new Hono().basePath("/v1"); + +app.use(logger()); + +// --------------------------------------------------------------------------- +// GET /v1/locations/default +// --------------------------------------------------------------------------- + +app.get("/locations/default", (c) => { + return c.json({ locations: defaultLocations }); +}); + +// --------------------------------------------------------------------------- +// GET /v1/locations/search?query= +// --------------------------------------------------------------------------- + +app.get("/locations/search", (c) => { + const query = (c.req.query("query") ?? "").trim().toLowerCase(); + if (!query) return c.json({ locations: [] }); + + const matches = searchPool.filter( + (loc) => + loc.name.toLowerCase().includes(query) || + loc.subtitle.toLowerCase().includes(query) || + (loc.country?.toLowerCase().includes(query) ?? false), + ); + return c.json({ locations: matches }); +}); + +// --------------------------------------------------------------------------- +// GET /v1/weather/:locationID +// --------------------------------------------------------------------------- + +app.get("/weather/:locationID", (c) => { + const locationID = c.req.param("locationID"); + const scenario = scenarioByLocationID[locationID]; + + if (!scenario) { + return c.json({ error: "Unknown location" }, 404); + } + + const current = currentWeather[scenario]; + const hourly = hourlyForecast[scenario]; + const daily = dailyForecast[scenario]; + const precipDetail = getPrecipitationDetail(scenario); + + return c.json({ + current: { ...current, id: `weather-current-${locationID}` }, + hourly, + daily, + precipitationDetailCurrent: { + ...precipDetail, + id: `weather-precip-${locationID}`, + }, + }); +}); + +// --------------------------------------------------------------------------- +// Data +// --------------------------------------------------------------------------- + +type Scenario = "clearDay" | "rainy" | "snowy" | "night" | "stormy"; + +const scenarioByLocationID: Record = { + "loc-current-san-francisco": "clearDay", + "loc-us-or-portland": "rainy", + "loc-us-co-aspen": "snowy", + "loc-is-reykjavik": "night", + "loc-us-la-new-orleans": "stormy", + "loc-jp-tokyo": "clearDay", + "loc-pt-lisbon": "clearDay", + "loc-fr-paris": "clearDay", + "loc-gb-london": "rainy", + "loc-de-berlin": "rainy", + "loc-us-ny-new-york": "clearDay", + "loc-au-sydney": "clearDay", + "loc-sg-singapore": "stormy", + "loc-in-mumbai": "clearDay", + "loc-eg-cairo": "clearDay", + "loc-za-cape-town": "clearDay", + "loc-is-capital-reykjavik": "night", + "loc-no-oslo": "snowy", + "loc-se-stockholm": "rainy", + "loc-ca-vancouver": "rainy", + "loc-ca-toronto": "rainy", + "loc-mx-mexico-city": "clearDay", + "loc-ar-buenos-aires": "clearDay", + "loc-kr-seoul": "clearDay", + "loc-th-bangkok": "stormy", + "loc-ae-dubai": "clearDay", + "loc-es-madrid": "clearDay", +}; + +const defaultLocations = [ + loc("loc-current-san-francisco", "San Francisco", "Current Location", null, 18, 20, 12, "mostly_sunny", 13, 24), + loc("loc-us-or-portland", "Portland", "Oregon, USA", null, 11, 13, 9, "light_rain", 13, 24), + loc("loc-us-co-aspen", "Aspen", "Colorado, USA", null, -4, -2, -10, "light_snow", 14, 24), + loc("loc-is-reykjavik", "Reykjavík", "Iceland", null, 3, 6, 1, "clear_night", 20, 24), + loc("loc-us-la-new-orleans", "New Orleans", "Louisiana, USA", null, 22, 26, 20, "thunderstorms", 15, 24), + loc("loc-jp-tokyo", "Tokyo", "Japan", null, 14, 17, 11, "partly_cloudy", 5, 24), + loc("loc-pt-lisbon", "Lisbon", "Portugal", null, 19, 22, 14, "sunny", 21, 24), +]; + +const searchPool = [ + loc("loc-fr-paris", "Paris", "Île-de-France, France", "FR", 15, 18, 11, "partly_cloudy", 22, 24), + loc("loc-gb-london", "London", "England, United Kingdom", "GB", 13, 16, 9, "light_rain", 21, 24), + loc("loc-de-berlin", "Berlin", "Germany", "DE", 11, 14, 7, "cloudy", 22, 24), + loc("loc-us-ny-new-york", "New York", "New York, USA", "US", 16, 19, 12, "sunny", 16, 24), + loc("loc-au-sydney", "Sydney", "New South Wales, Australia", "AU", 22, 25, 18, "sunny", 6, 24), + loc("loc-sg-singapore", "Singapore", "Singapore", "SG", 29, 31, 26, "thunderstorms", 4, 24), + loc("loc-in-mumbai", "Mumbai", "Maharashtra, India", "IN", 31, 33, 26, "hazy", 1, 54), + loc("loc-eg-cairo", "Cairo", "Egypt", "EG", 28, 32, 19, "sunny", 23, 24), + loc("loc-za-cape-town", "Cape Town", "Western Cape, South Africa", "ZA", 20, 23, 14, "mostly_sunny", 23, 24), + loc("loc-is-capital-reykjavik", "Reykjavík", "Capital Region, Iceland", "IS", 3, 6, 1, "clear_night", 20, 24), + loc("loc-no-oslo", "Oslo", "Norway", "NO", 5, 8, 1, "snow_showers", 22, 24), + loc("loc-se-stockholm", "Stockholm", "Sweden", "SE", 6, 9, 2, "partly_cloudy", 22, 24), + loc("loc-ca-vancouver", "Vancouver", "British Columbia, Canada", "CA", 9, 12, 6, "light_rain", 13, 24), + loc("loc-ca-toronto", "Toronto", "Ontario, Canada", "CA", 8, 12, 5, "cloudy", 16, 24), + loc("loc-mx-mexico-city", "Mexico City", "Mexico", "MX", 22, 26, 13, "sunny", 14, 24), + loc("loc-ar-buenos-aires", "Buenos Aires", "Argentina", "AR", 18, 21, 13, "partly_cloudy", 17, 24), + loc("loc-kr-seoul", "Seoul", "South Korea", "KR", 13, 17, 8, "clear_day", 5, 24), + loc("loc-th-bangkok", "Bangkok", "Thailand", "TH", 32, 34, 26, "thunderstorms", 3, 24), + loc("loc-ae-dubai", "Dubai", "United Arab Emirates", "AE", 33, 37, 26, "sunny", 0, 24), + loc("loc-es-madrid", "Madrid", "Spain", "ES", 19, 22, 12, "sunny", 22, 24), +]; + +function loc( + id: string, name: string, subtitle: string, country: string | null, + temperatureC: number, highC: number, lowC: number, + condition: string, hour: number, minute: number, +) { + return { id, name, subtitle, country, temperatureC, highC, lowC, condition, localTime: { hour, minute } }; +} + +// --------------------------------------------------------------------------- +// Weather data by scenario +// --------------------------------------------------------------------------- + +const currentWeather: Record = { + clearDay: { + id: "", temperatureC: 18, highC: 20, lowC: 12, feelsLikeC: 17, dewPointC: 9, + condition: "mostly_sunny", + solarProgress: { kind: "daylight", daylightFraction: 0.62 }, + sunrise: { hour: 6, minute: 18 }, sunset: { hour: 19, minute: 42 }, + airQualityIndex: 38, airQualityCategory: "good", + uvIndex: 6, uvCategory: "high", + windKph: 13, windDirectionDegrees: 292, humidity: 64, + visibilityKilometers: 16.1, pressureMillibars: 1018, pressureTrend: "rising", precipChance: 5, + }, + rainy: { + id: "", temperatureC: 11, highC: 13, lowC: 9, feelsLikeC: 9, dewPointC: 8, + condition: "light_rain", + solarProgress: { kind: "daylight", daylightFraction: 0.45 }, + sunrise: { hour: 6, minute: 42 }, sunset: { hour: 19, minute: 18 }, + airQualityIndex: 22, airQualityCategory: "good", + uvIndex: 1, uvCategory: "low", + windKph: 23, windDirectionDegrees: 225, humidity: 89, + visibilityKilometers: 9.7, pressureMillibars: 1006, pressureTrend: "falling", precipChance: 78, + }, + snowy: { + id: "", temperatureC: -4, highC: -2, lowC: -10, feelsLikeC: -8, dewPointC: -7, + condition: "light_snow", + solarProgress: { kind: "daylight", daylightFraction: 0.50 }, + sunrise: { hour: 7, minute: 14 }, sunset: { hour: 17, minute: 38 }, + airQualityIndex: 18, airQualityCategory: "good", + uvIndex: 2, uvCategory: "low", + windKph: 10, windDirectionDegrees: 0, humidity: 78, + visibilityKilometers: 6.4, pressureMillibars: 1022, pressureTrend: "steady", precipChance: 65, + }, + night: { + id: "", temperatureC: 3, highC: 6, lowC: 1, feelsLikeC: 1, dewPointC: 0, + condition: "clear_night", + solarProgress: { kind: "after_sunset", daylightFraction: null }, + sunrise: { hour: 5, minute: 46 }, sunset: { hour: 20, minute: 24 }, + airQualityIndex: 12, airQualityCategory: "good", + uvIndex: 0, uvCategory: "none", + windKph: 6, windDirectionDegrees: 45, humidity: 71, + visibilityKilometers: 16.1, pressureMillibars: 1014, pressureTrend: "steady", precipChance: 8, + }, + stormy: { + id: "", temperatureC: 22, highC: 26, lowC: 20, feelsLikeC: 24, dewPointC: 19, + condition: "thunderstorms", + solarProgress: { kind: "daylight", daylightFraction: 0.78 }, + sunrise: { hour: 6, minute: 8 }, sunset: { hour: 19, minute: 52 }, + airQualityIndex: 55, airQualityCategory: "moderate", + uvIndex: 3, uvCategory: "moderate", + windKph: 35, windDirectionDegrees: 180, humidity: 86, + visibilityKilometers: 4.8, pressureMillibars: 998, pressureTrend: "falling", precipChance: 92, + }, +}; + +function h(kind: "current" | "clock", temp: number, condition: string, hour?: number, minute?: number) { + const id = kind === "current" + ? `hourly-now-${temp}-${condition}` + : `hourly-${hour}-${minute}-${temp}-${condition}`; + return { id, hour: { kind, hour: hour ?? null, minute: minute ?? null }, temperatureC: temp, condition }; +} + +const hourlyForecast: Record = { + clearDay: [ + h("current", 18, "sunny"), h("clock", 19, "sunny", 14, 0), h("clock", 19, "sunny", 15, 0), + h("clock", 20, "sunny", 16, 0), h("clock", 19, "sunny", 17, 0), h("clock", 18, "partly_cloudy", 18, 0), + h("clock", 17, "partly_cloudy", 19, 0), h("clock", 16, "clear_night", 20, 0), + h("clock", 14, "clear_night", 21, 0), h("clock", 13, "clear_night", 22, 0), + h("clock", 13, "clear_night", 23, 0), h("clock", 12, "clear_night", 0, 0), + ], + rainy: [ + h("current", 11, "light_rain"), h("clock", 12, "light_rain", 14, 0), h("clock", 12, "light_rain", 15, 0), + h("clock", 13, "heavy_rain", 16, 0), h("clock", 12, "heavy_rain", 17, 0), h("clock", 12, "light_rain", 18, 0), + h("clock", 11, "light_rain", 19, 0), h("clock", 11, "cloudy", 20, 0), + h("clock", 10, "cloudy", 21, 0), h("clock", 9, "cloudy", 22, 0), + h("clock", 9, "light_rain", 23, 0), h("clock", 9, "light_rain", 0, 0), + ], + snowy: [ + h("current", -4, "light_snow"), h("clock", -3, "light_snow", 14, 0), h("clock", -3, "light_snow", 15, 0), + h("clock", -2, "cloudy", 16, 0), h("clock", -3, "cloudy", 17, 0), h("clock", -4, "light_snow", 18, 0), + h("clock", -6, "light_snow", 19, 0), h("clock", -7, "light_snow", 20, 0), + h("clock", -8, "light_snow", 21, 0), h("clock", -9, "cloudy", 22, 0), + h("clock", -9, "cloudy", 23, 0), h("clock", -10, "clear_night", 0, 0), + ], + night: [ + h("current", 3, "clear_night"), h("clock", 3, "clear_night", 23, 0), h("clock", 2, "clear_night", 0, 0), + h("clock", 2, "clear_night", 1, 0), h("clock", 1, "clear_night", 2, 0), h("clock", 1, "clear_night", 3, 0), + h("clock", 1, "clear_night", 4, 0), h("clock", 1, "clear_night", 5, 0), + h("clock", 2, "partly_cloudy", 6, 0), h("clock", 3, "sunny", 7, 0), + h("clock", 4, "sunny", 8, 0), h("clock", 6, "sunny", 9, 0), + ], + stormy: [ + h("current", 22, "thunderstorms"), h("clock", 23, "thunderstorms", 14, 0), h("clock", 24, "heavy_rain", 15, 0), + h("clock", 24, "heavy_rain", 16, 0), h("clock", 26, "thunderstorms", 17, 0), h("clock", 26, "thunderstorms", 18, 0), + h("clock", 25, "light_rain", 19, 0), h("clock", 23, "light_rain", 20, 0), + h("clock", 22, "cloudy", 21, 0), h("clock", 21, "cloudy", 22, 0), + h("clock", 21, "cloudy", 23, 0), h("clock", 20, "cloudy", 0, 0), + ], +}; + +function d(kind: "today" | "weekday", condition: string, low: number, high: number, weekLow: number, weekHigh: number, weekdayRawValue?: number) { + const dayPart = kind === "today" ? "today" : `weekday-${weekdayRawValue}`; + return { + id: `daily-${dayPart}-${condition}-${low}-${high}`, + day: { kind, weekdayRawValue: weekdayRawValue ?? null }, + condition, lowC: low, highC: high, weekLowC: weekLow, weekHighC: weekHigh, + }; +} + +const dailyForecast: Record = { + clearDay: [ + d("today", "sunny", 12, 20, 9, 23), + d("weekday", "sunny", 13, 21, 9, 23, 4), + d("weekday", "partly_cloudy", 13, 21, 9, 23, 5), + d("weekday", "cloudy", 11, 19, 9, 23, 6), + d("weekday", "light_rain", 9, 16, 9, 23, 7), + d("weekday", "light_rain", 9, 14, 9, 23, 1), + d("weekday", "sunny", 11, 19, 9, 23, 2), + ], + rainy: [ + d("today", "light_rain", 9, 13, 6, 17), + d("weekday", "light_rain", 8, 12, 6, 17, 4), + d("weekday", "cloudy", 8, 13, 6, 17, 5), + d("weekday", "cloudy", 9, 14, 6, 17, 6), + d("weekday", "partly_cloudy", 10, 17, 6, 17, 7), + d("weekday", "sunny", 9, 16, 6, 17, 1), + d("weekday", "light_rain", 6, 12, 6, 17, 2), + ], + snowy: [ + d("today", "light_snow", -10, -2, -13, 1), + d("weekday", "light_snow", -11, -3, -13, 1, 4), + d("weekday", "cloudy", -9, -1, -13, 1, 5), + d("weekday", "partly_cloudy", -7, 1, -13, 1, 6), + d("weekday", "sunny", -8, 0, -13, 1, 7), + d("weekday", "light_snow", -12, -6, -13, 1, 1), + d("weekday", "light_snow", -13, -8, -13, 1, 2), + ], + night: [ + d("today", "clear_night", 1, 6, -1, 9), + d("weekday", "sunny", 2, 8, -1, 9, 4), + d("weekday", "cloudy", 2, 7, -1, 9, 5), + d("weekday", "light_rain", 1, 5, -1, 9, 6), + d("weekday", "light_rain", 0, 4, -1, 9, 7), + d("weekday", "sunny", 2, 8, -1, 9, 1), + d("weekday", "sunny", 3, 9, -1, 9, 2), + ], + stormy: [ + d("today", "thunderstorms", 20, 26, 18, 31), + d("weekday", "light_rain", 21, 28, 18, 31, 4), + d("weekday", "cloudy", 22, 29, 18, 31, 5), + d("weekday", "partly_cloudy", 22, 30, 18, 31, 6), + d("weekday", "sunny", 23, 31, 18, 31, 7), + d("weekday", "sunny", 21, 29, 18, 31, 1), + d("weekday", "thunderstorms", 19, 24, 18, 31, 2), + ], +}; + +interface PrecipDetail { + id: string; + temperatureC: number; + highC: number; + lowC: number; + feelsLikeC: number; + dewPointC: number; + condition: string; + solarProgress: { kind: string; daylightFraction: number | null }; + sunrise: { hour: number; minute: number }; + sunset: { hour: number; minute: number }; + airQualityIndex: number; + airQualityCategory: string; + uvIndex: number; + uvCategory: string; + windKph: number; + windDirectionDegrees: number; + humidity: number; + visibilityKilometers: number; + pressureMillibars: number; + pressureTrend: string; + precipChance: number; +} + +function getPrecipitationDetail(scenario: Scenario): PrecipDetail { + const detail = precipitationDetail[scenario]!; + return { + ...detail, + precipChance: Math.round(detail.precipChance), + }; +} + +const precipitationDetail: Partial> = { + clearDay: { + id: "", temperatureC: 18, highC: 20, lowC: 12, feelsLikeC: 17, dewPointC: 9, + condition: "mostly_sunny", + solarProgress: { kind: "daylight", daylightFraction: 0.62 }, + sunrise: { hour: 6, minute: 18 }, sunset: { hour: 19, minute: 42 }, + airQualityIndex: 38, airQualityCategory: "good", + uvIndex: 6, uvCategory: "high", + windKph: 13, windDirectionDegrees: 292, humidity: 64, + visibilityKilometers: 16.1, pressureMillibars: 1018, pressureTrend: "rising", precipChance: 5, + }, + snowy: { + id: "", temperatureC: -4, highC: -2, lowC: -10, feelsLikeC: -8, dewPointC: -7, + condition: "light_snow", + solarProgress: { kind: "daylight", daylightFraction: 0.50 }, + sunrise: { hour: 7, minute: 14 }, sunset: { hour: 17, minute: 38 }, + airQualityIndex: 18, airQualityCategory: "good", + uvIndex: 2, uvCategory: "low", + windKph: 10, windDirectionDegrees: 0, humidity: 78, + visibilityKilometers: 6.4, pressureMillibars: 1022, pressureTrend: "steady", precipChance: 65, + }, + night: { + id: "", temperatureC: 3, highC: 6, lowC: 1, feelsLikeC: 1, dewPointC: 0, + condition: "clear_night", + solarProgress: { kind: "after_sunset", daylightFraction: null }, + sunrise: { hour: 5, minute: 46 }, sunset: { hour: 20, minute: 24 }, + airQualityIndex: 12, airQualityCategory: "good", + uvIndex: 0, uvCategory: "none", + windKph: 6, windDirectionDegrees: 45, humidity: 71, + visibilityKilometers: 16.1, pressureMillibars: 1014, pressureTrend: "steady", precipChance: 8, + }, + stormy: { + id: "", temperatureC: 22, highC: 26, lowC: 20, feelsLikeC: 24, dewPointC: 19, + condition: "thunderstorms", + solarProgress: { kind: "daylight", daylightFraction: 0.78 }, + sunrise: { hour: 6, minute: 8 }, sunset: { hour: 19, minute: 52 }, + airQualityIndex: 55, airQualityCategory: "moderate", + uvIndex: 3, uvCategory: "moderate", + windKph: 35, windDirectionDegrees: 180, humidity: 86, + visibilityKilometers: 4.8, pressureMillibars: 998, pressureTrend: "falling", precipChance: 92, + }, +}; + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- + +const port = parseInt(process.env.PORT ?? "3001", 10); + +serve({ fetch: app.fetch, port }, (info) => { + console.log(`Atmos Weather API running on http://localhost:${info.port}`); +}); diff --git a/example_projects/Weather/backend/tsconfig.json b/example_projects/Weather/backend/tsconfig.json new file mode 100644 index 000000000..2f98f9751 --- /dev/null +++ b/example_projects/Weather/backend/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["src"] +}