Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ product requirements.
- Preserve browser zoom, semantic HTML, and accessible responsive behavior.
- Do not introduce runtime dependencies or external network services without
an explicit product requirement.
- Local development supports Windows PowerShell and Linux; CI runs on Ubuntu.
Keep shell-specific setup in [`docs/code.md`](../docs/code.md).
- Nothing in `docs/` is loaded automatically. Read only the topics the task
needs.

Expand Down
10 changes: 4 additions & 6 deletions .github/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ maintainer can supply independent human approval. See
3. Update that ruleset from the checked-in JSON.
4. Fetch the live ruleset and compare it with the intended change.

```sh
```powershell
gh api repos/OWNER/REPO/rulesets
gh api --method PUT repos/OWNER/REPO/rulesets/<id> \
--input .github/rulesets/main.json
gh api --method PUT repos/OWNER/REPO/rulesets/<id> --input .github/rulesets/main.json
gh api repos/OWNER/REPO/rulesets/<id>
```

To create the ruleset when none exists:

```sh
gh api --method POST repos/OWNER/REPO/rulesets \
--input .github/rulesets/main.json
```powershell
gh api --method POST repos/OWNER/REPO/rulesets --input .github/rulesets/main.json
```

The required check context must match the workflow job name. A rename on either
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/windows-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Windows verification

on:
workflow_dispatch:

permissions:
contents: read

jobs:
verify-windows:
name: Verify (Windows)
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- name: Check Chrome
shell: pwsh
run: |
$chrome = (Get-Command chrome.exe -ErrorAction SilentlyContinue).Source
if (-not $chrome) {
$chrome = @(
(Join-Path $env:ProgramFiles 'Google\Chrome\Application\chrome.exe'),
(Join-Path ${env:ProgramFiles(x86)} 'Google\Chrome\Application\chrome.exe'),
(Join-Path $env:LOCALAPPDATA 'Google\Chrome\Application\chrome.exe')
) | Where-Object { Test-Path $_ } | Select-Object -First 1
}
if (-not $chrome) {
throw 'Google Chrome was not found on PATH or in its standard installation folders.'
}
& $chrome --version

- name: Install ImageMagick
shell: pwsh
run: |
if (-not (Get-Command magick.exe -ErrorAction SilentlyContinue)) {
choco install imagemagick --yes --no-progress
$magick = Get-ChildItem $env:ProgramFiles -Directory -Filter 'ImageMagick*' |
Select-Object -First 1
if (-not $magick) {
throw 'ImageMagick installed but magick.exe was not found.'
}
$env:PATH = "$($magick.FullName);$env:PATH"
$magick.FullName | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
}
magick -version

- name: Install dependencies
run: npm ci

- name: Verify
run: npm run verify -- --no-cache

- name: Upload screenshots
if: always()
uses: actions/upload-artifact@v7
with:
name: web-screenshots-windows
path: .art/shots/
if-no-files-found: warn
retention-days: 7
108 changes: 55 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,89 @@
# TypeScript static web template
# Your first web app

An accessible hello-world web app with TypeScript, Vite, layered documentation,
bounded verification, and CI-gated GitHub Pages deployment.
This template gives you a simple web page to explore and change. You do not
need to know Git or GitHub to get started. GitHub is where your project lives;
the Copilot App is where you can open and work on it.

