@uniweb/build 0.14.28 → 0.14.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/hosts/ci-workflow.js +136 -0
- package/src/hosts/cloudflare-pages.js +265 -4
- package/src/hosts/deploy-utils.js +151 -0
- package/src/hosts/generic-static.js +12 -0
- package/src/hosts/github-pages.js +389 -39
- package/src/hosts/index.js +15 -6
- package/src/hosts/netlify.js +281 -0
- package/src/hosts/s3-cloudfront.js +14 -7
- package/src/hosts/vercel.js +239 -11
|
@@ -17,11 +17,19 @@
|
|
|
17
17
|
* meta-refresh HTML the prerender already emits for `redirect:` /
|
|
18
18
|
* `rewrite:` directives.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Two ways to publish, both supported:
|
|
21
|
+
*
|
|
22
|
+
* - initCi (recommended) — emits a `.github/workflows/…` that runs
|
|
23
|
+
* `uniweb build --host=github-pages` and uploads via the official
|
|
24
|
+
* actions/deploy-pages flow. One-time setup; every push deploys.
|
|
25
|
+
* - deploy — publishes an already-built `dist/` straight to the
|
|
26
|
+
* `gh-pages` branch from your machine. For repos without Actions
|
|
27
|
+
* minutes, or a one-off publish. Uses a detached worktree so your
|
|
28
|
+
* working tree is never touched, and a normal (non-force) commit so
|
|
29
|
+
* the branch history stays intact and revertible.
|
|
30
|
+
*
|
|
31
|
+
* UNIWEB_BASE is decided at workflow scaffold time based on the deploy
|
|
32
|
+
* target (per gotcha #15 for the base-path flow):
|
|
25
33
|
*
|
|
26
34
|
* - With `domain`: bakes `UNIWEB_BASE: /` and emits a CNAME file under
|
|
27
35
|
* `<sitePath>/public/`. GitHub Pages serves the custom domain at
|
|
@@ -34,21 +42,252 @@
|
|
|
34
42
|
* deploy target.
|
|
35
43
|
*/
|
|
36
44
|
|
|
37
|
-
import { writeFile } from 'node:fs/promises'
|
|
38
|
-
import {
|
|
45
|
+
import { writeFile, mkdtemp, rm, cp, readdir } from 'node:fs/promises'
|
|
46
|
+
import { tmpdir } from 'node:os'
|
|
47
|
+
import { join, basename } from 'node:path'
|
|
48
|
+
|
|
49
|
+
import { DeployError, spawnTool } from './deploy-utils.js'
|
|
50
|
+
import { setupSteps, uniwebBuildCommand, pushTrigger, workflowHeader } from './ci-workflow.js'
|
|
51
|
+
|
|
52
|
+
const DEFAULT_PUBLISH_BRANCH = 'gh-pages'
|
|
53
|
+
|
|
54
|
+
// Only used to seed a brand-new publish branch (`git checkout --orphan`
|
|
55
|
+
// requires a name). Always deleted in the deploy's finally block.
|
|
56
|
+
const TEMP_BRANCH = 'uniweb-deploy-tmp'
|
|
57
|
+
|
|
58
|
+
const GIT_INSTALL = [
|
|
59
|
+
'Install git:',
|
|
60
|
+
' macOS: xcode-select --install (or brew install git)',
|
|
61
|
+
' Linux: apt install git / dnf install git',
|
|
62
|
+
' Windows: https://git-scm.com/download/win',
|
|
63
|
+
].join('\n')
|
|
64
|
+
|
|
65
|
+
function git(args, opts) {
|
|
66
|
+
return spawnTool('git', args, { install: GIT_INSTALL, quiet: true, ...opts })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Publish `distDir` to the repo's publish branch (default `gh-pages`).
|
|
71
|
+
*
|
|
72
|
+
* Mechanics, and why each choice:
|
|
73
|
+
* - A detached worktree in a temp dir, never the user's working tree.
|
|
74
|
+
* A deploy must not be able to disturb uncommitted work.
|
|
75
|
+
* - `git rm -rf .` then copy, so the published tree is an exact mirror
|
|
76
|
+
* of dist/ and pages deleted locally actually disappear.
|
|
77
|
+
* - A normal commit + push, never `--force`. History on the publish
|
|
78
|
+
* branch stays intact, so a bad deploy is one `git revert` away.
|
|
79
|
+
*/
|
|
80
|
+
async function deploy({ distDir, deployConfig = {}, env = process.env, log = () => {} }) {
|
|
81
|
+
const branch = deployConfig.branch || DEFAULT_PUBLISH_BRANCH
|
|
82
|
+
const remote = deployConfig.remote || 'origin'
|
|
83
|
+
|
|
84
|
+
// Must be inside a git repo with the named remote, or there's nothing
|
|
85
|
+
// to publish to.
|
|
86
|
+
let repoRoot
|
|
87
|
+
try {
|
|
88
|
+
const { stdout } = await git(['rev-parse', '--show-toplevel'], { env })
|
|
89
|
+
repoRoot = stdout.trim()
|
|
90
|
+
} catch {
|
|
91
|
+
throw new DeployError(
|
|
92
|
+
'Not inside a git repository.',
|
|
93
|
+
{
|
|
94
|
+
hint: [
|
|
95
|
+
'The github-pages deploy publishes to a branch of your repo, so it needs one.',
|
|
96
|
+
'',
|
|
97
|
+
' git init && git remote add origin git@github.com:<user>/<repo>.git',
|
|
98
|
+
'',
|
|
99
|
+
'Or use the CI path instead: `uniweb add ci --host=github-pages`.',
|
|
100
|
+
].join('\n'),
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await git(['remote', 'get-url', remote], { env, cwd: repoRoot })
|
|
107
|
+
} catch {
|
|
108
|
+
throw new DeployError(
|
|
109
|
+
`This repository has no '${remote}' remote.`,
|
|
110
|
+
{
|
|
111
|
+
hint: [
|
|
112
|
+
`Add one, then retry:`,
|
|
113
|
+
` git remote add ${remote} git@github.com:<user>/<repo>.git`,
|
|
114
|
+
].join('\n'),
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const entries = await readdir(distDir).catch(() => [])
|
|
120
|
+
if (!entries.length) {
|
|
121
|
+
throw new DeployError(
|
|
122
|
+
'dist/ is empty — nothing to publish.',
|
|
123
|
+
{ hint: 'Run `uniweb build --host=github-pages` first.' }
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
log(`\n→ Publishing dist/ to the '${branch}' branch of ${remote}`)
|
|
128
|
+
|
|
129
|
+
const worktree = await mkdtemp(join(tmpdir(), 'uniweb-ghpages-'))
|
|
130
|
+
let worktreeAdded = false
|
|
131
|
+
let tempBranchCreated = false
|
|
132
|
+
try {
|
|
133
|
+
// Does the publish branch already exist on the remote?
|
|
134
|
+
let branchExists = true
|
|
135
|
+
try {
|
|
136
|
+
await git(['fetch', remote, branch], { env, cwd: repoRoot })
|
|
137
|
+
} catch {
|
|
138
|
+
branchExists = false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (branchExists) {
|
|
142
|
+
// Detached at the remote tip: we commit on top of it without ever
|
|
143
|
+
// creating a local branch ref, so a failed run leaves nothing to
|
|
144
|
+
// clean up and the next attempt isn't blocked by a stale branch.
|
|
145
|
+
await git(['worktree', 'add', '--detach', worktree, `${remote}/${branch}`], { env, cwd: repoRoot })
|
|
146
|
+
worktreeAdded = true
|
|
147
|
+
} else {
|
|
148
|
+
log(` '${branch}' does not exist yet — creating it.`)
|
|
149
|
+
await git(['worktree', 'add', '--detach', worktree], { env, cwd: repoRoot })
|
|
150
|
+
worktreeAdded = true
|
|
151
|
+
// `--orphan` is the only way to start a history with no parent, and
|
|
152
|
+
// it insists on a branch name. Clear any leftover from a crashed
|
|
153
|
+
// run, then delete ours in the finally block.
|
|
154
|
+
await git(['branch', '-D', TEMP_BRANCH], { env, cwd: repoRoot }).catch(() => {})
|
|
155
|
+
await git(['checkout', '--orphan', TEMP_BRANCH], { env, cwd: worktree })
|
|
156
|
+
tempBranchCreated = true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Exact mirror: drop everything tracked, then lay down dist/. Without
|
|
160
|
+
// this, pages deleted locally would linger on the published site.
|
|
161
|
+
await git(['rm', '-rf', '--quiet', '.'], { env, cwd: worktree }).catch(() => {})
|
|
162
|
+
|
|
163
|
+
await cp(distDir, worktree, { recursive: true })
|
|
164
|
+
// Belt and braces: postBuild already wrote one, but a dist/ built for
|
|
165
|
+
// a different host wouldn't have it, and without it GH Pages eats
|
|
166
|
+
// every `_`-prefixed path.
|
|
167
|
+
await writeFile(join(worktree, '.nojekyll'), '')
|
|
168
|
+
|
|
169
|
+
await git(['add', '-A'], { env, cwd: worktree })
|
|
170
|
+
|
|
171
|
+
// Nothing changed → don't create an empty commit.
|
|
172
|
+
try {
|
|
173
|
+
await git(['diff', '--cached', '--quiet'], { env, cwd: worktree })
|
|
174
|
+
log('\n✓ Already up to date — nothing to publish.')
|
|
175
|
+
return { url: null, unchanged: true }
|
|
176
|
+
} catch {
|
|
177
|
+
// Non-zero exit from `diff --quiet` means there ARE staged changes.
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const message = deployConfig.message || `deploy: site build ${new Date().toISOString()}`
|
|
181
|
+
await git(['commit', '-m', message], { env, cwd: worktree })
|
|
182
|
+
const { stdout: head } = await git(['rev-parse', 'HEAD'], { env, cwd: worktree })
|
|
183
|
+
const sha = head.trim()
|
|
184
|
+
|
|
185
|
+
// Push the commit BY SHA, from the repo root. Two reasons, both bugs
|
|
186
|
+
// found the hard way: the root is where `remote` is configured (a
|
|
187
|
+
// relative remote path like `../origin.git` resolves only there), and
|
|
188
|
+
// pushing `HEAD` from the root would push the root's HEAD — the
|
|
189
|
+
// developer's working branch — not what the worktree just built.
|
|
190
|
+
await git(['push', remote, `${sha}:refs/heads/${branch}`], { env, cwd: repoRoot, log })
|
|
191
|
+
|
|
192
|
+
const url = await inferPagesUrl(repoRoot, remote, env)
|
|
193
|
+
log('\n✓ Published.')
|
|
194
|
+
if (url) {
|
|
195
|
+
log(` ${url}`)
|
|
196
|
+
log(' (Settings → Pages → Source must be set to "Deploy from a branch")')
|
|
197
|
+
}
|
|
198
|
+
return { url }
|
|
199
|
+
} finally {
|
|
200
|
+
// Order matters: a branch checked out in a worktree can't be deleted.
|
|
201
|
+
if (worktreeAdded) {
|
|
202
|
+
await git(['worktree', 'remove', '--force', worktree], { env, cwd: repoRoot }).catch(() => {})
|
|
203
|
+
}
|
|
204
|
+
if (tempBranchCreated) {
|
|
205
|
+
await git(['branch', '-D', TEMP_BRANCH], { env, cwd: repoRoot }).catch(() => {})
|
|
206
|
+
}
|
|
207
|
+
await rm(worktree, { recursive: true, force: true }).catch(() => {})
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Derive the public Pages URL from the remote. Best-effort — a miss only
|
|
213
|
+
* costs the echoed link.
|
|
214
|
+
*/
|
|
215
|
+
async function inferPagesUrl(repoRoot, remote, env) {
|
|
216
|
+
try {
|
|
217
|
+
const { stdout } = await git(['remote', 'get-url', remote], { env, cwd: repoRoot })
|
|
218
|
+
return pagesUrlFromRemote(stdout.trim())
|
|
219
|
+
} catch {
|
|
220
|
+
return null
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Map a GitHub remote URL to the site's public Pages URL.
|
|
226
|
+
*
|
|
227
|
+
* Two repo shapes, served differently: `<user>.github.io` is the profile
|
|
228
|
+
* repo and is served at the domain root; every other repo is served under
|
|
229
|
+
* `/<repo>/`. Non-GitHub remotes yield null — we don't guess.
|
|
230
|
+
*
|
|
231
|
+
* Exported for tests.
|
|
232
|
+
*/
|
|
233
|
+
export function pagesUrlFromRemote(remoteUrl) {
|
|
234
|
+
const match = String(remoteUrl).match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/i)
|
|
235
|
+
if (!match) return null
|
|
236
|
+
const [, owner, repo] = match
|
|
237
|
+
if (/\.github\.io$/i.test(repo)) return `https://${repo.toLowerCase()}/`
|
|
238
|
+
return `https://${owner.toLowerCase()}.github.io/${repo}/`
|
|
239
|
+
}
|
|
39
240
|
|
|
40
241
|
const adapter = {
|
|
41
242
|
name: 'github-pages',
|
|
42
243
|
|
|
244
|
+
display: {
|
|
245
|
+
|
|
246
|
+
order: 10,
|
|
247
|
+
|
|
248
|
+
pushWith: 'git — commits dist/ to the gh-pages branch',
|
|
249
|
+
title: 'GitHub Pages',
|
|
250
|
+
qualifier: 'free, CI on push',
|
|
251
|
+
summary: 'Free static hosting from your repo, with a custom domain over HTTPS. Set up a workflow once and every push deploys.',
|
|
252
|
+
ci: true,
|
|
253
|
+
// Can also publish FOUNDATIONS at permanent versioned URLs
|
|
254
|
+
// (`initCi({ target: 'foundation' })`). No other adapter implements
|
|
255
|
+
// that today, and the CLI gates on this flag rather than silently
|
|
256
|
+
// scaffolding a site workflow for a foundation request.
|
|
257
|
+
foundationCi: true,
|
|
258
|
+
previews: false,
|
|
259
|
+
},
|
|
260
|
+
|
|
43
261
|
async postBuild({ distDir, onProgress = () => {} }) {
|
|
44
262
|
await writeFile(join(distDir, '.nojekyll'), '')
|
|
45
263
|
onProgress('Wrote .nojekyll (opts out of Jekyll on GitHub Pages)')
|
|
46
264
|
},
|
|
47
265
|
|
|
266
|
+
deploy,
|
|
267
|
+
|
|
48
268
|
// `pnpmVersion` is the pnpm major for the generated CI. The CLI passes the
|
|
49
269
|
// authoritative value (versions.js::PNPM_VERSION); the default here is only a
|
|
50
270
|
// fallback for direct/test callers.
|
|
51
|
-
|
|
271
|
+
//
|
|
272
|
+
// `target` picks what gets published:
|
|
273
|
+
// 'site' — the site's dist/ at the Pages root (the common case)
|
|
274
|
+
// 'foundation' — built foundations at versioned, permanent URLs
|
|
275
|
+
// (foundations/<name>/<version>/entry.js). The free
|
|
276
|
+
// distribution path for a foundation product; sites
|
|
277
|
+
// reference the URL from site.yml.
|
|
278
|
+
async initCi({
|
|
279
|
+
site,
|
|
280
|
+
foundations = [],
|
|
281
|
+
target = 'site',
|
|
282
|
+
packageManager = 'pnpm',
|
|
283
|
+
nodeVersion = '20',
|
|
284
|
+
pnpmVersion = '11',
|
|
285
|
+
domain = null,
|
|
286
|
+
}) {
|
|
287
|
+
if (target === 'foundation') {
|
|
288
|
+
return initFoundationCi({ foundations, packageManager, nodeVersion, pnpmVersion })
|
|
289
|
+
}
|
|
290
|
+
|
|
52
291
|
const sitePath = site.path
|
|
53
292
|
const workflowPath = `.github/workflows/deploy-github-pages.yml`
|
|
54
293
|
const yaml = renderWorkflow({ sitePath, packageManager, nodeVersion, pnpmVersion, domain })
|
|
@@ -94,26 +333,144 @@ const adapter = {
|
|
|
94
333
|
},
|
|
95
334
|
}
|
|
96
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Foundation distribution workflow.
|
|
338
|
+
*
|
|
339
|
+
* Publishes each built foundation at `foundations/<name>/<version>/` on
|
|
340
|
+
* the gh-pages branch. Versions accumulate: a bumped version creates a
|
|
341
|
+
* new directory next to the old ones, so every URL a site ever pinned
|
|
342
|
+
* keeps resolving. This is the free alternative to the Uniweb catalog —
|
|
343
|
+
* permanent stable URLs and GitHub's CDN, without propagation or
|
|
344
|
+
* license gating.
|
|
345
|
+
*/
|
|
346
|
+
function initFoundationCi({ foundations, packageManager, nodeVersion, pnpmVersion }) {
|
|
347
|
+
if (!foundations.length) {
|
|
348
|
+
throw new Error('No foundation found to publish. Add one with `uniweb add foundation` first.')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const setup = setupSteps({ packageManager, nodeVersion, pnpmVersion })
|
|
352
|
+
const buildCmd = packageManager === 'pnpm' ? 'pnpm build' : 'npm run build'
|
|
353
|
+
|
|
354
|
+
// `<public-name>:<dir>` pairs, resolved by the CLI and baked in rather
|
|
355
|
+
// than derived with `basename` at CI time. The name becomes part of a
|
|
356
|
+
// permanent URL that sites pin, so it must not drift when a directory
|
|
357
|
+
// is renamed — and the CLI already printed these exact names in its
|
|
358
|
+
// next-steps output.
|
|
359
|
+
const pairs = foundations.map(f => `${f.name}:${f.path}`)
|
|
360
|
+
|
|
361
|
+
const content = `${workflowHeader({
|
|
362
|
+
title: 'Publish foundations to GitHub Pages',
|
|
363
|
+
command: 'uniweb add ci --host=github-pages --foundation',
|
|
364
|
+
notes: [
|
|
365
|
+
'Each foundation is published at a permanent versioned URL:',
|
|
366
|
+
'',
|
|
367
|
+
' https://<user>.github.io/<repo>/foundations/<name>/<version>/entry.js',
|
|
368
|
+
'',
|
|
369
|
+
'Versions accumulate — bumping package.json version creates a new',
|
|
370
|
+
'directory alongside the old ones, so URLs already pinned by a site',
|
|
371
|
+
'keep resolving forever. Reference one from a site\'s site.yml:',
|
|
372
|
+
'',
|
|
373
|
+
' foundation: \'https://<user>.github.io/<repo>/foundations/<name>/<version>/entry.js\'',
|
|
374
|
+
],
|
|
375
|
+
})}
|
|
376
|
+
|
|
377
|
+
name: Publish Foundations
|
|
378
|
+
|
|
379
|
+
${pushTrigger()}
|
|
380
|
+
|
|
381
|
+
permissions:
|
|
382
|
+
contents: write # writes to the gh-pages branch
|
|
383
|
+
|
|
384
|
+
concurrency:
|
|
385
|
+
group: publish-foundations
|
|
386
|
+
cancel-in-progress: false
|
|
387
|
+
|
|
388
|
+
jobs:
|
|
389
|
+
build-and-publish:
|
|
390
|
+
runs-on: ubuntu-latest
|
|
391
|
+
steps:
|
|
392
|
+
${setup}
|
|
393
|
+
- name: Build and stage foundations
|
|
394
|
+
shell: bash
|
|
395
|
+
run: |
|
|
396
|
+
set -e
|
|
397
|
+
mkdir -p _staging/foundations
|
|
398
|
+
for pair in ${pairs.map(p => JSON.stringify(p)).join(' ')}; do
|
|
399
|
+
name="\${pair%%:*}"
|
|
400
|
+
dir="\${pair#*:}"
|
|
401
|
+
version=$(jq -r '.version' "$dir/package.json")
|
|
402
|
+
if [ -z "$version" ] || [ "$version" = "null" ]; then
|
|
403
|
+
echo "::warning::skipping $name — no version in package.json"
|
|
404
|
+
continue
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
echo "Building $name@$version"
|
|
408
|
+
(cd "$dir" && ${buildCmd})
|
|
409
|
+
|
|
410
|
+
if [ ! -d "$dir/dist" ]; then
|
|
411
|
+
echo "::error::build produced no dist/ for $name"
|
|
412
|
+
exit 1
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
mkdir -p "_staging/foundations/$name/$version"
|
|
416
|
+
cp -R "$dir/dist/." "_staging/foundations/$name/$version/"
|
|
417
|
+
echo "Staged $name@$version"
|
|
418
|
+
done
|
|
419
|
+
|
|
420
|
+
- name: Layer onto gh-pages
|
|
421
|
+
shell: bash
|
|
422
|
+
run: |
|
|
423
|
+
set -e
|
|
424
|
+
git config user.name "github-actions[bot]"
|
|
425
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
426
|
+
|
|
427
|
+
if git fetch origin gh-pages 2>/dev/null; then
|
|
428
|
+
git worktree add _gh-pages gh-pages
|
|
429
|
+
else
|
|
430
|
+
git worktree add --detach _gh-pages
|
|
431
|
+
(cd _gh-pages && git checkout --orphan gh-pages && git rm -rf . 2>/dev/null || true)
|
|
432
|
+
fi
|
|
433
|
+
|
|
434
|
+
# Layer, don't replace: older versions must survive so sites
|
|
435
|
+
# pinning them keep working.
|
|
436
|
+
mkdir -p _gh-pages/foundations
|
|
437
|
+
cp -R _staging/foundations/. _gh-pages/foundations/
|
|
438
|
+
|
|
439
|
+
# Without this, Jekyll silently 404s every _-prefixed chunk the
|
|
440
|
+
# foundation build emits.
|
|
441
|
+
touch _gh-pages/.nojekyll
|
|
442
|
+
|
|
443
|
+
cd _gh-pages
|
|
444
|
+
git add -A
|
|
445
|
+
if git diff --cached --quiet; then
|
|
446
|
+
echo "No foundation changes to publish."
|
|
447
|
+
exit 0
|
|
448
|
+
fi
|
|
449
|
+
git commit -m "publish: foundations from \${GITHUB_SHA::7}"
|
|
450
|
+
git push origin gh-pages
|
|
451
|
+
`
|
|
452
|
+
|
|
453
|
+
const names = foundations.map(f => f.name)
|
|
454
|
+
return {
|
|
455
|
+
files: [{ path: '.github/workflows/publish-foundations.yml', content }],
|
|
456
|
+
postInstructions: [
|
|
457
|
+
'Commit and push .github/workflows/publish-foundations.yml.',
|
|
458
|
+
'On GitHub: Settings → Pages → Source: "Deploy from a branch" → gh-pages.',
|
|
459
|
+
'',
|
|
460
|
+
'After the first run, each foundation is served at:',
|
|
461
|
+
...names.map(n => ` https://<user>.github.io/<repo>/foundations/${n}/<version>/entry.js`),
|
|
462
|
+
'',
|
|
463
|
+
'Bump a foundation\'s package.json version to publish a new one; older',
|
|
464
|
+
'versions stay reachable at their original URLs.',
|
|
465
|
+
],
|
|
466
|
+
targetConfig: null,
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
97
470
|
function renderWorkflow({ sitePath, packageManager, nodeVersion, pnpmVersion, domain }) {
|
|
98
471
|
const isPnpm = packageManager === 'pnpm'
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
with:
|
|
102
|
-
version: ${pnpmVersion}
|
|
103
|
-
- uses: actions/setup-node@v4
|
|
104
|
-
with:
|
|
105
|
-
node-version: '${nodeVersion}'
|
|
106
|
-
cache: pnpm
|
|
107
|
-
- run: pnpm install --frozen-lockfile`
|
|
108
|
-
: ` - uses: actions/setup-node@v4
|
|
109
|
-
with:
|
|
110
|
-
node-version: '${nodeVersion}'
|
|
111
|
-
cache: npm
|
|
112
|
-
- run: npm ci`
|
|
113
|
-
|
|
114
|
-
const buildCmd = isPnpm
|
|
115
|
-
? 'pnpm exec uniweb build --host=github-pages'
|
|
116
|
-
: 'npx uniweb build --host=github-pages'
|
|
472
|
+
const setup = setupSteps({ packageManager, nodeVersion, pnpmVersion })
|
|
473
|
+
const buildCmd = uniwebBuildCommand({ packageManager, host: 'github-pages' })
|
|
117
474
|
|
|
118
475
|
// Two shapes:
|
|
119
476
|
// - Custom domain: UNIWEB_BASE is hardcoded to '/' (GH Pages serves
|
|
@@ -135,20 +492,14 @@ function renderWorkflow({ sitePath, packageManager, nodeVersion, pnpmVersion, do
|
|
|
135
492
|
fi
|
|
136
493
|
- run: ${buildCmd}`
|
|
137
494
|
|
|
138
|
-
return
|
|
139
|
-
|
|
495
|
+
return `${workflowHeader({
|
|
496
|
+
title: 'Deploy to GitHub Pages',
|
|
497
|
+
command: 'uniweb add ci --host=github-pages',
|
|
498
|
+
})}
|
|
140
499
|
|
|
141
500
|
name: Deploy to GitHub Pages
|
|
142
501
|
|
|
143
|
-
|
|
144
|
-
push:
|
|
145
|
-
# Both names are listed so the workflow fires whether the repo
|
|
146
|
-
# uses 'main' (GitHub's current default) or 'master' (older repos
|
|
147
|
-
# and any not migrated). GHA only triggers on a branch that exists,
|
|
148
|
-
# so the unused name is a harmless no-op. Users on a different
|
|
149
|
-
# default (trunk, develop, release) edit this list directly.
|
|
150
|
-
branches: [main, master]
|
|
151
|
-
workflow_dispatch:
|
|
502
|
+
${pushTrigger()}
|
|
152
503
|
|
|
153
504
|
permissions:
|
|
154
505
|
contents: read
|
|
@@ -166,8 +517,7 @@ jobs:
|
|
|
166
517
|
name: github-pages
|
|
167
518
|
url: \${{ steps.deployment.outputs.page_url }}
|
|
168
519
|
steps:
|
|
169
|
-
|
|
170
|
-
${setupSteps}
|
|
520
|
+
${setup}
|
|
171
521
|
${baseStep}
|
|
172
522
|
- uses: actions/configure-pages@v5
|
|
173
523
|
- uses: actions/upload-pages-artifact@v3
|
package/src/hosts/index.js
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
import cloudflarePages from './cloudflare-pages.js'
|
|
44
44
|
import githubPages from './github-pages.js'
|
|
45
45
|
import genericStatic from './generic-static.js'
|
|
46
|
+
import netlify from './netlify.js'
|
|
46
47
|
import s3Cloudfront from './s3-cloudfront.js'
|
|
47
48
|
import vercel from './vercel.js'
|
|
48
49
|
|
|
@@ -50,6 +51,7 @@ const builtins = new Map([
|
|
|
50
51
|
[cloudflarePages.name, cloudflarePages],
|
|
51
52
|
[githubPages.name, githubPages],
|
|
52
53
|
[genericStatic.name, genericStatic],
|
|
54
|
+
[netlify.name, netlify],
|
|
53
55
|
[s3Cloudfront.name, s3Cloudfront],
|
|
54
56
|
[vercel.name, vercel],
|
|
55
57
|
])
|
|
@@ -57,18 +59,25 @@ const builtins = new Map([
|
|
|
57
59
|
/**
|
|
58
60
|
* Aliases mapping a user-facing host name to a canonical adapter that
|
|
59
61
|
* already implements the right behavior. Aliases exist when two hosts
|
|
60
|
-
* share an artifact contract
|
|
61
|
-
*
|
|
62
|
-
* multiple discoverable names.
|
|
62
|
+
* share an artifact contract — one tested code path, multiple
|
|
63
|
+
* discoverable names.
|
|
63
64
|
*
|
|
64
65
|
* The returned adapter's `name` is rewritten to the *requested* name,
|
|
65
66
|
* so the deploy manifest, dry-run output, and lastDeploy entry record
|
|
66
67
|
* what the user picked. Adapters that need to *behave* differently per
|
|
67
68
|
* name should become canonical entries in `builtins`, not aliases.
|
|
69
|
+
*
|
|
70
|
+
* Netlify used to alias cloudflare-pages (they share the `_redirects`
|
|
71
|
+
* contract, so one postBuild covers both). It was promoted to a
|
|
72
|
+
* canonical adapter when deploy hooks landed: `netlify deploy` and
|
|
73
|
+
* `wrangler pages deploy` are different tools with different auth, so
|
|
74
|
+
* the behavior genuinely diverges. `netlify.js` still imports
|
|
75
|
+
* cloudflare-pages' `emitRedirectsFile` — the shared part stays shared.
|
|
76
|
+
*
|
|
77
|
+
* Empty today. Kept because the lookup path below is the documented
|
|
78
|
+
* extension point and re-adding an alias should not require rewiring.
|
|
68
79
|
*/
|
|
69
|
-
const aliases = new Map(
|
|
70
|
-
['netlify', 'cloudflare-pages'],
|
|
71
|
-
])
|
|
80
|
+
const aliases = new Map()
|
|
72
81
|
|
|
73
82
|
/**
|
|
74
83
|
* Look up an adapter by name. Throws with the list of known names if
|