Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add devcontainers configuration #1212

Draft
wants to merge 10 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "Hangar",
"image": "mcr.microsoft.com/devcontainers/java:0-17",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/java:1": {
"version": "none", // Java is already present.
"installMaven": "true"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
},
"forwardPorts": [3333, 24678],
"portsAttributes": {
"1025": {
"label": "MailSlurper SMTP"
},
"3333": {
"label": "Nuxt Frontend"
},
"4436-4437": {
"label": "MailSlurper HTTP"
},
"5432": {
"label": "Postgres"
},
"8080": {
"label": "Maven Backend"
},
"9411": {
"label": "Zipkin"
},
"24678": {
"label": "Nuxt WebSocket"
}
},

"postAttachCommand": "bash ./.devcontainer/post-attach.sh",
"postCreateCommand": "bash ./.devcontainer/post-create.sh",
"remoteEnv": {
"BACKEND_HOST": "https://localhost:8080",
"BACKEND_DATA_HOST": "https://localhost:8080"
},
"customizations": {
"vscode": {
"extensions": [
// Language support
"Vue.volar",
"vscjava.vscode-java-pack",
// Spring Boot Tools
"vmware.vscode-spring-boot",
"vscjava.vscode-spring-boot-dashboard",
// Editor tools
"EditorConfig.EditorConfig"
]
}
}
}
18 changes: 18 additions & 0 deletions .devcontainer/post-attach.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# Workaround required for GitHub Codespaces.
# Preflight requests are not handled correctly if the port is Private.
# @see https://github.com/orgs/community/discussions/15351

PORTS=(3333 24678);

if [ -n "$CODESPACES" ]; then
# Required to prevent race condition
sleep 3

for PORT in ${PORTS[@]}
do
echo "Port $PORT is now public as an workaround to an Codespaces issue."
gh codespace ports visibility $PORT:public -c $CODESPACE_NAME
done
fi
12 changes: 12 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

# Sets up an bare-bones Hangar environment to make developing in Codespaces quicker and easier.

# Create Docker containers (db & email)
cd docker && docker-compose -f dev.yml up --no-start

# Install PNPM dependencies (frontend)
cd ../frontend && pnpm i --silent

# Install Maven dependencies (backend)
cd ../backend && exec mvn dependency:go-offline -q
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ src/server/middleware/@proxy/proxy.ts
.output
tsconfig.tsbuildinfo
.unlighthouse
.pnpm-store
34 changes: 34 additions & 0 deletions frontend/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Icons from "unplugin-icons/vite";
import Components from "unplugin-vue-components/vite";
import { ProxyOptions } from "@nuxt-alt/proxy";
import { defineNuxtConfig } from "nuxt/config";
import type { ViteConfig } from "nuxt/schema";
import type { HmrOptions } from "vite";
import prettier from "./src/vite/prettier";
import unocss from "./unocss.config";

Expand Down Expand Up @@ -72,11 +74,28 @@ export default defineNuxtConfig({
// fix: true,
// }),
prettier(),

(() => {
if (process.env.CODESPACES)
return {
name: "client-host",
transform(code, id) {
if (id.endsWith("dist/client/client.mjs") || id.endsWith("dist/client/env.mjs")) {
return code
.replace("__HMR_HOSTNAME__", JSON.stringify((<HmrOptions>getDevContainerWorkaround().server?.hmr).host))
.replace("__HMR_PROTOCOL__", JSON.stringify("wss")); // seems to be ignoring the server.hmr.protocol value
}

return code;
},
};
})(),
],
ssr: {
// Workaround until they support native ESM
noExternal: ["vue3-popper"],
},
server: getDevContainerWorkaround(),
},
experimental: {
writeEarlyHints: false,
Expand Down Expand Up @@ -137,3 +156,18 @@ function defineProxyBackend(): ProxyOptions {
changeOrigin: true,
};
}

// Codespaces workaround
function getDevContainerWorkaround(): ViteConfig {
return process.env.CODESPACES === "true"
? {
server: {
https: true,
hmr: {
host: `${process.env.CODESPACE_NAME}-24678.${process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`,
protocol: "wss",
},
},
}
: {};
}
11 changes: 6 additions & 5 deletions frontend/src/composables/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,19 @@ export function useConfig(): Config {
key = location.hostname;
}