[Live example](https://OWNER.github.io/REPO/) ·
[Use this template](https://github.com/OWNER/REPO/generate)
[See the live example](https://OWNER.github.io/REPO/) ·
[Create your own copy](https://github.com/OWNER/REPO/generate)

## Start an app
## Start in the Copilot App

Choose **Use this template** on GitHub to create a repository with its own
history. Clone your new repository, use Node.js 22.12+ (or a supported newer
version), and run:
1. On GitHub, choose **Create your own copy** (or **Use this template**) and
create a repository for your project.
2. Open that new repository in the Copilot App.
3. Install [Node.js 22.12 or newer](https://nodejs.org/en/download/) on your
Windows laptop. Node.js is the tool that runs this project. If the
Copilot App was open during installation, reopen its terminal.
4. In the Copilot App terminal, run these commands:

```sh
npm ci
npm run dev
```

Replace the greeting in `src/main.ts`, styles in `src/style.css`, and page
metadata in `index.html`. Normal browser zoom is enabled. There are no runtime
dependencies, binary assets, or application network services.
```powershell
npm ci
npm run dev
```

## Verify and build
`npm ci` sets up the tools for this project. You only need it the first time
(or after the project's tools change). `npm run dev` starts your web page on
your laptop. Open the address printed in the terminal, usually
`http://localhost:5173/`. Leave the command running while you work; press
**Ctrl+C** in the terminal when you are done.

```sh
npm run verify -- --no-cache
npm run build
npm run preview
```
5. Open `src/main.ts` in the editor and change the greeting. Save the file to
see the page update in your browser. You can change its colors and layout in
`src/style.css`, and its browser-tab title in `index.html`.

Full verification needs Chrome/Chromium and ImageMagick as well as Node.js.
On Debian or Ubuntu:
The page works on small screens, supports normal browser zoom, and has no
application services to set up.

```sh
sudo apt-get install chromium imagemagick
```
## When you are ready for more

Set `CHROME_BIN` when your browser has another name. Verification includes
unit tests, TypeScript, lint, formatting, documentation links and context
budgets, bundle budgets, and real-browser root/subpath checks at narrow/wide
sizes. Screenshots and the contact sheet land in `.art/shots/`, not the site.
Bundle thresholds remain explicit in `scripts/check-bundle.mjs`.
Run `npm run verify` to check the project, or `npm run build` to make its
publishable files. Full verification works on Windows and Linux and needs
Chrome or Chromium plus ImageMagick; setup details are in the
[code guide](docs/code.md#local-and-ci-environments). GitHub also runs the
checks for you when you make a change. The normal checks run on Linux even if
you are working on Windows; maintainers can manually run Windows verification to
check Windows too.

## Configure the generated repository
## For repository maintainers

GitHub templates copy files, **not live repository settings**.

1. Rename the package metadata with `npm pkg set name=your-app` and update the
lockfile with `npm install --package-lock-only`. Replace `OWNER` and `REPO`
in this README and [ruleset instructions](.github/rulesets/README.md), the
path example in `vite.config.ts`, and the owner in `.github/CODEOWNERS`.
2. Update the working agreement and selected topic guides for your app.
Keep the original MIT copyright notice when retaining this code; add your
own attribution where appropriate.
1. Rename the package with `npm pkg set name=your-app`, then update the lockfile
with `npm install --package-lock-only`. Replace `OWNER` and `REPO` in this
README and [ruleset instructions](.github/rulesets/README.md), the path
example in `vite.config.ts`, and the owner in `.github/CODEOWNERS`.
2. Update the [working agreement](.github/copilot-instructions.md) and selected
[documentation topics](docs/) for your app. Keep the original MIT copyright
notice when retaining this code; add your own attribution where appropriate.
3. Enable GitHub Actions and select **Settings → Pages → Source → GitHub
Actions**. An administrator may instead configure it with:

```sh
```powershell
gh api --method POST repos/OWNER/REPO/pages -f build_type=workflow
# If Pages already exists:
gh api --method PUT repos/OWNER/REPO/pages -f build_type=workflow
```

4. Push the initial app to `main` and wait for **CI / Verify** to succeed.
Run **Deploy** manually once if its completion trigger was not yet present
on the default branch. Manual deployment also requires successful push CI
for that exact main commit.
4. Push the initial app to `main` and wait for **CI / Verify** to succeed. Run
**Deploy** manually once if its completion trigger was not yet present on
the default branch. Manual deployment also requires successful push CI for
that exact `main` commit.
5. Apply and read back the checked-in
[main ruleset](.github/rulesets/README.md) after initial publication. Its
required check is `Verify`; it requires no human approval for a solo
maintainer. Review CODEOWNERS and the policy before using them in a team.

Publishing is a Pages deployment of the exact successful main push SHA,
not a tagged release or a committed build branch. The workflow token does not
have administration permission and cannot configure Pages or branch rules.
The relative Vite base supports any repository name without rebuilding for
another URL prefix.
Publishing deploys the exact successful `main` push commit to GitHub Pages; it
is not a tagged release or a committed build branch. The workflow token cannot
configure Pages or branch rules. The relative Vite base supports any
repository name without rebuilding for another URL prefix.

## Working agreement

Start with [AGENTS.md](AGENTS.md), which points to the
[working agreement](.github/copilot-instructions.md). It routes contributors to
focused [documentation topics](docs/) and on-demand
[working agreement](.github/copilot-instructions.md). It routes contributors
to focused [documentation topics](docs/) and on-demand
[decision records](docs/decisions/README.md).

## Attribution
Expand Down
34 changes: 30 additions & 4 deletions docs/code.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ The available commands and their current composition live in `package.json` and
the scripts themselves. Use a command's help or source instead of maintaining a
second catalogue here.

## Local and CI environments

The project commands work from Windows PowerShell or a Linux shell. Full local
verification needs Chrome and ImageMagick on Windows, or Chromium and
ImageMagick on Linux. GitHub Actions runs the same verification on Ubuntu.

On Debian or Ubuntu, install the screenshot tools with:

```sh
sudo apt-get install chromium imagemagick
```

On Windows, install Google Chrome and ImageMagick, and make sure ImageMagick's
`magick.exe` is on `PATH`. Chrome is found in its standard installation
folders; set these PowerShell variables if either tool is installed elsewhere:
replace the example paths with the actual locations on your computer.

```powershell
$env:CHROME_BIN = 'C:\Program Files\Google\Chrome\Application\chrome.exe'
$env:IMAGEMAGICK_BIN = 'C:\Program Files\ImageMagick\magick.exe'
npm run verify
```

The variables only apply to the current terminal window.
The Windows verification workflow is available through a manual run;
pull-request and push checks continue to use Ubuntu.

## Change the owning abstraction

- Search for the canonical type, table, helper, or check before adding another
Expand Down Expand Up @@ -68,9 +95,8 @@ A stack is reviewed and merged from its base upward.
2. When a pull request's base is not `main`, explicitly request Copilot review;
it does not start automatically:

```sh
gh api -X POST repos/<owner>/<repo>/pulls/<number>/requested_reviewers \
-f "reviewers[]=copilot-pull-request-reviewer[bot]"
```powershell
gh api -X POST repos/<owner>/<repo>/pulls/<number>/requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"
```

3. Do not push new commits to a branch another open pull request uses as its
Expand All @@ -87,7 +113,7 @@ A stack is reviewed and merged from its base upward.
4. If the upload surface is unavailable, say so and link the screenshot artifact
from the current CI run instead:

```sh
```powershell
gh run list --branch <branch> --workflow CI --limit 1
gh run view <run-id>
```
Expand Down
38 changes: 31 additions & 7 deletions scripts/chrome.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,29 @@
import { spawn } from "node:child_process";
import { existsSync, readFileSync, rmSync } from "node:fs";
import { createConnection } from "node:net";
import { join } from "node:path";
import { join, win32 } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";

const CHROME = process.env.CHROME_BIN || "chromium";
export function resolveChromeExecutable({
platform = process.platform,
env = process.env,
exists = existsSync,
} = {}) {
if (env.CHROME_BIN) return env.CHROME_BIN;
if (platform !== "win32") return "chromium";

const chromePath = (base) =>
base ? win32.join(base, "Google", "Chrome", "Application", "chrome.exe") : null;
const installed = [
chromePath(env.PROGRAMFILES),
chromePath(env["PROGRAMFILES(X86)"]),
chromePath(env.LOCALAPPDATA),
].find((candidate) => candidate && exists(candidate));

return installed ?? "chrome.exe";
}

const CHROME = resolveChromeExecutable();

/**
* Is anything already listening on the debugging port?
Expand Down Expand Up @@ -73,9 +92,12 @@ export async function openChrome({ debugPort, profileDir, windowSize = "1280,800
(browser ? `It says it is ${browser}.\n` : "") +
"\nThat is nearly always a headless browser left behind by an " +
"interrupted run.\nIt has to go, because attaching to it would drive " +
"the wrong browser and fail\nsomewhere unrelated. Find it and stop it:\n\n" +
` ps -eo pid,cmd | grep '[r]emote-debugging-port=${debugPort}'\n` +
" kill <pid>\n",
"the wrong browser and fail\nsomewhere unrelated. Close the leftover " +
"headless browser, then try again.\n" +
(process.platform === "win32"
? "On Windows, find it in Task Manager and choose End task.\n"
: `On Linux, find it with: ps -eo pid,cmd | grep '[r]emote-debugging-port=${debugPort}'\n` +
"Then stop it with: kill <pid>\n"),
);
process.exit(1);
}
Expand Down Expand Up @@ -107,8 +129,10 @@ export async function openChrome({ debugPort, profileDir, windowSize = "1280,800
const why = error.code === "ENOENT" ? `no such program: ${CHROME}` : error.message;
launchError = new Error(
`Could not start Chrome (${why}).\n` +
"Install Chromium, or point CHROME_BIN at the browser to use:\n" +
" CHROME_BIN=google-chrome npm run shot",
"Install Chrome/Chromium, or point CHROME_BIN at the browser executable.\n" +
(process.platform === "win32"
? "PowerShell: $env:CHROME_BIN = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'; npm run shot\n"
: "Linux: CHROME_BIN=google-chrome npm run shot\n"),
);
});

Expand Down
8 changes: 5 additions & 3 deletions scripts/shot-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
import { existsSync, readdirSync, rmSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";

import { magick, haveImageMagick } from "./tools.mjs";

Expand Down Expand Up @@ -137,10 +137,12 @@ export function buildSheet() {
}
}

if (import.meta.url === `file://${process.argv[1]}`) {
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
if (!haveImageMagick()) {
console.error(
"Cannot find ImageMagick.\nInstall it first - on Debian or Ubuntu: sudo apt-get install imagemagick",
"Cannot find ImageMagick.\n" +
"On Debian or Ubuntu: sudo apt-get install imagemagick.\n" +
"On Windows, install ImageMagick and make sure magick.exe is on PATH.",
);
process.exit(1);
}
Expand Down
Loading
Loading