مطالب
PowerShell 7.x - قسمت سیزدهم - ساخت یک Static Site Generator ساده توسط PowerShell و GitHub Actions
در این مطلب میخواهیم یک مثال دیگر از PowerShell را به همراه GitHub Actions را بررسی کنیم. هدف ایجاد یک Static Site Generator و در نهایت پابلیش خروجی استاتیک بر روی GitHub Pages است. روالی که در ادامه بررسی میکنیم صرفاً یک مثال از ترکیب این تکنولوژی‌ها است و قاعدتاً روش‌های ساده‌تر و سرراست‌تری نیز برای اینکار وجود دارد. به عنوان مثال میتوانید از Jekyll که یک SSG مبتنی بر Ruby است نیز استفاده کنید که GitHub Pages، به صورت پیش‌فرض از آن پشتیبانی میکند. در اینحالت به محض پوش کردن سایت بر روی ریپوزیتوری (با فرض اینکه این امکان را فعال کرده باشید) به صورت خودکار سایت بیلد شده و خروجی بر روی یک برنچ دیگر قرار خواهد گرفت و در نهایت برنچ بیلد شده توسط GitHub Pages میزبانی خواهد شد (البته امکان تغییر برنچ پیش‌فرض را نیز دارید). اما اگر بخواهیم کل فرآیند بیلد را به صورت سفارشی انجام دهیم، میتوانیم از GitHub Actions استفاده کنیم؛ یعنی مشابه کاری که Jekyll انجام میدهد. به محض پوش کردن محتوا، یک اسکریپت PowerShell برای اینکار فراخوانی شود و خروجی نهایی بر روی یک برنچ دیگر منتشر شود. خروجی نهایی این چنین قالبی خواهد داشت:

نکته: در اینجا از فونت آقای راستی‌کردار استفاده شده است؛ با آرزوی بهبودی و سلامتی ایشان.

ساختار پروژه
ساختاری که برای پروژه در نظر گرفته‌ام به صورت زیر است:
├── _layout
│   ├── _footer.html
│   ├── _header.html
│   ├── _nav.html
│   └── main.html
├── build
├── img
├── posts
└── set-posts.ps1
  • دایرکتوری layout_: درون این دایرکتوری، ساختار اصلی بلاگ را قرار داده‌ایم. در ادامه محتویات هر فایل را مشاهده خواهید کرد: 
<!--main.html-->
<!DOCTYPE html>
<html dir="rtl">

{{header}}

<body>
    {{nav}}
    <main>
        {{content}}
    </main>
    {{footer}}
</body>



<!--_header.html-->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/samim-font@v4.0.5/dist/font-face.css" rel="stylesheet"
        type="text/css" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
    <style>
        * {
        }
    </style>
</head>

<!--_nav.html-->
<header>
    <nav>
        <div>
            <div>
                <a href="#">بلاگ من</a>
            </div>
            <div>
                <ul>
                    {{nav}}
                </ul>
            </div>
        </div>
    </nav>
</header>


<!--_footer.html-->
<footer>
    <div>
        <div>
            <p>
                تمامی حقوق محفوظ است
            </p>
        </div>
    </div>
</footer>
  • دایرکتوری build: درون این دایرکتوری، خروجی‌های HTML که قرار است توسط اسکریپت PowerShell جنریت شوند، قرار خواهند گرفت. این پوشه در واقع قرار است توسط GitHub Pages میزبانی شود.
  • دایرکتوری img: درون این دایرکتوری، تصاویر مربوط به هر بلاگ‌پست را قرار خواهیم داد.
  • دایرکتوری posts: درون این دایرکتوری، مطالب‌مان را با فرمت Markdown، قرار خواهیم داد. به عنوان مثال در ادامه یک نمونه از آن را مشاهده خواهید کرد (در کد زیر از Front Matter برای اضافه کردن یکسری متادیتای موردنیاز که حین بیلد شدن ضروری هستند استفاده شده‌است) 
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

# اولین پست من

## اولین پست من

### اولین پست من

#### اولین پست من

##### اولین پست من

###### اولین پست من

لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می‌باشد. کتابهای زیادی در شصت و سه درصد گذشته، حال و آینده شناخت فراوان جامعه و متخصصان را می‌طلبد تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی و فرهنگ پیشرو در زبان فارسی ایجاد کرد. در این صورت می‌توان امید داشت که تمام و دشواری موجود در ارائه راهکارها و شرایط سخت تایپ به پایان رسد وزمان مورد نیاز شامل حروفچینی دستاوردهای اصلی و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد.