if (key.startsWith("192.168")) {
if (key.startsWith("192.168") || key.endsWith(".ngrok.io")) {
const local = configs.localhost;
local.publicHost = local.publicHost.replace("http://localhost", "https://" + key);
local.proxyHost = local.proxyHost.replace("http://localhost", "https://" + key);
configLog("useConfig", key);
return local;
} else if (key.endsWith(".ngrok.io")) {
} else if (key.endsWith(".app.github.dev")) {
const [codespaceName, codespacePortForwardDomain] = location.hostname.split("-3333");
const local = configs.localhost;
local.publicHost = local.publicHost.replace("http://localhost", "https://" + key);
local.proxyHost = local.proxyHost.replace("http://localhost", "https://" + key);
local.publicHost = local.publicHost.replace("http://localhost:3333", "https://" + codespaceName + "-3333" + codespacePortForwardDomain);
local.proxyHost = local.proxyHost.replace("http://localhost:8080", "https://" + codespaceName + "-8080" + codespacePortForwardDomain);
configLog("useConfig", key);
return local;
return local; // TODO: Fix http proxy error:Error: write EPROTO 00178A8AFB7E0000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:
}
}
configLog("useConfig", key);
Expand Down
100 changes: 0 additions & 100 deletions frontend/src/types/generated/icons.d.ts
Original file line number Diff line number Diff line change
@@ -1,100 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

export {};

