From d36c60a138cfb808d6756d6c5c9675fa8988bf88 Mon Sep 17 00:00:00 2001 From: Steven Murawski Date: Thu, 14 Jul 2022 23:30:51 +0000 Subject: [PATCH 1/4] first steps to add c# wasi projects --- .gitignore | 2 + templates/csharp/.github/workflows/build.yml | 23 ++ .../.github/workflows/release.azurecr.yml | 73 ++++ .../.github/workflows/release.hippo.yml | 61 +++ .../.github/workflows/release.nopublish.yml | 39 ++ .../csharp/.github/workflows/release.yml | 72 ++++ templates/csharp/.gitignore.removeext | 350 ++++++++++++++++++ templates/csharp/.vscode/extensions.json | 5 + templates/csharp/.vscode/launch.json | 24 ++ templates/csharp/.vscode/settings.json | 9 + templates/csharp/.vscode/tasks.json | 50 +++ templates/csharp/HIPPOFACTS | 8 + templates/csharp/LICENSE | 21 ++ templates/csharp/Program.cs | 4 + templates/csharp/README.md | 16 + templates/csharp/WasmProject.csproj | 14 + ts/generators/app/index.ts | 6 +- ts/generators/app/languages/csharp.ts | 49 +++ 18 files changed, 825 insertions(+), 1 deletion(-) create mode 100644 templates/csharp/.github/workflows/build.yml create mode 100644 templates/csharp/.github/workflows/release.azurecr.yml create mode 100644 templates/csharp/.github/workflows/release.hippo.yml create mode 100644 templates/csharp/.github/workflows/release.nopublish.yml create mode 100644 templates/csharp/.github/workflows/release.yml create mode 100644 templates/csharp/.gitignore.removeext create mode 100644 templates/csharp/.vscode/extensions.json create mode 100644 templates/csharp/.vscode/launch.json create mode 100644 templates/csharp/.vscode/settings.json create mode 100644 templates/csharp/.vscode/tasks.json create mode 100644 templates/csharp/HIPPOFACTS create mode 100644 templates/csharp/LICENSE create mode 100644 templates/csharp/Program.cs create mode 100644 templates/csharp/README.md create mode 100644 templates/csharp/WasmProject.csproj create mode 100644 ts/generators/app/languages/csharp.ts diff --git a/.gitignore b/.gitignore index e3a5e0a..48e26b3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules coverage generators/**/* *.tgz +bin/ +obj/ \ No newline at end of file diff --git a/templates/csharp/.github/workflows/build.yml b/templates/csharp/.github/workflows/build.yml new file mode 100644 index 0000000..d178d07 --- /dev/null +++ b/templates/csharp/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + include-prerelease: true + - name: Build + run: dotnet build + diff --git a/templates/csharp/.github/workflows/release.azurecr.yml b/templates/csharp/.github/workflows/release.azurecr.yml new file mode 100644 index 0000000..2f27c6b --- /dev/null +++ b/templates/csharp/.github/workflows/release.azurecr.yml @@ -0,0 +1,73 @@ +name: release +on: + push: + branches: + - main + tags: + - "v*" + +env: + ACR_NAME: <%= registryName %> + +jobs: + build: + name: Build release assets + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Set the release version (tag) + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version (main) + if: github.ref == 'refs/heads/main' + shell: bash + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Build + run: dotnet publish -c Release + + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: WasmProject.wasm + path: bin/Release/net7.0/WasmProject.wasm + + publish: + name: Push released module to OCI registry + runs-on: ubuntu-latest + needs: build + steps: + + - name: Install wasm-to-oci + run: | + mkdir tools + wget https://github.com/engineerd/wasm-to-oci/releases/download/v0.1.1/linux-amd64-wasm-to-oci -q -O ./tools/wasm-to-oci + chmod +x ./tools/wasm-to-oci + + - name: Set the release version + if: startsWith(github.ref, 'refs/tags/v') + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version + if: github.ref == 'refs/heads/main' + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Download release assets + uses: actions/download-artifact@v1 + with: + name: WasmProject.wasm + path: ./assets + + - name: Log into registry + run: az acr login -n ${ACR_NAME} -u ${{ secrets.ACR_SP_ID }} -p ${{ secrets.ACR_SP_PASSWORD }} + + - name: Publish to registry + run: ./tools/wasm-to-oci push ./assets/WasmProject.wasm ${ACR_NAME}.azurecr.io/<%= moduleName %>:${RELEASE_VERSION} diff --git a/templates/csharp/.github/workflows/release.hippo.yml b/templates/csharp/.github/workflows/release.hippo.yml new file mode 100644 index 0000000..31ba89d --- /dev/null +++ b/templates/csharp/.github/workflows/release.hippo.yml @@ -0,0 +1,61 @@ +name: release +on: + push: + branches: + - main + tags: + - "v*" + +env: + BINDLE_URL: <%= serverUrl %> + HIPPO_URL: <%= hippoUrl %> + +jobs: + publish: + name: Build and publish release assets + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Set the release version (tag) + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version (main) + if: github.ref == 'refs/heads/main' + shell: bash + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Build + run: dotnet publish -c Release + + - name: Install Hippo CLI + run: | + mkdir -p tools/tmp + wget https://github.com/deislabs/hippo-cli/releases/download/v0.9.0/hippo-v0.9.0-linux-amd64.tar.gz -q -O ./tools/tmp/hippo.tar.gz + tar xf ./tools/tmp/hippo.tar.gz -C ./tools + + - name: Publish bindle (tagged) + if: startsWith(github.ref, 'refs/tags/v') + env: + HIPPO_USERNAME: ${{ secrets.HIPPO_USERNAME }} + HIPPO_PASSWORD: ${{ secrets.HIPPO_PASSWORD }} + BINDLE_USERNAME: ${{ secrets.BINDLE_USERNAME }} + BINDLE_PASSWORD: ${{ secrets.BINDLE_PASSWORD }} + run: ./tools/hippo push . -v production -o message + + - name: Publish bindle (main) + if: github.ref == 'refs/heads/main' + env: + HIPPO_USERNAME: ${{ secrets.HIPPO_USERNAME }} + HIPPO_PASSWORD: ${{ secrets.HIPPO_PASSWORD }} + BINDLE_USERNAME: ${{ secrets.BINDLE_USERNAME }} + BINDLE_PASSWORD: ${{ secrets.BINDLE_PASSWORD }} + run: USER=canary ./tools/hippo push . -o message diff --git a/templates/csharp/.github/workflows/release.nopublish.yml b/templates/csharp/.github/workflows/release.nopublish.yml new file mode 100644 index 0000000..3258596 --- /dev/null +++ b/templates/csharp/.github/workflows/release.nopublish.yml @@ -0,0 +1,39 @@ +name: release +on: + push: + branches: + - main + tags: + - "v*" + +jobs: + build: + name: Build release assets + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Set the release version (tag) + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version (main) + if: github.ref == 'refs/heads/main' + shell: bash + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Build + run: dotnet publish -c Release + + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: WasmProject.wasm + path: bin/Release/net7.0/WasmProject.wasm diff --git a/templates/csharp/.github/workflows/release.yml b/templates/csharp/.github/workflows/release.yml new file mode 100644 index 0000000..b82c35d --- /dev/null +++ b/templates/csharp/.github/workflows/release.yml @@ -0,0 +1,72 @@ +name: release +on: + push: + branches: + - main + tags: + - "v*" + +jobs: + build: + name: Build release assets + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install .NET SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + include-prerelease: true + + - name: Set the release version (tag) + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version (main) + if: github.ref == 'refs/heads/main' + shell: bash + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Build + run: dotnet publish -c Release + + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: WasmProject.wasm + path: bin/Release/net7.0/WasmProject.wasm + + publish: + name: Push released module to OCI registry + runs-on: ubuntu-latest + needs: build + steps: + + - name: Install wasm-to-oci + run: | + mkdir tools + wget https://github.com/engineerd/wasm-to-oci/releases/download/v0.1.1/linux-amd64-wasm-to-oci -q -O ./tools/wasm-to-oci + chmod +x ./tools/wasm-to-oci + + - name: Set the release version + if: startsWith(github.ref, 'refs/tags/v') + run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + - name: Set the release version + if: github.ref == 'refs/heads/main' + run: echo "RELEASE_VERSION=canary" >> $GITHUB_ENV + + - name: Download release assets + uses: actions/download-artifact@v1 + with: + name: WasmProject.wasm + path: ./assets + + # TODO: FILL IN THE << ... >> bit below + + # - name: Log into registry + # run: << your docker login equivalent here >> + + # - name: Publish to registry + # run: ./tools/wasm-to-oci push ./assets/<%= moduleName %>.wasm << your OCI reference here e.g. myname.myprovider.io/<%= moduleName %>:${RELEASE_VERSION} >> diff --git a/templates/csharp/.gitignore.removeext b/templates/csharp/.gitignore.removeext new file mode 100644 index 0000000..dfcfd56 --- /dev/null +++ b/templates/csharp/.gitignore.removeext @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/templates/csharp/.vscode/extensions.json b/templates/csharp/.vscode/extensions.json new file mode 100644 index 0000000..442a1b5 --- /dev/null +++ b/templates/csharp/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "vadimcn.vscode-lldb" + ] +} diff --git a/templates/csharp/.vscode/launch.json b/templates/csharp/.vscode/launch.json new file mode 100644 index 0000000..ac35c47 --- /dev/null +++ b/templates/csharp/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "configurations": [ + { + "name": "Debug Native", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/target/debug/${workspaceRootFolderName}", + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["csharp"], + "preLaunchTask": "Cargo: Build Native" + }, + { + "name": "Debug WASM", + "type": "lldb", + "request": "launch", + "program": "wasmtime", + "args": ["run", "${workspaceFolder}/target/wasm32-wasi/debug/${workspaceRootFolderName}.wasm", "-g"], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["csharp"], + "preLaunchTask": "Cargo: Build WASM" + } + ] +} diff --git a/templates/csharp/.vscode/settings.json b/templates/csharp/.vscode/settings.json new file mode 100644 index 0000000..630bfe8 --- /dev/null +++ b/templates/csharp/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/**": true, + "**/.hg/store/**": true, + "**/target/**": true + } +} diff --git a/templates/csharp/.vscode/tasks.json b/templates/csharp/.vscode/tasks.json new file mode 100644 index 0000000..c8060d5 --- /dev/null +++ b/templates/csharp/.vscode/tasks.json @@ -0,0 +1,50 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/WasmProject.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "run", + "command": "dotnet", + "type": "process", + "args": [ + "build" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/WasmProject.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/WasmProject.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/templates/csharp/HIPPOFACTS b/templates/csharp/HIPPOFACTS new file mode 100644 index 0000000..2b926d2 --- /dev/null +++ b/templates/csharp/HIPPOFACTS @@ -0,0 +1,8 @@ +[bindle] +name = "<%= bindleId %>" +version = "0.1.0" +authors = ["<%= authorName %>"] + +[[handler]] +route = "/" +name = "bin/Debug/net7.0/WasmProject.wasm" diff --git a/templates/csharp/LICENSE b/templates/csharp/LICENSE new file mode 100644 index 0000000..6dcceec --- /dev/null +++ b/templates/csharp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 <%= authorName %> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/templates/csharp/Program.cs b/templates/csharp/Program.cs new file mode 100644 index 0000000..b3253c7 --- /dev/null +++ b/templates/csharp/Program.cs @@ -0,0 +1,4 @@ +// See https://aka.ms/new-console-template for more information +<% if (wagi) { %> +Console.WriteLine("Content-Type: text/plain\n");<% } %> +Console.WriteLine("Hello, World!"); diff --git a/templates/csharp/README.md b/templates/csharp/README.md new file mode 100644 index 0000000..38ad7cf --- /dev/null +++ b/templates/csharp/README.md @@ -0,0 +1,16 @@ +# <%= moduleName %> + +A WASM hello-world written in C# and compiled into WASM via the [.NET WASI SDK](https://github.com/SteveSandersonMS/dotnet-wasi-sdk). + +You will need: +* .NET 7 - Preview 4 or newer: `https://dotnet.microsoft.com/en-us/download/dotnet/7.0` +* wasmtime: `curl https://wasmtime.dev/install.sh -sSf | bash` + +To build: +* VS Code: `Run Task > build` +* Command line: `dotnet build` + +To run: +* VS Code: `Run Task > run` +* Command line: `dotnet run` +* Command line: `wasmtime bin/Debug/net7.0/WasmProject.wasm` diff --git a/templates/csharp/WasmProject.csproj b/templates/csharp/WasmProject.csproj new file mode 100644 index 0000000..7b33637 --- /dev/null +++ b/templates/csharp/WasmProject.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/ts/generators/app/index.ts b/ts/generators/app/index.ts index dc6033c..f8dce8d 100644 --- a/ts/generators/app/index.ts +++ b/ts/generators/app/index.ts @@ -14,6 +14,7 @@ import { assemblyScript } from './languages/assembly-script'; import { failed } from './utils/errorable'; import { swift } from './languages/swift'; import { tinygo } from './languages/tinygo'; +import { csharp } from './languages/csharp'; import { FMT_CHALK, FMT_MARKDOWN } from './formatter'; import { ask, Moarable } from './question-sequence'; @@ -70,7 +71,8 @@ module.exports = class extends Generator { 'C', 'Rust', 'Swift', - 'TinyGo' + 'TinyGo', + 'C#' ], default: 'Rust' }, @@ -230,6 +232,8 @@ function languageProvider(language: string): Language { return swift; case 'TinyGo': return tinygo; + case 'C#': + return csharp; default: throw new Error("You didn't choose a language"); } diff --git a/ts/generators/app/languages/csharp.ts b/ts/generators/app/languages/csharp.ts new file mode 100644 index 0000000..42d632d --- /dev/null +++ b/ts/generators/app/languages/csharp.ts @@ -0,0 +1,49 @@ +import { Formatter } from '../formatter'; +import { Errorable } from '../utils/errorable'; +import { Language } from './language'; + +export const csharp: Language = { + instructions(fmt: Formatter): ReadonlyArray { + return [ + "You'll need the following to build and run this project locally:", + "* .NET 7 - Preview 4 or newer: `https://dotnet.microsoft.com/en-us/download/dotnet/7.0`", + `* wasmtime: ${fmt.cmd('curl https://wasmtime.dev/install.sh -sSf | bash')}`, + '', + `Build using VS Code ${fmt.instr('build')} task or ${fmt.cmd('dotnet build')}.`, + `Run using VS Code ${fmt.instr('run')} task or ${fmt.cmd('dotnet run')}.`, + ]; + }, + + templateFolder(): string { + return 'csharp'; + }, + + templateFiles(): string[] { + return [ + '.gitignore.removeext', + 'HIPPOFACTS', + 'LICENSE', + 'README.md', + '.vscode/extensions.json', + '.vscode/launch.json', + '.vscode/settings.json', + '.vscode/tasks.json', + 'Program.cs', + 'WasmProject.csproj' + ]; + }, + + async offerToInstallTools(): Promise { + return undefined; + }, + + async installTools(_projectDir: string): Promise> { + return { succeeded: true, result: null }; + }, + + augment(answers: any): any { + return answers; + } +} + + From 1d26118def9c21351f8af95600948d3cd4bd6afa Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Fri, 15 Jul 2022 07:45:27 -0700 Subject: [PATCH 2/4] Support for renaming project name to module name --- .../csharp/.github/workflows/release.azurecr.yml | 8 ++++---- .../csharp/.github/workflows/release.nopublish.yml | 4 ++-- templates/csharp/.github/workflows/release.yml | 6 +++--- templates/csharp/.vscode/tasks.json | 6 +++--- templates/csharp/HIPPOFACTS | 2 +- templates/csharp/README.md | 2 +- .../csharp/{WasmProject.csproj => csharp-wasm.csproj} | 0 ts/generators/app/index.ts | 11 ++++++++--- ts/generators/app/languages/csharp.ts | 2 +- 9 files changed, 23 insertions(+), 18 deletions(-) rename templates/csharp/{WasmProject.csproj => csharp-wasm.csproj} (100%) diff --git a/templates/csharp/.github/workflows/release.azurecr.yml b/templates/csharp/.github/workflows/release.azurecr.yml index 2f27c6b..e62220f 100644 --- a/templates/csharp/.github/workflows/release.azurecr.yml +++ b/templates/csharp/.github/workflows/release.azurecr.yml @@ -38,8 +38,8 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v1 with: - name: WasmProject.wasm - path: bin/Release/net7.0/WasmProject.wasm + name: <%= moduleName %>.wasm + path: bin/Release/net7.0/<%= moduleName %>.wasm publish: name: Push released module to OCI registry @@ -63,11 +63,11 @@ jobs: - name: Download release assets uses: actions/download-artifact@v1 with: - name: WasmProject.wasm + name: <%= moduleName %>.wasm path: ./assets - name: Log into registry run: az acr login -n ${ACR_NAME} -u ${{ secrets.ACR_SP_ID }} -p ${{ secrets.ACR_SP_PASSWORD }} - name: Publish to registry - run: ./tools/wasm-to-oci push ./assets/WasmProject.wasm ${ACR_NAME}.azurecr.io/<%= moduleName %>:${RELEASE_VERSION} + run: ./tools/wasm-to-oci push ./assets/<%= moduleName %>.wasm ${ACR_NAME}.azurecr.io/<%= moduleName %>:${RELEASE_VERSION} diff --git a/templates/csharp/.github/workflows/release.nopublish.yml b/templates/csharp/.github/workflows/release.nopublish.yml index 3258596..7423508 100644 --- a/templates/csharp/.github/workflows/release.nopublish.yml +++ b/templates/csharp/.github/workflows/release.nopublish.yml @@ -35,5 +35,5 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v1 with: - name: WasmProject.wasm - path: bin/Release/net7.0/WasmProject.wasm + name: <%= moduleName %>.wasm + path: bin/Release/net7.0/<%= moduleName %>.wasm diff --git a/templates/csharp/.github/workflows/release.yml b/templates/csharp/.github/workflows/release.yml index b82c35d..4156957 100644 --- a/templates/csharp/.github/workflows/release.yml +++ b/templates/csharp/.github/workflows/release.yml @@ -35,8 +35,8 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v1 with: - name: WasmProject.wasm - path: bin/Release/net7.0/WasmProject.wasm + name: <%= moduleName %>.wasm + path: bin/Release/net7.0/<%= moduleName %>.wasm publish: name: Push released module to OCI registry @@ -60,7 +60,7 @@ jobs: - name: Download release assets uses: actions/download-artifact@v1 with: - name: WasmProject.wasm + name: <%= moduleName %>.wasm path: ./assets # TODO: FILL IN THE << ... >> bit below diff --git a/templates/csharp/.vscode/tasks.json b/templates/csharp/.vscode/tasks.json index c8060d5..5f098cc 100644 --- a/templates/csharp/.vscode/tasks.json +++ b/templates/csharp/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/WasmProject.csproj", + "${workspaceFolder}/<%= moduleName %>.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -28,7 +28,7 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/WasmProject.csproj", + "${workspaceFolder}/<%= moduleName %>.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -42,7 +42,7 @@ "watch", "run", "--project", - "${workspaceFolder}/WasmProject.csproj" + "${workspaceFolder}/<%= moduleName %>.csproj" ], "problemMatcher": "$msCompile" } diff --git a/templates/csharp/HIPPOFACTS b/templates/csharp/HIPPOFACTS index 2b926d2..3326254 100644 --- a/templates/csharp/HIPPOFACTS +++ b/templates/csharp/HIPPOFACTS @@ -5,4 +5,4 @@ authors = ["<%= authorName %>"] [[handler]] route = "/" -name = "bin/Debug/net7.0/WasmProject.wasm" +name = "bin/Debug/net7.0/<%= moduleName %>.wasm" diff --git a/templates/csharp/README.md b/templates/csharp/README.md index 38ad7cf..5d44235 100644 --- a/templates/csharp/README.md +++ b/templates/csharp/README.md @@ -13,4 +13,4 @@ To build: To run: * VS Code: `Run Task > run` * Command line: `dotnet run` -* Command line: `wasmtime bin/Debug/net7.0/WasmProject.wasm` +* Command line: `wasmtime bin/Debug/net7.0/<%= moduleName %>.wasm` diff --git a/templates/csharp/WasmProject.csproj b/templates/csharp/csharp-wasm.csproj similarity index 100% rename from templates/csharp/WasmProject.csproj rename to templates/csharp/csharp-wasm.csproj diff --git a/ts/generators/app/index.ts b/ts/generators/app/index.ts index f8dce8d..52f7c49 100644 --- a/ts/generators/app/index.ts +++ b/ts/generators/app/index.ts @@ -113,7 +113,7 @@ module.exports = class extends Generator { for (const path of language.templateFiles()) { this.fs.copyTpl( this.templatePath(fspath.join(templateFolder, path)), - removeSuppressionExtension(this.destinationPath(path)), + cleanupTemplateFiles(this.destinationPath(path), this.answers), templateValues ); } @@ -128,7 +128,7 @@ module.exports = class extends Generator { for (const path of registry.languageFiles()) { this.fs.copyTpl( this.templatePath(fspath.join(templateFolder, path)), - removeSuppressionExtension(this.destinationPath(path)), + cleanupTemplateFiles(this.destinationPath(path), this.answers), templateValues ); } @@ -260,10 +260,15 @@ async function languageSpecificPrompts(answers: any): Promise<(Generator.Questio return installationPrompts; } -function removeSuppressionExtension(path: string): string { +function cleanupTemplateFiles(path: string, answers: any): string { if (fspath.extname(path) === '.removeext') { return path.substring(0, path.length - '.removeext'.length); } + // Perform a project filename change for c# projects. + // This will change the templated .csproj file name to the user-provided module name. + if ((path.includes('csharp-wasm'))) { + return path.replace('csharp-wasm', answers.moduleName); + } return path; } diff --git a/ts/generators/app/languages/csharp.ts b/ts/generators/app/languages/csharp.ts index 42d632d..abac08b 100644 --- a/ts/generators/app/languages/csharp.ts +++ b/ts/generators/app/languages/csharp.ts @@ -29,7 +29,7 @@ export const csharp: Language = { '.vscode/settings.json', '.vscode/tasks.json', 'Program.cs', - 'WasmProject.csproj' + 'csharp-wasm.csproj' ]; }, From 9c59c0a69680a16a2f7d472fc5338f6cdc999138 Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Wed, 20 Jul 2022 15:36:04 -0700 Subject: [PATCH 3/4] Adding .devcontainer to csharp template --- templates/csharp/.devcontainer/Dockerfile | 38 ++ .../csharp/.devcontainer/devcontainer.json | 60 +++ .../library-scripts/common-debian.sh | 455 ++++++++++++++++++ .../.devcontainer/library-scripts/meta.env | 1 + .../library-scripts/node-debian.sh | 169 +++++++ .../.devcontainer/library-scripts/wasmtime.sh | 1 + ts/generators/app/languages/csharp.ts | 6 + 7 files changed, 730 insertions(+) create mode 100644 templates/csharp/.devcontainer/Dockerfile create mode 100644 templates/csharp/.devcontainer/devcontainer.json create mode 100644 templates/csharp/.devcontainer/library-scripts/common-debian.sh create mode 100644 templates/csharp/.devcontainer/library-scripts/meta.env create mode 100644 templates/csharp/.devcontainer/library-scripts/node-debian.sh create mode 100644 templates/csharp/.devcontainer/library-scripts/wasmtime.sh diff --git a/templates/csharp/.devcontainer/Dockerfile b/templates/csharp/.devcontainer/Dockerfile new file mode 100644 index 0000000..adb8024 --- /dev/null +++ b/templates/csharp/.devcontainer/Dockerfile @@ -0,0 +1,38 @@ +# [Choice] .NET version: 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal, 7.0-jammy +ARG VARIANT=7.0-jammy +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:${VARIANT} + +# Copy library scripts to execute +COPY library-scripts/*.sh library-scripts/*.env /tmp/library-scripts/ + +# [Option] Install zsh +ARG INSTALL_ZSH="true" +# [Option] Upgrade OS packages to their latest versions +ARG UPGRADE_PACKAGES="true" +# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# [Choice] Node.js version: none, lts/*, 18, 16, 14 +ARG NODE_VERSION="none" +ENV NVM_DIR=/usr/local/share/nvm +ENV NVM_SYMLINK_CURRENT=true \ + PATH=${NVM_DIR}/current/bin:${PATH} +RUN bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# Install wasmtime as vscode user +RUN su vscode -c 'bash /tmp/library-scripts/wasmtime.sh' + +# Remove library scripts for final image +RUN rm -rf /tmp/library-scripts + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/templates/csharp/.devcontainer/devcontainer.json b/templates/csharp/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a712c37 --- /dev/null +++ b/templates/csharp/.devcontainer/devcontainer.json @@ -0,0 +1,60 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/dotnet +{ + "name": "yo-wasm (c#)", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update 'VARIANT' to pick a .NET Core version: 3.1, 6.0 + // Append -bullseye or -focal or -jammy to pin to an OS version. + "VARIANT": "7.0-jammy", + // Options + "NODE_VERSION": "lts/*" + } + }, + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-dotnettools.csharp", + "ms-vsliveshare.vsliveshare", + "dtsvet.vscode-wasm" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + + // [Optional] To reuse of your local HTTPS dev cert: + // + // 1. Export it locally using this command: + // * Windows PowerShell: + // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" + // * macOS/Linux terminal: + // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" + // + // 2. Uncomment these 'remoteEnv' lines: + // "remoteEnv": { + // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere", + // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx", + // }, + // + // 3. Do one of the following depending on your scenario: + // * When using GitHub Codespaces and/or Remote - Containers: + // 1. Start the container + // 2. Drag ~/.aspnet/https/aspnetapp.pfx into the root of the file explorer + // 3. Open a terminal in VS Code and run "mkdir -p /home/vscode/.aspnet/https && mv aspnetapp.pfx /home/vscode/.aspnet/https" + // + // * If only using Remote - Containers with a local container, uncomment this line instead: + // "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" ], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "dotnet restore", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/templates/csharp/.devcontainer/library-scripts/common-debian.sh b/templates/csharp/.devcontainer/library-scripts/common-debian.sh new file mode 100644 index 0000000..5434cc7 --- /dev/null +++ b/templates/csharp/.devcontainer/library-scripts/common-debian.sh @@ -0,0 +1,455 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages] + +set -e + +INSTALL_ZSH=${1:-"true"} +USERNAME=${2:-"automatic"} +USER_UID=${3:-"automatic"} +USER_GID=${4:-"automatic"} +UPGRADE_PACKAGES=${5:-"true"} +INSTALL_OH_MYS=${6:-"true"} +ADD_NON_FREE_PACKAGES=${7:-"false"} +SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)" +MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# If in automatic mode, determine if a user already exists, if not use vscode +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=vscode + fi +elif [ "${USERNAME}" = "none" ]; then + USERNAME=root + USER_UID=0 + USER_GID=0 +fi + +# Load markers to see which steps have already run +if [ -f "${MARKER_FILE}" ]; then + echo "Marker file found:" + cat "${MARKER_FILE}" + source "${MARKER_FILE}" +fi + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Function to call apt-get if needed +apt_get_update_if_needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + + package_list="apt-utils \ + openssh-client \ + gnupg2 \ + dirmngr \ + iproute2 \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + zip \ + xz-utils \ + nano \ + vim-tiny \ + less \ + jq \ + lsb-release \ + apt-transport-https \ + dialog \ + libc6 \ + libgcc1 \ + libkrb5-3 \ + libgssapi-krb5-2 \ + libicu[0-9][0-9] \ + liblttng-ust[0-9] \ + libstdc++6 \ + zlib1g \ + locales \ + sudo \ + ncdu \ + man-db \ + strace \ + manpages \ + manpages-dev \ + init-system-helpers" + + # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian + if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then + # Bring in variables from /etc/os-release like VERSION_CODENAME + . /etc/os-release + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + echo "Running apt-get update..." + apt-get update + package_list="${package_list} manpages-posix manpages-posix-dev" + else + apt_get_update_if_needed + fi + + # Install libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + package_list="${package_list} libssl1.1" + fi + + # Install appropriate version of libssl1.0.x if available + libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + package_list="${package_list} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04, 16.04, earlier + package_list="${package_list} libssl1.0.0" + fi + fi + + echo "Packages to verify are installed: ${package_list}" + apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + apt-get -y install --no-install-recommends git + fi + + PACKAGES_ALREADY_INSTALLED="true" +fi + +# Get to latest versions of all packages +if [ "${UPGRADE_PACKAGES}" = "true" ]; then + apt_get_update_if_needed + apt-get -y upgrade --no-install-recommends + apt-get autoremove -y +fi + +# Ensure at least the en_US.UTF-8 UTF-8 locale is available. +# Common need for both applications and things like the agnoster ZSH theme. +if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen + LOCALE_ALREADY_SET="true" +fi + +# Create or update a non-root user to match UID/GID. +group_name="${USERNAME}" +if id -u ${USERNAME} > /dev/null 2>&1; then + # User exists, update if needed + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then + group_name="$(id -gn $USERNAME)" + groupmod --gid $USER_GID ${group_name} + usermod --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + usermod --uid $USER_UID $USERNAME + fi +else + # Create user + if [ "${USER_GID}" = "automatic" ]; then + groupadd $USERNAME + else + groupadd --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" = "automatic" ]; then + useradd -s /bin/bash --gid $USERNAME -m $USERNAME + else + useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME + fi +fi + +# Add add sudo support for non-root user +if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then + echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME + chmod 0440 /etc/sudoers.d/$USERNAME + EXISTING_NON_ROOT_USER="${USERNAME}" +fi + +# ** Shell customization section ** +if [ "${USERNAME}" = "root" ]; then + user_rc_path="/root" +else + user_rc_path="/home/${USERNAME}" +fi + +# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then + cp /etc/skel/.bashrc "${user_rc_path}/.bashrc" +fi + +# Restore user .profile defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then + cp /etc/skel/.profile "${user_rc_path}/.profile" +fi + +# .bashrc/.zshrc snippet +rc_snippet="$(cat << 'EOF' + +if [ -z "${USER}" ]; then export USER=$(whoami); fi +if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi + +# Display optional first run image specific notice if configured and terminal is interactive +if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then + if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then + cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" + elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then + cat "/workspaces/.codespaces/shared/first-run-notice.txt" + fi + mkdir -p "$HOME/.config/vscode-dev-containers" + # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it + ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) +fi + +# Set the default git editor if not already set +if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then + if [ "${TERM_PROGRAM}" = "vscode" ]; then + if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then + export GIT_EDITOR="code-insiders --wait" + else + export GIT_EDITOR="code --wait" + fi + fi +fi + +EOF +)" + +# code shim, it fallbacks to code-insiders if code is not available +cat << 'EOF' > /usr/local/bin/code +#!/bin/sh + +get_in_path_except_current() { + which -a "$1" | grep -A1 "$0" | grep -v "$0" +} + +code="$(get_in_path_except_current code)" + +if [ -n "$code" ]; then + exec "$code" "$@" +elif [ "$(command -v code-insiders)" ]; then + exec code-insiders "$@" +else + echo "code or code-insiders is not installed" >&2 + exit 127 +fi +EOF +chmod +x /usr/local/bin/code + +# systemctl shim - tells people to use 'service' if systemd is not running +cat << 'EOF' > /usr/local/bin/systemctl +#!/bin/sh +set -e +if [ -d "/run/systemd/system" ]; then + exec /bin/systemctl/systemctl "$@" +else + echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all' +fi +EOF +chmod +x /usr/local/bin/systemctl + +# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme +codespaces_bash="$(cat \ +<<'EOF' + +# Codespaces bash prompt theme +__bash_prompt() { + local userpart='`export XIT=$? \ + && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ + && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' + local gitbranch='`\ + if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ + export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ + if [ "${BRANCH}" != "" ]; then \ + echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ + && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ + echo -n " \[\033[1;33m\]✗"; \ + fi \ + && echo -n "\[\033[0;36m\]) "; \ + fi; \ + fi`' + local lightblue='\[\033[1;34m\]' + local removecolor='\[\033[0m\]' + PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " + unset -f __bash_prompt +} +__bash_prompt + +EOF +)" + +codespaces_zsh="$(cat \ +<<'EOF' +# Codespaces zsh prompt theme +__zsh_prompt() { + local prompt_username + if [ ! -z "${GITHUB_USER}" ]; then + prompt_username="@${GITHUB_USER}" + else + prompt_username="%n" + fi + PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow + PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd + PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status + PROMPT+='%{$fg[white]%}$ %{$reset_color%}' + unset -f __zsh_prompt +} +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " +ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" +__zsh_prompt + +EOF +)" + +# Add RC snippet and custom bash prompt +if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/bash.bashrc + echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + echo "${codespaces_bash}" >> "/root/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc" + fi + chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Optionally install and configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + if ! type zsh > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get install -y zsh + fi + if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/zsh/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. + # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. + oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + user_rc_file="${user_rc_path}/.zshrc" + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} + + mkdir -p ${oh_my_install_dir}/custom/themes + echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + chown -R ${USERNAME}:${group_name} "${user_rc_path}" + fi + fi +fi + +# Persist image metadata info, script if meta.env found in same directory +meta_info_script="$(cat << 'EOF' +#!/bin/sh +. /usr/local/etc/vscode-dev-containers/meta.env + +# Minimal output +if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then + echo "${VERSION}" + exit 0 +elif [ "$1" = "release" ]; then + echo "${GIT_REPOSITORY_RELEASE}" + exit 0 +elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then + echo "${CONTENTS_URL}" + exit 0 +fi + +#Full output +echo +echo "Development container image information" +echo +if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi +if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi +if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi +if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi +if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi +if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi +if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi +echo +EOF +)" +if [ -f "${SCRIPT_DIR}/meta.env" ]; then + mkdir -p /usr/local/etc/vscode-dev-containers/ + cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env + echo "${meta_info_script}" > /usr/local/bin/devcontainer-info + chmod +x /usr/local/bin/devcontainer-info +fi + +# Write marker file +mkdir -p "$(dirname "${MARKER_FILE}")" +echo -e "\ + PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ + LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ + EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ + RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ + ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" + +echo "Done!" \ No newline at end of file diff --git a/templates/csharp/.devcontainer/library-scripts/meta.env b/templates/csharp/.devcontainer/library-scripts/meta.env new file mode 100644 index 0000000..9e54336 --- /dev/null +++ b/templates/csharp/.devcontainer/library-scripts/meta.env @@ -0,0 +1 @@ +VERSION='dev' diff --git a/templates/csharp/.devcontainer/library-scripts/node-debian.sh b/templates/csharp/.devcontainer/library-scripts/node-debian.sh new file mode 100644 index 0000000..5394a8f --- /dev/null +++ b/templates/csharp/.devcontainer/library-scripts/node-debian.sh @@ -0,0 +1,169 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/node.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] [Update rc files flag] [install node-gyp deps] + +export NVM_DIR=${1:-"/usr/local/share/nvm"} +export NODE_VERSION=${2:-"lts"} +USERNAME=${3:-"automatic"} +UPDATE_RC=${4:-"true"} +INSTALL_TOOLS_FOR_NODE_GYP="${5:-true}" +export NVM_VERSION="0.38.0" + +set -e + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi + +updaterc() { + if [ "${UPDATE_RC}" = "true" ]; then + echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." + if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then + echo -e "$1" >> /etc/bash.bashrc + fi + if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then + echo -e "$1" >> /etc/zsh/zshrc + fi + fi +} + +# Function to run apt-get if needed +apt_get_update_if_needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get -y install --no-install-recommends "$@" + fi +} + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Install dependencies +check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr + +# Install yarn +if type yarn > /dev/null 2>&1; then + echo "Yarn already installed." +else + # Import key safely (new method rather than deprecated apt-key approach) and install + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list + apt-get update + apt-get -y install --no-install-recommends yarn +fi + +# Adjust node version if required +if [ "${NODE_VERSION}" = "none" ]; then + export NODE_VERSION= +elif [ "${NODE_VERSION}" = "lts" ]; then + export NODE_VERSION="lts/*" +fi + +# Create a symlink to the installed version for use in Dockerfile PATH statements +export NVM_SYMLINK_CURRENT=true + +# Install the specified node version if NVM directory already exists, then exit +if [ -d "${NVM_DIR}" ]; then + echo "NVM already installed." + if [ "${NODE_VERSION}" != "" ]; then + su ${USERNAME} -c ". $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache" + fi + exit 0 +fi + +# Create nvm group, nvm dir, and set sticky bit +if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then + groupadd -r nvm +fi +umask 0002 +usermod -a -G nvm ${USERNAME} +mkdir -p ${NVM_DIR} +chown :nvm ${NVM_DIR} +chmod g+s ${NVM_DIR} +su ${USERNAME} -c "$(cat << EOF + set -e + umask 0002 + # Do not update profile - we'll do this manually + export PROFILE=/dev/null + curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash + source ${NVM_DIR}/nvm.sh + if [ "${NODE_VERSION}" != "" ]; then + nvm alias default ${NODE_VERSION} + fi + nvm clear-cache +EOF +)" 2>&1 +# Update rc files +if [ "${UPDATE_RC}" = "true" ]; then +updaterc "$(cat < /dev/null 2>&1; then + to_install="${to_install} make" + fi + if ! type gcc > /dev/null 2>&1; then + to_install="${to_install} gcc" + fi + if ! type g++ > /dev/null 2>&1; then + to_install="${to_install} g++" + fi + if ! type python3 > /dev/null 2>&1; then + to_install="${to_install} python3-minimal" + fi + if [ ! -z "${to_install}" ]; then + apt_get_update_if_needed + apt-get -y install ${to_install} + fi +fi + +echo "Done!" \ No newline at end of file diff --git a/templates/csharp/.devcontainer/library-scripts/wasmtime.sh b/templates/csharp/.devcontainer/library-scripts/wasmtime.sh new file mode 100644 index 0000000..3d1bf77 --- /dev/null +++ b/templates/csharp/.devcontainer/library-scripts/wasmtime.sh @@ -0,0 +1 @@ +curl https://wasmtime.dev/install.sh -sSf | bash diff --git a/ts/generators/app/languages/csharp.ts b/ts/generators/app/languages/csharp.ts index abac08b..f278f63 100644 --- a/ts/generators/app/languages/csharp.ts +++ b/ts/generators/app/languages/csharp.ts @@ -24,6 +24,12 @@ export const csharp: Language = { 'HIPPOFACTS', 'LICENSE', 'README.md', + '.devcontainer/library-scripts/common-debian.sh', + '.devcontainer/library-scripts/meta.env', + '.devcontainer/library-scripts/node-debian.sh', + '.devcontainer/library-scripts/wasmtime.sh', + '.devcontainer/devcontainer.json', + '.devcontainer/Dockerfile', '.vscode/extensions.json', '.vscode/launch.json', '.vscode/settings.json', From 5323436176b7ff763bc310578148705cd60efdec Mon Sep 17 00:00:00 2001 From: Paul Yu Date: Thu, 21 Jul 2022 17:23:49 -0700 Subject: [PATCH 4/4] Update instructions for csharp --- ts/generators/app/languages/csharp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/generators/app/languages/csharp.ts b/ts/generators/app/languages/csharp.ts index f278f63..b6306eb 100644 --- a/ts/generators/app/languages/csharp.ts +++ b/ts/generators/app/languages/csharp.ts @@ -9,6 +9,8 @@ export const csharp: Language = { "* .NET 7 - Preview 4 or newer: `https://dotnet.microsoft.com/en-us/download/dotnet/7.0`", `* wasmtime: ${fmt.cmd('curl https://wasmtime.dev/install.sh -sSf | bash')}`, '', + "Optionally, you can run this project in the provided Dev Container which already includes .NET 7 and Wasmtime.", + '', `Build using VS Code ${fmt.instr('build')} task or ${fmt.cmd('dotnet build')}.`, `Run using VS Code ${fmt.instr('run')} task or ${fmt.cmd('dotnet run')}.`, ];