<img src="/img/graphql.jpg"/>
همانطور که مشاهده میکنید، مسیر تصویر استفاده شده در بلاگ‌پست، از دایرکتوری img خوانده شده‌است.
  • فایل set_post.ps1: موتور اصلی جنریت کردن صفحات HTML این فایل میباشد. در ادامه محتویات آن را مشاهده خواهید کرد. سپس هر کدام از توابع استفاده شده را یکی‌یکی توضیح خواهیم داد: 
Function Get-Layouts {
    $headerLayout = Get-Content -Path ./_layout/_header.html -Raw
    $homeLayout = Get-Content -Path ./_layout/main.html -Raw
    $footerLayout = Get-Content -Path ./_layout/_footer.html -Raw

    Return @{
        Header = $headerLayout
        Home   = $homeLayout
        Footer = $footerLayout
    }
}

Function Get-PostFrontMatter($postContent) {
    $frontMatter = [regex]::Match($postContent, "(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)")
    Return $frontMatter
}

Function Set-Headings($postHtml) {
    Return $postHtml -Replace '<h(\d) id="(.*)">', {
        $level = $_.Groups[1].Value
        $id = $_.Groups[2].Value
        $class = Switch ($level) {
            '1' { 'text-4xl font-bold mb-2' }
            '2' { 'text-3xl font-bold mb-2' }
            '3' { 'text-2xl font-bold mb-2' }
            '4' { 'text-xl font-bold mb-2' }
            '5' { 'text-lg font-bold mb-2' }
            '6' { 'text-base font-bold mb-2' }
        }
        "<h$level class='$class' id='$id'>"
    }
}

Function ConvertTo-Slug {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$String
    )
    process {
        $slug = $String -replace '[^\w\s-]', '' # remove non-word characters except hyphens
        $slug = $slug -replace '\s+', '-' # replace whitespace with a single hyphen
        $slug = $slug -replace '^-+|-+$', '' # remove leading/trailing hyphens
        $slug = $slug.ToLower() # convert to lowercase
        Write-Output $slug
    }
}

Function Get-Posts {
    $markdownPosts = Get-ChildItem -Path ./posts -Filter *.md
    $posts = @()
    Foreach ($post in $markdownPosts) {
        $postContent = Get-Content -Path $post.FullName -Raw
        $frontMatter = Get-PostFrontMatter $postContent
        $frontMatterObject = $frontMatter | ConvertFrom-Yaml

        $slug = $frontMatterObject.slug ?? (ConvertTo-Slug "$($frontMatterObject.date)-$($frontMatterObject.title)")
        $body = $postContent.Replace($frontMatter.Value, "") | ConvertFrom-Markdown
        $postHtml = $layouts.Home -replace '{{header}}', $layouts.Header `
            -replace '{{title}}', $frontMatterObject.title `
            -replace '{{nav}}', (Set-Navs) `
            -replace '{{content}}', $body.Html `
            -replace '{{footer}}', $layouts.Footer

        $postHtml = Set-Headings $postHtml
        $postHtml | Out-File -FilePath ./build/$slug.html

        $posts += @{
            title   = $frontMatterObject.title
            slug    = $slug
            excerpt = $frontMatterObject.excerpt
            date    = $frontMatterObject.date
            author  = $frontMatterObject.author
            body    = $body.Html
        }
    }
    Return $posts
}

Function Set-Archive {
    $posts = Get-Posts
    $archive = @()
    $archive = @"
        <ul>
            $($posts | ForEach-Object { "<li><a href='$($_.slug).html'>$($_.title)</a></li>" })
        </ul>
"@
    Return $archive -join "`r`n"
}

Function Copy-ToBuild {
    $layouts = Get-Layouts
    $latestPosts = Get-Posts | ForEach-Object { @"
    <div>
        <img src="https://via.placeholder.com/300x200" alt="$($_.title)">
        <h2>$($_.title)</h2>
        <p>$($_.excerpt)</p>
            <a href="$($_.slug).html">ادامه مطلب</a>
        </div>
"@
    }

    $homeLayout = $layouts.Home -replace '{{header}}', $layouts.Header `
        -replace '{{nav}}', (Set-Navs) `
        -replace '{{title}}', 'بلاگ من' `
        -replace '{{content}}', ('<div>' + $latestPosts + '</div>') `
        -replace '{{footer}}', $layouts.Footer

    $homeLayout | Out-File -FilePath ./build/index.html

    Copy-Item -Path ./img -Destination ./build -Recurse -Force

}

