Skip to content

Commit 9bc1c07

Browse files
committed
chore: adopt structure and conventions of angular-cli-ghpages
- TypeScript sources in src/, compiled to src/dist, published from there - vitest, prettier config, editorconfig - ng add schematic: swaps the build target's builder and enables flat output - CI builds a fresh Angular 22 app with SSG and checks the flat output - npm publish via Trusted Publishers with dry-run and environment approval - LICENSE, README and CLAUDE.md in the style of angular-cli-ghpages
1 parent be6ced2 commit 9bc1c07

31 files changed

Lines changed: 3933 additions & 2088 deletions

‎.editorconfig‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Editor configuration, see http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
max_line_length = off
13+
trim_trailing_whitespace = false

‎.github/workflows/main.yml‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Build and test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
jobs:
11+
build-and-test:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: 24
20+
cache: npm
21+
cache-dependency-path: src/package-lock.json
22+
23+
- name: Build and test
24+
working-directory: src
25+
run: |
26+
npm ci
27+
npm run build
28+
npm run test
29+
30+
- name: Upload build artifact
31+
uses: actions/upload-artifact@v4
32+
with:
33+
name: dist
34+
path: src/dist
35+
retention-days: 1
36+
37+
# Builds a fresh Angular app with SSG and checks the flat output end to end:
38+
# ng add swaps the builder, ng build writes about.html next to index.html.
39+
# The project is created OUTSIDE the repo so it gets its own node_modules.
40+
test-angular-versions:
41+
needs: build-and-test
42+
runs-on: ubuntu-latest
43+
env:
44+
NG_CLI_ANALYTICS: 'false'
45+
46+
steps:
47+
- uses: actions/checkout@v4
48+
49+
- uses: actions/setup-node@v4
50+
with:
51+
node-version: 24
52+
cache: npm
53+
cache-dependency-path: src/package-lock.json
54+
55+
- name: Download build artifact
56+
uses: actions/download-artifact@v4
57+
with:
58+
name: dist
59+
path: src/dist
60+
61+
- name: Pack @angular-schule/flat-prerender
62+
working-directory: src/dist
63+
run: npm pack --pack-destination /tmp
64+
65+
- name: Test Angular 22
66+
run: |
67+
cd /tmp
68+
npx @angular/cli@22 new test-app-22 --defaults --ssr --skip-git
69+
cd test-app-22
70+
npm install --save-dev /tmp/angular-schule-flat-prerender-*.tgz
71+
npx ng add @angular-schule/flat-prerender --skip-confirmation
72+
# ng new --ssr uses outputMode "server", which flat output refuses
73+
if npx ng build; then echo "Expected the build to fail with outputMode server"; exit 1; fi
74+
node -e '
75+
const fs = require("fs");
76+
const a = JSON.parse(fs.readFileSync("angular.json", "utf8"));
77+
a.projects["test-app-22"].architect.build.options.outputMode = "static";
78+
fs.writeFileSync("angular.json", JSON.stringify(a, null, 2));
79+
'
80+
cat > src/app/app.routes.ts <<'EOF'
81+
import { Routes } from '@angular/router';
82+
import { App } from './app';
83+
84+
export const routes: Routes = [
85+
{ path: '', component: App },
86+
{ path: 'about', component: App },
87+
{ path: 'blog/article', component: App }
88+
];
89+
EOF
90+
npx ng build
91+
cd dist/test-app-22/browser
92+
find . -name '*.html' | sort
93+
test -f index.html
94+
test -f about.html
95+
test -f blog/article.html
96+
test ! -e about/index.html
97+
test ! -e blog/article/index.html
98+
echo "Angular 22: flat prerender output successful"

‎.github/workflows/npm-publish.yml‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Publish to npm
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dry_run:
7+
type: boolean
8+
default: true
9+
description: 'Dry-run (test without publishing)'
10+
tag:
11+
type: choice
12+
default: 'latest'
13+
options:
14+
- latest
15+
- next
16+
description: 'npm tag (use "next" for pre-release/RC versions)'
17+
18+
# Required for OIDC token exchange with npm (Trusted Publishers)
19+
permissions:
20+
contents: read
21+
id-token: write
22+
23+
jobs:
24+
publish:
25+
runs-on: ubuntu-latest
26+
environment: npm-publish
27+
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: 24
34+
registry-url: 'https://registry.npmjs.org'
35+
36+
- name: Build package
37+
working-directory: src
38+
run: |
39+
npm ci
40+
npm run build
41+
npm test
42+
43+
- name: Show version info
44+
working-directory: src/dist
45+
run: |
46+
VERSION=$(node -p "require('./package.json').version")
47+
echo "📦 Package: @angular-schule/flat-prerender"
48+
echo "📌 Version: $VERSION"
49+
echo "🏷️ Tag: ${{ inputs.tag }}"
50+
echo ""
51+
if [ "${{ inputs.dry_run }}" = "true" ]; then
52+
echo "🧪 Mode: DRY-RUN (no actual publish)"
53+
else
54+
echo "🚀 Mode: PUBLISH FOR REAL"
55+
fi
56+
57+
- name: Publish to npm (dry-run)
58+
if: ${{ inputs.dry_run }}
59+
working-directory: src/dist
60+
run: |
61+
echo ""
62+
echo "==============="
63+
npm publish --provenance --tag ${{ inputs.tag }} --dry-run
64+
echo "==============="
65+
echo ""
66+
echo "✅ Dry-run successful! Package is ready to publish."
67+
echo " To publish for real, run this workflow again with dry_run unchecked."
68+
69+
- name: Publish to npm
70+
if: ${{ !inputs.dry_run }}
71+
working-directory: src/dist
72+
run: |
73+
echo ""
74+
echo "==============="
75+
npm publish --provenance --tag ${{ inputs.tag }}
76+
echo "==============="
77+
echo ""
78+
echo "✅ Published successfully with provenance!"
79+
echo " https://www.npmjs.com/package/@angular-schule/flat-prerender"