declare module "@vue/runtime-core" {
export interface GlobalComponents {
IconMdiAccountPlus: typeof import("~icons/mdi/account-plus")["default"];
IconMdiAlert: typeof import("~icons/mdi/alert")["default"];
IconMdiAlertBox: typeof import("~icons/mdi/alert-box")["default"];
IconMdiAlertDecagram: typeof import("~icons/mdi/alert-decagram")["default"];
IconMdiAlertOutline: typeof import("~icons/mdi/alert-outline")["default"];
IconMdiBell: typeof import("~icons/mdi/bell")["default"];
IconMdiBellBadge: typeof import("~icons/mdi/bell-badge")["default"];
IconMdiBellOutline: typeof import("~icons/mdi/bell-outline")["default"];
IconMdiBin: typeof import("~icons/mdi/bin")["default"];
IconMdiBookshelf: typeof import("~icons/mdi/bookshelf")["default"];
IconMdiCached: typeof import("~icons/mdi/cached")["default"];
IconMdiCalendar: typeof import("~icons/mdi/calendar")["default"];
IconMdiCancel: typeof import("~icons/mdi/cancel")["default"];
IconMdiCashMultiple: typeof import("~icons/mdi/cash-multiple")["default"];
IconMdiChat: typeof import("~icons/mdi/chat")["default"];
IconMdiCheck: typeof import("~icons/mdi/check")["default"];
IconMdiCheckBold: typeof import("~icons/mdi/check-bold")["default"];
IconMdiCheckboxMarkedCircle: typeof import("~icons/mdi/checkbox-marked-circle")["default"];
IconMdiCheckDecagram: typeof import("~icons/mdi/check-decagram")["default"];
IconMdiCheckDecagramOutline: typeof import("~icons/mdi/check-decagram-outline")["default"];
IconMdiChevronDown: typeof import("~icons/mdi/chevron-down")["default"];
IconMdiCircle: typeof import("~icons/mdi/circle")["default"];
IconMdiClipboardOutline: typeof import("~icons/mdi/clipboard-outline")["default"];
IconMdiClose: typeof import("~icons/mdi/close")["default"];
IconMdiCloseCircle: typeof import("~icons/mdi/close-circle")["default"];
IconMdiCloudSearch: typeof import("~icons/mdi/cloud-search")["default"];
IconMdiCodeBracesBox: typeof import("~icons/mdi/code-braces-box")["default"];
IconMdiCogTransfer: typeof import("~icons/mdi/cog-transfer")["default"];
IconMdiComment: typeof import("~icons/mdi/comment")["default"];
IconMdiContentCopy: typeof import("~icons/mdi/content-copy")["default"];
IconMdiContentSave: typeof import("~icons/mdi/content-save")["default"];
IconMdiController: typeof import("~icons/mdi/controller")["default"];
IconMdiDelete: typeof import("~icons/mdi/delete")["default"];
IconMdiDeleteAlert: typeof import("~icons/mdi/delete-alert")["default"];
IconMdiDownload: typeof import("~icons/mdi/download")["default"];
IconMdiDownloadOutline: typeof import("~icons/mdi/download-outline")["default"];
IconMdiEarth: typeof import("~icons/mdi/earth")["default"];
IconMdiEye: typeof import("~icons/mdi/eye")["default"];
IconMdiEyeOff: typeof import("~icons/mdi/eye-off")["default"];
IconMdiFileDocument: typeof import("~icons/mdi/file-document")["default"];
IconMdiFlag: typeof import("~icons/mdi/flag")["default"];
IconMdiFolderPlusOutline: typeof import("~icons/mdi/folder-plus-outline")["default"];
IconMdiFormatListNumbered: typeof import("~icons/mdi/format-list-numbered")["default"];
IconMdiGamepadRoundLeft: typeof import("~icons/mdi/gamepad-round-left")["default"];
IconMdiHelpCircleOutline: typeof import("~icons/mdi/help-circle-outline")["default"];
IconMdiHome: typeof import("~icons/mdi/home")["default"];
IconMdiHorseVariant: typeof import("~icons/mdi/horse-variant")["default"];
IconMdiInformation: typeof import("~icons/mdi/information")["default"];
IconMdiInformationOutline: typeof import("~icons/mdi/information-outline")["default"];
IconMdiKeyOutline: typeof import("~icons/mdi/key-outline")["default"];
IconMdiLeaf: typeof import("~icons/mdi/leaf")["default"];
IconMdiLicense: typeof import("~icons/mdi/license")["default"];
IconMdiLink: typeof import("~icons/mdi/link")["default"];
IconMdiListStatus: typeof import("~icons/mdi/list-status")["default"];
IconMdiLockOpenOutline: typeof import("~icons/mdi/lock-open-outline")["default"];
IconMdiLockOutline: typeof import("~icons/mdi/lock-outline")["default"];
IconMdiMenu: typeof import("~icons/mdi/menu")["default"];
IconMdiMenuDown: typeof import("~icons/mdi/menu-down")["default"];
IconMdiOpenInNew: typeof import("~icons/mdi/open-in-new")["default"];
IconMdiPencil: typeof import("~icons/mdi/pencil")["default"];
IconMdiPin: typeof import("~icons/mdi/pin")["default"];
IconMdiPinOff: typeof import("~icons/mdi/pin-off")["default"];
IconMdiPlay: typeof import("~icons/mdi/play")["default"];
IconMdiPlus: typeof import("~icons/mdi/plus")["default"];
IconMdiPuzzleOutline: typeof import("~icons/mdi/puzzle-outline")["default"];
IconMdiRefresh: typeof import("~icons/mdi/refresh")["default"];
IconMdiRenameBox: typeof import("~icons/mdi/rename-box")["default"];
IconMdiSend: typeof import("~icons/mdi/send")["default"];
IconMdiShape: typeof import("~icons/mdi/shape")["default"];
IconMdiShieldSun: typeof import("~icons/mdi/shield-sun")["default"];
IconMdiSort: typeof import("~icons/mdi/sort")["default"];
IconMdiSortAscending: typeof import("~icons/mdi/sort-ascending")["default"];
IconMdiSortDescending: typeof import("~icons/mdi/sort-descending")["default"];
IconMdiSortVariant: typeof import("~icons/mdi/sort-variant")["default"];
IconMdiStar: typeof import("~icons/mdi/star")["default"];
IconMdiStarOutline: typeof import("~icons/mdi/star-outline")["default"];
IconMdiStop: typeof import("~icons/mdi/stop")["default"];
IconMdiSubdirectoryArrowLeft: typeof import("~icons/mdi/subdirectory-arrow-left")["default"];
IconMdiTag: typeof import("~icons/mdi/tag")["default"];
IconMdiTools: typeof import("~icons/mdi/tools")["default"];
IconMdiTrophy: typeof import("~icons/mdi/trophy")["default"];
IconMdiUndo: typeof import("~icons/mdi/undo")["default"];
IconMdiUpload: typeof import("~icons/mdi/upload")["default"];
IconMdiWeatherNight: typeof import("~icons/mdi/weather-night")["default"];
IconMdiWhiteBalanceSunny: typeof import("~icons/mdi/white-balance-sunny")["default"];
RouterLink: typeof import("vue-router")["RouterLink"];
RouterView: typeof import("vue-router")["RouterView"];
}
}