Function Set-Navs {
    $navs = @(
        @{
            title = "صفحه اصلی"
            url   = "/sample"
        },
        @{
            title = "درباره ما"
            url   = "/sample/about.html"
        },
        @{
            title = "تماس با ما"
            url   = "/sample/contact.html"
        }
    )
    $navLayout = Get-Content -Path ./_layout/_nav.html -Raw
    $navLayout -replace '{{nav}}', ($navs | ForEach-Object { "<li><a href=""$($_.url)""text-gray-700 hover:text-gray-800 m-2"">$($_.title)</a></li>" })
}


Copy-ToBuild
همانطور که در کد فوق مشاهده میکنید، تابع Copy-ToBuild فراخوانی شده است. درون این تابع، ابتدا لی‌اوت‌های موردنیاز برای تولید صفحات HTML را درون یک متغییر با نام layouts قرار داده‌ایم. درون لی‌اوت‌ها یکسری placeholder برای قرارگیری قسمت‌های مختلف سایت تعریف کرده‌ایم که قرار است توسط تابع عنوان شده، جایگزین شوند. در ادامه توسط تابع Get-Posts، تمامی مطالب درون دایرکتوری posts را واکشی کرده و برای هر کدام، صفحه‌ی HTML معادل آن را تولید کرده‌ایم. برای پارز کردن قسمت Front Matter هر بلاگ‌پست نیز از پکیج powershell-yaml استفاده شده‌است. درون تابع Get-Posts باید هر مطلب را همراه با لی‌اوت اصلی سایت، به HTML تبدیل کنیم. بنابراین یکسری عملیات string replacement درون تابع عنوان شده انجام گرفته است. در نهایت، مطالب همراه با لی‌اوت مناسبی درون دایرکتوری build ذخیره خواهند شد و سپس یک لیست از مطالب پارز شده به عنوان خروجی تابع برگردانده خواهد شد. در نهایت درون تابع Copy-ToBuild، یکسری card برای نمایش آخرین مطالب بلاگ تهیه شده، سپس خروجی نهایی درون فایل index.html همراه با قالب اصلی ذخیره خواهد شد.

ایجاد GitHub Actions Workflow
در ادامه برای ساختن workflow نهایی باید با کمک GitHub Actions، اسکریپت PowerShellی را که ساختیم، اجرا کنیم. این اسکریپت ابتدا پروژه را clone کرده، سپس وابستگی موردنیاز را نصب کرده و در نهایت اسکریپت را اجرا خواهد کرد: 
name: Deploy static content to Pages

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Install powershell-yaml module
        shell: pwsh
        run: |
          Set-PSRepository PSGallery -InstallationPolicy Trusted
          Install-Module powershell-yaml -ErrorAction Stop

      - name: Setup Pages
        uses: actions/configure-pages@v3

      - name: Build Static Site
        shell: pwsh
        run: |
          . ./set-posts.ps1

      - name: Upload Static Site Artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: build

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

نحوه انتشار یک مطلب جدید
  • درون دایرکتوری posts، مطلب موردنظر را به همراه Front Matter زیر ایجاد کرده و سپس محتویات مطلب را بعد از آن وارد کنید:
---
title: اولین پست من
slug: hello
date: 2023-04-26
author: سیروان عفیفی
tags: [tag1, tag2, tag3]
excerpt: این یک پست تستی است در مورد اینکه چطور میتوانیم از این قالب استفاده کنیم
---

content
  • تغییرات ایجاد شده را کامیت و سپس پوش کنید. به محض پوش کردن تغییرات، GitHub Actions پروسه بیلد را انجام خواهد داد و بلافاصله میتوانید تغییرات را مشاهده نمائید.
نکته: قابلیت GitHub Pages به صورت پیش‌فرض فعال نیست. برای فعال کردن آن ابتدا باید به قسمت تنظیمات ریپوزیتوری GitHubتان مراجعه کرده و سپس از تب Pages، فیلد Source را بر روی GitHub Actions قرار دهید:


بنابراین بعد از پوش کردن تغییرات workflowایی که ایجاد کردیم توسط Source شناسایی خواهد شد و سپس وب‌سایت از طریق آدرس https://<username>.github.io/<repository> در دسترس قرار خواهد گرفت:

کدهای این مطلب را میتوانید از اینجا دریافت کنید.

اشتراک‌ها
رهانش ASP.NET Core updates in .NET 8 Preview 4

.NET 8 Preview 4 is now available and includes many great new improvements to ASP.NET Core.


Here’s a summary of what’s new in this preview release:


Blazor

Streaming rendering with Blazor components

Handling form posts with Blazor SSR

Route to named elements in Blazor

Webcil packaging for Blazor WebAssembly apps

API authoring

Expanded support for form binding in minimal APIs

API project template includes .http file

Native AOT

Logging and exception handling in compile-time generated minimal APIs

ASP.NET Core top-level APIs annotated for trim warnings

Reduced app size with configurable HTTPS support

Worker Service template updates

Additional default services configured in the slim builder

API template JSON configuration changes

Support for JSON serialization of compiler-generated IAsyncEnumerable unspeakable types

Authentication and authorization

Identity API endpoints

Improved support for custom authorization policies with IAuthorizationRequirementData

ASP.NET Core metrics

For more details on the ASP.NET Core work planned for .NET 8 see the full ASP.NET Core roadmap for .NET 8 on GitHub. 

رهانش ASP.NET Core updates in .NET 8 Preview 4
مطالب
استفاده از GitHub Actions برای Build و توزیع خودکار پروژه‌های NET Core.
پیشتر مطلب « تولید و ارسال خودکار بسته‌های NuGet پروژه‌های NET Core. به کمک AppVeyor » را در این سایت مطالعه کرده‌اید. اخیرا GitHub نیز دقیقا همین امکانات یکپارچگی مداوم یا Continuous Integration را تحت عنوان GitHub Action، به مخازن کد خود اضافه کرده‌است. البته این قابلیت هنوز در مرحله‌ی بتا است و برای فعالسازی آن بر روی مخازن کد خود نیاز است در اینجا ثبت نام کنید. بعد از یکی دو روز صبر کردن، این برگه‌ی جدید، به مخازن کد شما اضافه خواهد شد:



فعالسازی GitHub Action مخصوص NET Core.

در ادامه قصد داریم از این قابلیت جدید، جهت Build خودکار پروژه‌های NET Core. و در آخر ارسال خودکار بسته‌های نیوگت متناظر آن‌ها به سایت nuget.org، استفاده کنیم. برای این منظور به برگه‌ی Actions مخزن کد خود مراجعه کنید (تصویر فوق). سپس در این صفحه، بر روی لینک Work flows for … more کلیک کنید:


تا امکان انتخاب گردش کاری متناظر با NET Core. ظاهر شود:


در اینجا بر روی دکمه‌ی «Set up this workflow» کلیک کنید تا صفحه‌ی ویرایشی این گردش کاری که با فرمت yml است، ظاهر شود.


 محتویات آن‌‌را برای نمونه می‌توانید به صورت زیر تغییر دهید:
name: .NET Core Build

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.100-preview9-014004        
    - name: Build DNTCaptcha.Core lib
      run: dotnet build ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
در این گردش کاری، ابتدا SDK با شماره 3.0.100-preview9-014004 به صورت خودکار نصب خواهد شد. سپس دستور dotnet build بر روی یک فایل csproj خاص، اجرا می‌گردد. اگر نتیجه‌ی این Build موفقیت آمیز بود، یک علامت چک‌مارک سبز رنگ ظاهر می‌شود و در غیر اینصورت یک ایمیل شکست عملیات را نیز دریافت خواهید کرد.
پس از تکمیل محتوای فایل yml در این مرحله، در کنار صفحه بر روی لینک start commit کلیک کنید، تا این فایل را به صورت خودکار در مسیر github\workflows\aspnetcore.yml ذخیره کند. بدیهی است تغییرات آن‌را در قسمت commits مخزن کد نیز می‌توانید مشاهده کنید.


این فایل on: [push] کار می‌کند. یعنی اگر تغییری را به مخزن کد اعمال کردید، همانند یک تریگر عمل کرده و عملیات Build را به صورت خودکار آغاز می‌کند.



اضافه کردن نماد گردش کاری GitHub به پروژه

هر گردش کاری تعریف شده را می‌توان با یک نماد یا badge در فایل readme.md پروژه نیز نمایش داد:


فرمول آن نیز به صورت زیر است:
https://github.com/<OWNER>/<REPOSITORY>/workflows/<WORKFLOW_NAME>/badge.svg
یک مثال:
 ![Github tags](https://github.com/VahidN/DNTCaptcha.Core/workflows/.NET%20Core%20Build/badge.svg)
در اینجا نام گردش کاری، همان نامی است که داخل فایل yml درج شده‌است و نه نام فایل متناظر. بدیهی است این نام را باید به صورت encoded وارد کنید. برای مثال اگر دارای فاصله است، نیاز است 20%‌های فواصل را به این نام اضافه کنید تا URL نهایی درست کار کند.


تکمیل گردش کاری Build، جهت تولید خودکار و ارسال یک بسته‌ی نیوگت

برای ارسال خودکار حاصل Build به سایت نیوگت، نیاز است یک API Key داشته باشیم. به همین جهت به صفحه‌ی مخصوص آن در سایت nuget پس از ورود به سایت آن، مراجعه کرده و یک کلید API جدید را صرفا برای این پروژه تولید کنید (در قسمت Available Packages بسته‌ی پیشینی را که دستی آپلود کرده بودید انتخاب کنید).


پس از کپی کردن کلید تولید شده‌ی در سایت nuget:


به قسمت settings -> secrets مخزن کد خود مراجعه کرده و این کلید را به صورت زیر وارد کنید:


در ادامه برای دسترسی به این کلید با نام NUGET_API_KEY، می‌توان از روش {{ secrets.NUGET_API_KEY }}$ در اسکریپت گردش کاری استفاده کرد.

اکنون که مخزن کد به همراه کلید API نیوگت است، می‌توان مراحل dotnet pack (برای تولید فایل nupkg) و سپس dotnet nuget push (برای انتشار خودکار فایل nupkg) را به صورت زیر، به گردش کاری خود اضافه نمود:
name: .NET Core Build

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.100-preview9-014004        
    - name: Build DNTCaptcha.Core lib
      run: dotnet build ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
    - name: Build NuGet Package
      run: dotnet pack ./src/DNTCaptcha.Core/DNTCaptcha.Core.csproj --configuration Release
    - name: Deploy NuGet Package
      run: dotnet nuget push ./src/DNTCaptcha.Core/bin/Release/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
ویرایش یک گردش کاری، همانند ویرایش یک فایل متنی معمولی مخزن کد است. می‌توان آن‌را به صورت محلی در مسیر github\workflows\aspnetcore.yml. ویرایش کرد و سپس commit، تا سبب به روز رسانی گردش کاری پروژه شود.
اشتراک‌ها
ASP.NET Core 2.0 منتشر شد

The ASP.NET team is proud to announce general availability of ASP.NET Core 2.0.  This release features compatibility with .NET Core 2.0, tooling support in Visual Studio 2017 version 15.3, and the new Razor Pages user-interface design paradigm.  For a full list of updates, you can read the release notes.  The latest SDK and tools can be downloaded from https://dot.net/core. Read the .NET Core 2.0 release announcement for more information and watch the launch video on Channel 9.

 
ASP.NET Core 2.0 منتشر شد
اشتراک‌ها
یک مثال از Angular , Angular Material , Docker

Features

Responsive layout (flex layout module)
Internationalization
Lazy loading modules
Interceptors and Events (Progress bar active, if a request is pending)
CRUD: create, update and remove heroes
Service Workers
Custom example library
Search bar, to look for heroes
Angular Pipes
Custom loading page
Modal and toasts (snakbar)!
Unit tests with Jasmine and Karma including code coverage
End-to-end tests with Protractor
ES6 Promises
Github pages deploy ready
Google Tag Manager
Modernizr (browser features detection)
Following the best practices! 
یک مثال از Angular , Angular Material , Docker
اشتراک‌ها
GitHub هم ایران را تحریم کرد

البته شایدم بودیم، الان که برای من این ایمیل اومد

Due to U.S. trade controls law restrictions, your GitHub account has been restricted.
For individual accounts, you may have limited access to free GitHub public repository
services for personal communications only. Please read about GitHub and Trade Controls at
https://help.github.com/articles/github-and-trade-controls for more information. If you believe your account has
been flagged in error, please file an appeal at https://airtable.com/shrGBcceazKIoz6pY. 

 حساب کاربریم تو GitHub اینجوری شده

GitHub هم ایران را تحریم کرد