‎.gitignore‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
node_modules/
1+
node_modules
2+
dist
3+
coverage
4+
.DS_Store
5+
.idea
26
*.tgz

‎CLAUDE.md‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance when working with code in this repository.
4+
5+
## Overview
6+
7+
`@angular-schule/flat-prerender` is an Angular CLI builder that wraps `@angular/build:application`. With `prerenderOutputStyle: "flat"`, prerendered routes are written as `foo.html` instead of `foo/index.html`, so static hosts serve `/foo` without a trailing slash redirect. It implements the option proposed in [angular/angular-cli#29173](https://github.com/angular/angular-cli/issues/29173). Structure and conventions follow [angular-cli-ghpages](https://github.com/angular-schule/angular-cli-ghpages).
8+
9+
## Development Commands
10+
11+
All development commands must be run from the `src` directory:
12+
13+
```bash
14+
cd src
15+
```
16+
17+
**IMPORTANT:** The `src/.npmrc` file contains `ignore-scripts=false` to override global npm settings. **DO NOT DELETE OR MODIFY this file** - it's required for build scripts to run.
18+
19+
### Build
20+
```bash
21+
npm run build
22+
```
23+
Build process: `prebuild` (clean) → `build` (tsc) → `postbuild` (copy metadata, schema, README and LICENSE to dist/).
24+
25+
### Schema
26+
```bash
27+
npm run build:schema
28+
```
29+
`application/schema.json` is generated: the schema of `@angular/build:application` from the installed version plus `prerenderOutputStyle`. Regenerate it after updating `@angular/build` and commit the result. `npm test` fails if it is out of sync.
30+
31+
### Test
32+
```bash
33+
npm test
34+
```
35+
36+
### Local Development
37+
38+
For testing changes locally with an Angular project:
39+
40+
1. Build and pack from `src/dist`:
41+
```bash
42+
cd src
43+
npm run build
44+
cd dist
45+
npm pack
46+
```
47+
48+
2. In your Angular test project:
49+
```bash
50+
npm install --save-dev /path/to/angular-schule-flat-prerender-X.X.X.tgz
51+
ng add @angular-schule/flat-prerender
52+
ng build
53+
```
54+
55+
### Publishing
56+
57+
Publishing uses [npm Trusted Publishers](https://docs.npmjs.com/trusted-publishers) with OIDC – no tokens stored in CI!
58+
59+
1. Go to **Actions** → **Publish to npm**
60+
2. Click **Run workflow** → select branch
61+
3. Leave "Dry-run" checked to test, or uncheck for real publish
62+
4. Wait for approval (environment `npm-publish`)
63+
64+
Publishes with provenance attestation for supply chain security.
65+
66+
For pre-release versions, after publishing:
67+
```bash
68+
npm dist-tag add @angular-schule/flat-prerender@X.X.X-rc.X next
69+
```
70+
71+
## Architecture
72+
73+
1. **Builder** (`src/application/`):
74+
- `builder.ts` - Angular builder entry point, called by `ng build`. Strips `prerenderOutputStyle`, refuses flat output when a server is shipped, then delegates to `buildApplication`.
75+
- `flat-output.ts` - Wraps the internal `prerenderPages()` of `@angular/build`. `execute-post-bundle.js` reads it from the module's exports object at call time, so replacing the export takes effect for regular and localized builds.
76+
- `schema.json` - Generated, see above.
77+
78+
2. **Schematic** (`src/ng-add.ts`):
79+
- Implements `ng add @angular-schule/flat-prerender`
80+
- Swaps the build target's builder and sets `prerenderOutputStyle: "flat"`
81+
82+
### Internal API
83+
84+
`prerenderPages()` in `@angular/build/src/utils/server-rendering/prerender.js` is not public. The builder fails loudly if the module or the export is missing, and `flat-output.spec.ts` checks the call site in `execute-post-bundle.js`. After every `@angular/build` update: run the tests and the CI job that builds a fresh Angular app.

‎LICENSE‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2026 Angular.Schule (by Johannes Hoppe)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
the Software, and to permit persons to whom the Software is furnished to do so,
10+
subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

0 commit comments

Comments
 (0)