diff --git a/.codex/README.md b/.codex/README.md new file mode 100644 index 0000000..b7a513a --- /dev/null +++ b/.codex/README.md @@ -0,0 +1,33 @@ +# Codex Policy Notes (Strict Workspace Safety) + +This repository uses a strict Codex approval policy for cross-platform use on: + +- Windows 11 (`powershell` / `pwsh`) +- Devcontainer environments + +## Intent + +- Keep `approval_policy = "untrusted"` and `sandbox_mode = "workspace-write"`. +- Keep network access disabled in workspace sandbox (`network_access = false`). +- Use a narrow allowlist in `.codex/rules/default.rules` for expected low-risk workflows. + +## Important Limitation + +`allow` rules are convenience controls for command prefixes. They are **not** a workspace-path guard and do not prove a command is read-only. + +Because of that, this repo does **not** allow broad command prefixes such as: + +- generic shell wrappers (for example `pwsh -Command ...`, `bash -lc ...`) +- broad read command families (`ls`, `cat`, `find`, etc.) + +Extra read commands may still require approval by design. + +## Expected No-Prompt Commands + +- Selected git read operations (`status`, `diff`, `log`, `show`, `rev-parse`, `branch --show-current`, `ls-files`) +- Repo check script (`Invoke-RepoChecks.ps1`) in `pwsh` and `powershell` +- Direct analyzer/test commandlets: + - `Invoke-ScriptAnalyzer` + - `Invoke-Pester` + +All other commands are intentionally reviewed case-by-case. diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..9417db3 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,45 @@ +model = "gpt-5.3-codex" +model_reasoning_effort = "high" +model_verbosity = "high" + +approval_policy = "untrusted" +sandbox_mode = "workspace-write" +allow_login_shell = false + +cli_auth_credentials_store = "auto" +web_search = "live" + +[features] +multi_agent = true + +[agents] +max_threads = 4 +max_depth = 1 + +[shell_environment_policy] +inherit = "core" +ignore_default_excludes = false + +[sandbox_workspace_write] +network_access = false + +[history] +persistence = "save-all" +max_bytes = 5242880 + +# Profiles are currently experimental in Codex docs. +[profiles.ci_safe] +model = "gpt-5.3-codex" +model_reasoning_effort = "high" +model_verbosity = "medium" +approval_policy = "never" +sandbox_mode = "read-only" +allow_login_shell = false +web_search = "disabled" + +[profiles.ci_safe.shell_environment_policy] +inherit = "core" +ignore_default_excludes = false + +[profiles.ci_safe.history] +persistence = "none" diff --git a/.codex/rules/default.rules b/.codex/rules/default.rules new file mode 100644 index 0000000..c7e5e94 --- /dev/null +++ b/.codex/rules/default.rules @@ -0,0 +1,26 @@ +# Conservative allowlist for low-risk, high-frequency commands. +prefix_rule(pattern=["git", "status"], decision="allow") +prefix_rule(pattern=["git", "diff"], decision="allow") +prefix_rule(pattern=["git", "log"], decision="allow") +prefix_rule(pattern=["git", "show"], decision="allow") +prefix_rule(pattern=["git", "rev-parse"], decision="allow") +prefix_rule(pattern=["git", "branch", "--show-current"], decision="allow") +prefix_rule(pattern=["git", "ls-files"], decision="allow") + +# Shared repo validation workflow. +prefix_rule(pattern=["pwsh", "-NoProfile", "-File", ".\\Invoke-RepoChecks.ps1"], decision="allow") +prefix_rule(pattern=["pwsh", "-NoProfile", "-File", "./Invoke-RepoChecks.ps1"], decision="allow") +prefix_rule(pattern=["pwsh", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ".\\Invoke-RepoChecks.ps1"], decision="allow") +prefix_rule(pattern=["powershell", "-NoProfile", "-File", ".\\Invoke-RepoChecks.ps1"], decision="allow") +prefix_rule(pattern=["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ".\\Invoke-RepoChecks.ps1"], decision="allow") + +# Direct analyzer/test commandlets in both PowerShell 7 and Windows PowerShell. +prefix_rule(pattern=["pwsh", "-NoProfile", "-Command", "Invoke-ScriptAnalyzer"], decision="allow") +prefix_rule(pattern=["powershell", "-NoProfile", "-Command", "Invoke-ScriptAnalyzer"], decision="allow") +prefix_rule(pattern=["pwsh", "-NoProfile", "-Command", "Invoke-Pester"], decision="allow") +prefix_rule(pattern=["powershell", "-NoProfile", "-Command", "Invoke-Pester"], decision="allow") + +# Hard blocks for destructive patterns that should never auto-run. +prefix_rule(pattern=["git", "reset", "--hard"], decision="forbidden") +prefix_rule(pattern=["git", "clean", "-fdx"], decision="forbidden") +prefix_rule(pattern=["rm", "-rf"], decision="forbidden") diff --git a/.devcontainer/.dockerignore b/.devcontainer/.dockerignore new file mode 100644 index 0000000..d864cae --- /dev/null +++ b/.devcontainer/.dockerignore @@ -0,0 +1,3 @@ +* +!Dockerfile +!.dockerignore diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..cad52b7 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,149 @@ +# syntax=docker/dockerfile:1 +FROM cgr.dev/chainguard/wolfi-base:latest + +ARG PS_VERSION=7.5.4 +ARG PESTER_VERSION=5.7.1 +ARG PSSA_VERSION=1.24.0 +ARG TARGETARCH +ARG ENABLE_AI_TOOLS=true +ARG BUILDKIT_INLINE_CACHE=1 +ARG IMAGE_TITLE="PowerShell Dev Container" +ARG IMAGE_DESCRIPTION="Wolfi-based PowerShell development container" +ARG IMAGE_SOURCE="https://github.com/thetechgy/ArchiTechLabs-Script-Hub" +ARG IMAGE_LICENSES="MIT" +ARG VCS_REF="unknown" +ARG BUILD_DATE="unknown" + +LABEL org.opencontainers.image.title="${IMAGE_TITLE}" \ + org.opencontainers.image.description="${IMAGE_DESCRIPTION}" \ + org.opencontainers.image.source="${IMAGE_SOURCE}" \ + org.opencontainers.image.licenses="${IMAGE_LICENSES}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.created="${BUILD_DATE}" + +# ----------------------------- +# Base OS packages (always needed) +# ----------------------------- +RUN apk add --no-cache \ + bash \ + ca-certificates \ + curl \ + git \ + gnupg-gpgconf \ + openssh-client \ + icu-libs \ + libstdc++ \ + libgcc + +# ----------------------------- +# Install PowerShell +# ----------------------------- +RUN : "${TARGETARCH:?TARGETARCH must be set by BuildKit}" \ + && case "${TARGETARCH}" in \ + amd64) PS_ARCH="x64" ;; \ + arm64) PS_ARCH="arm64" ;; \ + *) echo "Unsupported TARGETARCH=${TARGETARCH}" && exit 1 ;; \ + esac \ + && PS_TARBALL="powershell-${PS_VERSION}-linux-${PS_ARCH}.tar.gz" \ + && mkdir -p "/opt/microsoft/powershell/${PS_VERSION}" \ + && curl -fsSL -o "/tmp/${PS_TARBALL}" \ + "https://github.com/PowerShell/PowerShell/releases/download/v${PS_VERSION}/${PS_TARBALL}" \ + && curl -fsSL -o /tmp/hashes.sha256 \ + "https://github.com/PowerShell/PowerShell/releases/download/v${PS_VERSION}/hashes.sha256" \ + && PS_EXPECTED_SHA256="" \ + && while IFS= read -r checksum_line; do \ + case "${checksum_line}" in \ + *"${PS_TARBALL}"*) PS_EXPECTED_SHA256="${checksum_line%% *}"; break ;; \ + esac; \ + done < /tmp/hashes.sha256 \ + && [ -n "${PS_EXPECTED_SHA256}" ] \ + && PS_EXPECTED_SHA256="$(printf '%s' "${PS_EXPECTED_SHA256}" | tr '[:upper:]' '[:lower:]')" \ + && set -- $(sha256sum "/tmp/${PS_TARBALL}") \ + && PS_ACTUAL_SHA256="$1" \ + && if [ "${PS_ACTUAL_SHA256}" != "${PS_EXPECTED_SHA256}" ]; then \ + echo "Checksum mismatch for ${PS_TARBALL}" >&2; \ + echo "Expected: ${PS_EXPECTED_SHA256}" >&2; \ + echo "Actual: ${PS_ACTUAL_SHA256}" >&2; \ + exit 1; \ + fi \ + && tar -xzf "/tmp/${PS_TARBALL}" -C "/opt/microsoft/powershell/${PS_VERSION}" \ + && rm -f "/tmp/${PS_TARBALL}" /tmp/hashes.sha256 \ + && chmod 755 "/opt/microsoft/powershell/${PS_VERSION}/pwsh" \ + && chmod -R a+rX "/opt/microsoft/powershell/${PS_VERSION}" \ + && mkdir -p /usr/local/bin \ + && ln -sf "/opt/microsoft/powershell/${PS_VERSION}/pwsh" /usr/local/bin/pwsh \ + && ln -sf "/opt/microsoft/powershell/${PS_VERSION}/pwsh" /usr/bin/pwsh \ + && pwsh -NoLogo -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' + +# ----------------------------- +# PowerShell tooling (always needed) +# ----------------------------- +RUN pwsh -NoLogo -NoProfile -Command "\ + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \ + Install-Module Pester -RequiredVersion ${PESTER_VERSION} -Scope AllUsers -Force -AllowClobber; \ + Install-Module PSScriptAnalyzer -RequiredVersion ${PSSA_VERSION} -Scope AllUsers -Force -AllowClobber; \ + Import-Module Pester -RequiredVersion ${PESTER_VERSION} -Force; \ + Import-Module PSScriptAnalyzer -RequiredVersion ${PSSA_VERSION} -Force; \ + " + +# ----------------------------- +# Non-root user for devcontainers +# ----------------------------- +RUN adduser -D -u 1000 vscode \ + && mkdir -p /home/vscode \ + && chown -R vscode:vscode /home/vscode + +# Avoid git dubious-ownership warnings for the fixed devcontainer workspace path. +RUN git config --system --add safe.directory /workspace + +# ----------------------------- +# AI tooling + CLIs (optional) +# ----------------------------- +RUN if [ "${ENABLE_AI_TOOLS}" = "true" ]; then \ + echo 'Installing AI assist tooling + Node + Codex + Claude...' && \ + apk add --no-cache \ + nodejs-22 \ + npm \ + bubblewrap \ + socat \ + procps \ + gh \ + delta \ + fzf \ + ripgrep \ + fd \ + jq \ + yq \ + patch \ + diffutils \ + sed \ + gawk \ + coreutils \ + findutils \ + tree \ + gzip \ + unzip \ + xz \ + && mkdir -p /etc/profile.d \ + && printf '%s\n' \ + 'export PATH="$HOME/.local/bin:$PATH"' \ + > /etc/profile.d/00-devcontainer-paths.sh \ + && su -s /bin/bash vscode -c ' \ + set -euo pipefail; \ + export PATH="$HOME/.local/bin:$PATH"; \ + mkdir -p "$HOME/.local" "$HOME/.npm"; \ + npm config set prefix "$HOME/.local"; \ + npm config set cache "$HOME/.npm"; \ + node --version; npm --version; \ + npm i -g @openai/codex@latest; \ + codex --version || true; \ + mkdir -p "$HOME/.local/bin"; \ + curl -fsSL https://claude.ai/install.sh | bash; \ + command -v claude >/dev/null 2>&1 && claude --version || true; \ + ' \ + ; else \ + echo 'AI tooling disabled (ENABLE_AI_TOOLS=false)'; \ + fi + +USER vscode +WORKDIR /workspaces diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000..4f12099 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,172 @@ +# Development Container (`.devcontainer/`) + +## Overview + +This directory defines a reusable VS Code devcontainer for PowerShell development on a Wolfi base image. + +Primary goals: + +- Consistent PowerShell tooling across machines +- Fast onboarding with minimal host setup +- Reasonable hardening for development workloads without breaking day-to-day workflows + +## Current Defaults + +- Base image: `cgr.dev/chainguard/wolfi-base:latest` +- PowerShell version arg: `PS_VERSION=7.5.4` +- Core modules pinned to CI parity: + - `Pester=5.7.1` + - `PSScriptAnalyzer=1.24.0` +- AI tooling: enabled by default (`ENABLE_AI_TOOLS=true`) +- Runtime user: `vscode` (non-root) +- Container init enabled (`init: true`) +- Explicit workspace mount/folder: host repo is mounted at `/workspace` +- Musl compatibility env for Claude tooling: `USE_BUILTIN_RIPGREP=0` + +## Security and Reliability Controls + +The current config includes the following controls: + +- Non-root development user (`remoteUser: vscode`) +- UID/GID alignment enabled (`updateRemoteUserUID: true`) +- Runtime hardening via `runArgs`: + - `--security-opt=no-new-privileges` + - `--cap-drop=ALL` + - `--cap-add=DAC_OVERRIDE` +- Read-only bind mount of `.devcontainer` into the container at `/workspace/.devcontainer` + - This reduces risk of in-container tampering with `devcontainer.json` / `Dockerfile` before a rebuild. +- Minimal init process enabled (`init: true`) to improve PID 1 signal handling and child process reaping +- Telemetry opt-out environment variables for .NET and PowerShell +- PowerShell tarball SHA-256 verification before extraction +- Strict shell behavior (`set -euo pipefail`) in the optional AI tooling setup block +- Build context locked down via `.dockerignore` +- OCI image labels for title/description/source/license/revision/created metadata +- Optional Claude sandbox prerequisites included when AI tooling is enabled (`bubblewrap`, `socat`) +- Git `safe.directory` pre-registration for `/workspace` to reduce Podman ownership-warning churn during attach +- Capability model rationale: + - `--cap-drop=ALL` remains the baseline. + - `DAC_OVERRIDE` is explicitly re-added because VS Code server install/setup in this Podman flow may execute through a root session that must create paths under `/vscode/vscode-server`. + - This avoids startup permission failures without host-side permission mutation scripts. + +Guardrails: + +- Do not add Linux capabilities beyond `DAC_OVERRIDE` unless explicitly required. +- Do not add `SYS_ADMIN` for this devcontainer profile. + +## Post-Create Validation + +`postCreateCommand` imports required versions and fails if version requirements are not met. It then prints versions for: + +- `pwsh` +- `Pester` +- `PSScriptAnalyzer` + +## Workspace and Persistence Model + +- Workspace path inside container is `/workspace` (set explicitly in `devcontainer.json`). +- `.devcontainer` is mounted read-only inside the container to help protect host-side devcontainer config from in-container modification. +- No repo-managed persistent dev volumes are configured in `devcontainer.json`. +- VS Code Dev Containers may mount a shared external `vscode` volume at `/vscode` for VS Code server and extension cache data. +- Rebuilds still reset most runtime state (for example, shell history/tool config) unless you add additional mounts. + +## Files in This Directory + +- `devcontainer.json`: Devcontainer runtime settings, hardening `runArgs`, env vars, post-create validation +- `Dockerfile`: Image build logic and tooling installation +- `.dockerignore`: Restricts build context to devcontainer files +- `README.md`: This document + +## Build Arguments + +Supported Docker build args: + +- `PS_VERSION` (default: `7.5.4`) +- `PESTER_VERSION` (default: `5.7.1`) +- `PSSA_VERSION` (default: `1.24.0`) +- `ENABLE_AI_TOOLS` (default: `true`) +- `TARGETARCH` (must be provided by BuildKit/devcontainer tooling) +- `BUILDKIT_INLINE_CACHE` (default: `1`, consumed to avoid noisy build warnings) +- `IMAGE_TITLE` +- `IMAGE_DESCRIPTION` +- `IMAGE_SOURCE` +- `IMAGE_LICENSES` +- `VCS_REF` +- `BUILD_DATE` + +Notes: + +- `TARGETARCH` has no fallback default by design. Builds fail fast if it is missing. +- The current image policy intentionally tracks latest Wolfi base and latest OS package versions at build time. +- PowerShell module versions are pinned to the same versions used in CI for local/CI parity. + +## Podman + VS Code Setup + +This repo is tested with Podman on Linux and Windows+WSL. + +Minimum VS Code setting: + +```json +{ + "dev.containers.dockerPath": "podman" +} +``` + +Optional if you use compose-based devcontainers: + +```json +{ + "dev.containers.dockerComposePath": "podman-compose" +} +``` + +Roadmap: Evaluate VS Code volume-based workspace workflow (Clone in Volume) as a future improvement for performance and reduced host-to-container exposure. + +## AI Tooling (Default On) + +AI tooling is enabled by default (`ENABLE_AI_TOOLS=true`). For constrained environments, you can opt out by setting `ENABLE_AI_TOOLS=false` in a local devcontainer override. + +When enabled, the image installs: + +- Wolfi Node runtime (`nodejs-22`, `npm`) and package-management install flow +- Codex CLI via npm (`@openai/codex@latest`) with user-local npm prefix under `/home/vscode/.local` +- Claude Code via native installer (`curl -fsSL https://claude.ai/install.sh | bash`) +- Claude sandbox prerequisites on Linux/WSL (`bubblewrap`, `socat`) +- additional workflow tools (`delta`, `fzf`, `gh`) +- common CLI helpers (`ripgrep`, `fd`, `jq`, `yq`, `patch`, `diffutils`, `tree`, etc.) + +Wolfi/musl notes: + +- `USE_BUILTIN_RIPGREP=0` is set in `containerEnv` for Claude compatibility on musl-based distributions. +- Required runtime libraries are present in the base image (`libgcc`, `libstdc++`) and `ripgrep` is installed in the AI tooling path. + +Sandboxing notes: + +- This profile is optimized for strong outer container isolation plus Claude nested sandbox support. +- Codex nested Docker firewall sandboxing (`NET_ADMIN`/`NET_RAW` + allowlists) is intentionally not enabled in this default hardened profile. + +This path can be disabled for constrained environments and is not required for PowerShell module development. + +## Expected Log Noise (Can Be Ignored) + +You may still see these warnings from VS Code/Podman internals during container startup: + +- `Ignoring option 'skip-requirements-check' ...` +- `Error: AttachConsole failed` (transient Windows ConPTY/node-pty noise; non-fatal when container startup and attach succeed) +- `safe.directory: Failed to get host owner ... powershell.exe ENOENT` (host owner probe noise on some Windows/WSL Podman paths; non-fatal when attach succeeds) + +These come from generated helper images or VS Code server internals, not from functional issues in this repo's devcontainer configuration. + +## Usage + +1. Open the repository in VS Code. +2. Run `Dev Containers: Reopen in Container`. +3. Wait for the first build to complete. +4. Confirm post-create output includes `pwsh`, `Pester`, and `PSScriptAnalyzer` version lines. + +## Non-Goals + +This devcontainer is for development convenience and consistency. It is not intended as: + +- A production runtime image +- A hardened service container profile +- A published, immutable release image diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7b14c83 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", + "name": "PowerShell Dev Container (Wolfi)", + "build": { + "dockerfile": "Dockerfile", + "args": { + "PS_VERSION": "7.5.4", + "PESTER_VERSION": "5.7.1", + "PSSA_VERSION": "1.24.0", + "ENABLE_AI_TOOLS": "true" + } + }, + "remoteUser": "vscode", + "init": true, + "updateRemoteUserUID": true, + "mounts": [ + "source=${localWorkspaceFolder}/.devcontainer,target=/workspace/.devcontainer,type=bind,readonly" + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", + "workspaceFolder": "/workspace", + "runArgs": [ + "--security-opt=no-new-privileges", + "--cap-drop=ALL", + "--cap-add=DAC_OVERRIDE" + ], + "containerEnv": { + "DOTNET_CLI_TELEMETRY_OPTOUT": "1", + "POWERSHELL_TELEMETRY_OPTOUT": "1", + "USE_BUILTIN_RIPGREP": "0" + }, + "postCreateCommand": "pwsh -NoProfile -Command '$ErrorActionPreference = \"Stop\"; $requiredPester = [Version]\"5.7.1\"; $requiredPssa = [Version]\"1.24.0\"; Import-Module Pester -RequiredVersion $requiredPester -Force; Import-Module PSScriptAnalyzer -RequiredVersion $requiredPssa -Force; $pesterVersion = (Get-Module -Name Pester).Version; $pssaVersion = (Get-Module -Name PSScriptAnalyzer).Version; if ($pesterVersion -ne $requiredPester) { throw \"Expected Pester $requiredPester but found $pesterVersion\" }; if ($pssaVersion -ne $requiredPssa) { throw \"Expected PSScriptAnalyzer $requiredPssa but found $pssaVersion\" }; Write-Host (\"pwsh: \" + $PSVersionTable.PSVersion); Write-Host (\"Pester: \" + $pesterVersion); Write-Host (\"PSScriptAnalyzer: \" + $pssaVersion)'", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "pwsh" + } + } + } +} diff --git a/.editorconfig b/.editorconfig index 2711ac1..5a952c7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,18 +1,81 @@ root = true +# ------------------------------------------------------- +# Defaults +# ------------------------------------------------------- [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true - -[*.ps1] indent_style = space indent_size = 4 +max_line_length = 120 + +# ------------------------------------------------------- +# PowerShell source files (code) +# ------------------------------------------------------- +[*.{ps1,psm1}] +indent_size = 4 +max_line_length = 120 + +# Pester test files (allow longer assertions/Describe blocks) +[**/*.[Tt]ests.ps1] +indent_size = 4 +max_line_length = 140 +# PowerShell manifests / data / session config (still PowerShell syntax) +# Best practice: keep consistent with PowerShell code indentation to reduce churn. +[*.{psd1,pssc,psrc}] +indent_size = 4 +max_line_length = 120 + +[PSScriptAnalyzerSettings.psd1] +indent_size = 4 + +# ------------------------------------------------------- +# Markdown +# ------------------------------------------------------- [*.md] +indent_size = 2 trim_trailing_whitespace = false +max_line_length = off -[*.json] -indent_style = space +# ------------------------------------------------------- +# JSON / JSONC (Logic Apps, configs) +# ------------------------------------------------------- +[*.{json,jsonc}] +indent_size = 2 +max_line_length = off + +# ------------------------------------------------------- +# YAML (GitHub Actions, Dependabot, issue templates) +# ------------------------------------------------------- +[*.{yml,yaml}] +indent_size = 2 +max_line_length = off + +# ------------------------------------------------------- +# XML +# ------------------------------------------------------- +[*.{xml,nuspec,ps1xml,cdxml}] indent_size = 2 +max_line_length = 120 + +# ------------------------------------------------------- +# Shell scripts +# ------------------------------------------------------- +[*.sh] +indent_size = 2 + +# ------------------------------------------------------- +# Windows batch scripts +# ------------------------------------------------------- +[*.{cmd,bat}] +end_of_line = crlf + +# ------------------------------------------------------- +# Repo meta / Git config +# ------------------------------------------------------- +[{.gitattributes,.gitignore,LICENSE}] +max_line_length = off diff --git a/.gitattributes b/.gitattributes index 669db0d..41cb633 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,22 @@ -* text=auto -*.ps1 text eol=lf -*.md text -*.log binary -*.csv text -*.json text -*.zip binary +# Normalize all text to LF +* text=auto eol=lf + +# Windows batch scripts +*.cmd text eol=crlf +*.bat text eol=crlf + +# Common binaries +*.zip binary +*.7z binary +*.tar binary +*.gz binary +*.rar binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary + +# SVG is text-based +*.svg text eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..ff8b4d1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# GitHub configuration (workflows, templates, dependabot, policies) + +.github/ @thetechgy diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ca517d..0d5ee92 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,40 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - version: 2 + updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "/" # Location of package manifests + # ------------------------------------------------------------ + # GitHub Actions (workflows + composite actions) + # ------------------------------------------------------------ + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "develop" schedule: interval: "weekly" + day: "monday" + time: "09:00" + timezone: "America/New_York" + open-pull-requests-limit: 2 + rebase-strategy: "auto" + + # Keep PRs easy to route/triage + labels: + - "dependencies" + - "github-actions" + + # Reduce PR spam by grouping updates + groups: + github-actions-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + github-actions-major: + patterns: + - "*" + update-types: + - "major" + + # Make PRs consistent and readable + commit-message: + prefix: "chore" + include: "scope" diff --git a/.github/workflows/ci-cd-pipeline.yml b/.github/workflows/ci-cd-pipeline.yml deleted file mode 100644 index bf2a95e..0000000 --- a/.github/workflows/ci-cd-pipeline.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: PowerShell CI/CD Pipeline - -on: - push: - branches: - - main - - develop - -jobs: - lint-and-test: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - # Install PSScriptAnalyzer - - name: Install PSScriptAnalyzer - run: Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck - - # Run PSScriptAnalyzer - - name: Run PSScriptAnalyzer - run: Invoke-ScriptAnalyzer -Path . -Recurse - - # Install Pester for testing - - name: Install Pester - run: Install-Module -Name Pester -Force -SkipPublisherCheck - - # Run Pester tests - - name: Run Pester Tests - run: Invoke-Pester -Path ./tests - - # Uncomment this section when you're ready to add packaging and deployment - # package-and-deploy: - # runs-on: windows-latest - # needs: lint-and-test - # if: github.ref == 'refs/heads/main' # Only run for the main branch - # steps: - # - uses: actions/checkout@v2 - # - # # Package Scripts - # - name: Package Scripts - # run: Compress-Archive -Path ./scripts/* -DestinationPath ./scripts.zip - # - # # Upload Release Asset to GitHub Releases - # - name: Upload Release Asset - # uses: softprops/action-gh-release@v1 - # with: - # files: scripts.zip - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # - # # Deployment steps (uncomment and modify when you're ready) - # - name: Deploy to Remote Server - # run: | - # # Deployment steps here diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5cf9e6f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,257 @@ +name: CI + +on: + pull_request: + branches: [develop, main] + paths-ignore: + - "docs/**" + - "**/*.md" + - ".vscode/**" + push: + branches: [develop, main] + paths-ignore: + - "docs/**" + - "**/*.md" + - ".vscode/**" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + PSSA_VERSION: "1.24.0" + PESTER_VERSION: "5.7.1" + +jobs: + analyze: + name: PSScriptAnalyzer + runs-on: ubuntu-24.04 + timeout-minutes: 10 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Linux) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.local/share/powershell/Modules + key: linux-psmodules-pssa-${{ env.PSSA_VERSION }} + restore-keys: | + linux-psmodules- + + - name: Install PSScriptAnalyzer + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'PSScriptAnalyzer' + $requiredVersion = [Version]$env:PSSA_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run PSScriptAnalyzer (repo policy) + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $settingsPath = Join-Path $PWD 'PSScriptAnalyzerSettings.psd1' + if (-not (Test-Path $settingsPath)) { throw "Settings file not found at $settingsPath" } + + $targets = @( + 'modules' + 'scripts' + 'automation/runbooks' + ) | Where-Object { Test-Path $_ } + + if (-not $targets) { + Write-Host "No analyzer targets found; skipping." + exit 0 + } + + $results = foreach ($t in $targets) { + Invoke-ScriptAnalyzer -Path $t -Recurse -Settings $settingsPath + } + + if ($results) { + $results | Format-Table RuleName, Severity, ScriptName, Line, Message -AutoSize + throw "PSScriptAnalyzer found $($results.Count) issue(s)." + } + + test_ps51: + name: Pester (Windows PowerShell 5.1) + runs-on: windows-2022 + timeout-minutes: 20 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Windows) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~\Documents\PowerShell\Modules + key: windows-psmodules-pester-${{ env.PESTER_VERSION }} + restore-keys: | + windows-psmodules- + + - name: Install Pester + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'Pester' + $requiredVersion = [Version]$env:PESTER_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run Pester + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + + $resultsPath = Join-Path $env:GITHUB_WORKSPACE 'Output\TestResults' + New-Item -ItemType Directory -Path $resultsPath -Force | Out-Null + $outFile = Join-Path $resultsPath 'PesterResults_WindowsPowerShell-5.1.xml' + + Import-Module Pester -RequiredVersion $env:PESTER_VERSION -Force + + $paths = @() + + # Module tests: modules/**/Tests + $moduleTests = Get-ChildItem -Path 'modules' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($moduleTests) { $paths += $moduleTests } + + # Script tests: scripts/**/Tests + $scriptTests = Get-ChildItem -Path 'scripts' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($scriptTests) { $paths += $scriptTests } + + if (-not $paths) { + Write-Host 'No Pester tests found (modules/**/Tests or scripts/**/Tests). Skipping.' + exit 0 + } + + $config = [PesterConfiguration]::Default + $config.Run.Path = $paths + $config.Run.PassThru = $true + $config.Output.Verbosity = 'Detailed' + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'JUnitXml' + $config.TestResult.OutputPath = $outFile + + $result = Invoke-Pester -Configuration $config + if ($result.FailedCount -gt 0) { throw "Pester failures: $($result.FailedCount)" } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: pester-results-windows-ps51 + path: Output/TestResults/*.xml + retention-days: 14 + if-no-files-found: warn + + test_pwsh7: + name: Pester (PowerShell 7) + runs-on: windows-2022 + timeout-minutes: 20 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Windows) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~\Documents\PowerShell\Modules + key: windows-psmodules-pester-${{ env.PESTER_VERSION }} + restore-keys: | + windows-psmodules- + + - name: Install Pester + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'Pester' + $requiredVersion = [Version]$env:PESTER_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run Pester + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + $resultsPath = Join-Path $env:GITHUB_WORKSPACE 'Output/TestResults' + New-Item -ItemType Directory -Path $resultsPath -Force | Out-Null + $outFile = Join-Path $resultsPath 'PesterResults_PowerShell-7.xml' + + Import-Module Pester -RequiredVersion $env:PESTER_VERSION -Force + + $paths = @() + + # Module tests: modules/**/Tests + $moduleTests = Get-ChildItem -Path 'modules' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($moduleTests) { $paths += $moduleTests } + + # Script tests: scripts/**/Tests + $scriptTests = Get-ChildItem -Path 'scripts' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($scriptTests) { $paths += $scriptTests } + + if (-not $paths) { + Write-Host 'No Pester tests found (modules/**/Tests or scripts/**/Tests). Skipping.' + exit 0 + } + + $config = [PesterConfiguration]::Default + $config.Run.Path = $paths + $config.Run.PassThru = $true + $config.Output.Verbosity = 'Detailed' + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'JUnitXml' + $config.TestResult.OutputPath = $outFile + + $result = Invoke-Pester -Configuration $config + if ($result.FailedCount -gt 0) { throw "Pester failures: $($result.FailedCount)" } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: pester-results-windows-pwsh7 + path: Output/TestResults/*.xml + retention-days: 14 + if-no-files-found: warn diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..7cdcdfc --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: 'Checkout Repository' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 0000000..036b80f --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,81 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: ["develop"] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + # To allow GraphQL ListCommits to work + issues: read + pull-requests: read + # To detect SAST tools + checks: read + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - name: "Checkout code" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 30f6733..308bc0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,89 @@ -**/Output/**/*.log -**/Output/**/*.csv -**/Output/**/*.json -**/Output/**/*.txt -**/Logs/** -*.log +# ---------------------------- +# OS Specific (Windows) +# ---------------------------- +Thumbs.db +Desktop.ini +# ---------------------------- +# VS Code (track team config only) +# ---------------------------- .vscode/* !.vscode/settings.json !.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json -*.zip -artifacts/ -build/ +.vscode-server/ +*.code-workspace.user -Thumbs.db -.DS_Store -*.ps1xml +# ---------------------------- +# PowerShell +# ---------------------------- +# PSScriptAnalyzer user overrides +PSScriptAnalyzerSettings.psd1.user + +# Transcripts +PowerShell_transcript*.txt + +# Extension/cache byproducts +*.ps1.cache +*.psd1.cache +*.psm1.cache + +# ---------------------------- +# Pester / Test output +# ---------------------------- +**/TestResults/ +testResults.xml +*.TestResults.xml +*.trx +*.nunit.xml +*.junit.xml + +# Coverage outputs +coverage.xml +**/coverage*/ +codecoverage*.xml +*.cobertura.xml + +# Pester cache +.pester/ + +# ---------------------------- +# Build / artifact output +# ---------------------------- +Output/ +Logs/ +Reports/ + +# ---------------------------- +# Logs (generic) +# ---------------------------- +*.log + +# ---------------------------- +# Secrets & Credentials (local-only) +# ---------------------------- +.env +.env.* +.env.local +.env.*.local + +*.secrets.json +*.private.json +*-credentials.json +*.azureauth + +sp-*.json +ServicePrincipal.json + +# ---------------------------- +# Temporary & editor junk +# ---------------------------- +*.tmp +*.temp +*.bak +*.swp +*~ +*.orig +*.rej diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..99ca0bf --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,10 @@ +{ + // Keep defaults on; selectively relax rules that commonly create noise. + "default": true, + // Line length: off (consistent with .editorconfig for Markdown) + "MD013": false, + // Inline HTML: allow (badges, tables,
, details/summary) + "MD033": false, + // First line heading: allow (badges/front-matter/title blocks) + "MD041": false +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 44b7e7b..ee60c27 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,16 +1,13 @@ { - "recommendations": [ - "ms-vscode.powershell", - "eamodio.gitlens", - "editorconfig.editorconfig", - "pspester.pester-test", - "yzhang.markdown-all-in-one", - "redhat.vscode-yaml", - "mutantdino.resourcemonitor", - "zainchen.json", - "janisdd.vscode-edit-csv", - "dotjoshjohnson.xml", - "IBM.output-colorizer", - "davidanson.vscode-markdownlint" - ] + "recommendations": [ + "ms-vscode.PowerShell", + "pspester.pester-test", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github", + "EditorConfig.EditorConfig", + "redhat.vscode-yaml", + "DavidAnson.vscode-markdownlint", + "streetsidesoftware.code-spell-checker", + "PKief.material-icon-theme" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 465f1bc..0bd8ee8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,15 @@ { - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "files.eol": "\n", - "editor.tabSize": 4, - "editor.insertSpaces": true, - "editor.formatOnSave": true, - "powershell.codeFormatting.preset": "OTBS", - "powershell.codeFormatting.useCorrectCasing": true, + "workbench.iconTheme": "material-icon-theme", + "chat.disableAIFeatures": true, + "git.blame.editorDecoration.enabled": true, + "git.blame.statusBarItem.enabled": true, "powershell.scriptAnalysis.enable": true, "powershell.scriptAnalysis.settingsPath": "./PSScriptAnalyzerSettings.psd1", - "extensions.ignoreRecommendations": false, - "terminal.integrated.defaultProfile.windows": "PowerShell 7", - "terminal.integrated.profiles.windows": { - "PowerShell 7": { - "path": "C:\\Program Files\\PowerShell\\7\\pwsh.exe" - } + // cSpell: keep Spell Checker available, stop reporting into Problems + "cSpell.diagnosticLevel": "Hint", + "search.exclude": { + "**/.git/**": true, + "**/Output/**": true, + "**/Logs/**": true } } diff --git a/ArchiTechLabs-Script-Hub.code-workspace b/ArchiTechLabs-Script-Hub.code-workspace deleted file mode 100644 index a03933b..0000000 --- a/ArchiTechLabs-Script-Hub.code-workspace +++ /dev/null @@ -1,14 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": { - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "powershell.codeFormatting.useCorrectCasing": true, - "editor.tabSize": 4, - "editor.defaultFormatter": "ms-vscode.powershell" - } -} diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 0000000..28b1942 --- /dev/null +++ b/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,94 @@ +@{ + # Keep signal high for a shared repo. Info-level findings tend to create noise. + Severity = @('Error', 'Warning') + + # Rules excluded intentionally due to common false-positives or known analyzer issues. + ExcludeRules = @( + # Frequently noisy with splatting, scriptblocks, and dynamic parameter patterns. + 'PSReviewUnusedParameter' + + # Known to be unstable/buggy in some analyzer versions/patterns. + 'AvoidReservedCharInCmdlet' + ) + + Rules = @{ + + # --------------------------------------------------------------------- + # Compatibility (PS 5.1 + PS 7) + # --------------------------------------------------------------------- + PSUseCompatibleSyntax = @{ + Enable = $true + # Enforces syntax that works in BOTH Windows PowerShell 5.1 and PowerShell 7+. + TargetVersions = @('5.1', '7.0') + } + + # --------------------------------------------------------------------- + # Authoring / maintainability + # --------------------------------------------------------------------- + PSUseApprovedVerbs = @{ + Enable = $true + } + + PSProvideCommentHelp = @{ + Enable = $true + # Public surface should be documented; internal helpers are often too noisy to enforce globally. + ExportedOnly = $true + } + + PSUseShouldProcessForStateChangingFunctions = @{ + Enable = $true + } + + PSAvoidGlobalVars = @{ + Enable = $true + } + + # --------------------------------------------------------------------- + # Security / unsafe patterns + # --------------------------------------------------------------------- + PSAvoidUsingInvokeExpression = @{ + Enable = $true + } + + PSAvoidUsingConvertToSecureStringWithPlainText = @{ + Enable = $true + } + + PSAvoidUsingPlainTextForPassword = @{ + Enable = $true + } + + # --------------------------------------------------------------------- + # Team-consistent readability (minimal, to reduce diff churn) + # --------------------------------------------------------------------- + PSAvoidUsingCmdletAliases = @{ + Enable = $true + } + + PSAvoidUsingWriteHost = @{ + Enable = $true + } + + PSUseConsistentIndentation = @{ + Enable = $true + IndentationSize = 4 + Kind = 'space' + } + + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + IgnoreOneLineBlock = $true + } + + PSPlaceCloseBrace = @{ + Enable = $true + NoEmptyLineBefore = $true + IgnoreOneLineBlock = $true + } + + # Intentionally NOT forcing these (higher churn / opinionated style policing): + PSUseConsistentWhitespace = @{ Enable = $false } + PSAlignAssignmentStatement = @{ Enable = $false } + } +} diff --git a/README.md b/README.md index 673248b..dd87127 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # ArchiTechLabs Script Hub -![Build Status](https://img.shields.io/github/workflow/status/thetechgy/ArchiTechLabs-Script-Hub/PowerShell%20CI%2FCD%20Pipeline) +[![CI](https://github.com/thetechgy/ArchiTechLabs-Script-Hub/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/thetechgy/ArchiTechLabs-Script-Hub/actions/workflows/ci.yml) +[![Dependabot](https://img.shields.io/badge/Dependabot-enabled-025e8c?logo=dependabot)](https://github.com/thetechgy/ArchiTechLabs-Script-Hub//network/updates) +[![PowerShell](https://img.shields.io/badge/PowerShell-5.1%20%7C%207.x-2671E5)](#requirements)