@moku-labs/ci 1.0.0
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/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/release.mjs +2497 -0
- package/examples/app/ci.yml +28 -0
- package/examples/package/ci.yml +22 -0
- package/examples/package/publish.local-publish.yml +84 -0
- package/examples/package/publish.yml +36 -0
- package/package.json +54 -0
- package/rulesets/main.json +33 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Thin caller for a Layer-3 moku app: validate on every PR, deploy to Cloudflare on push to main.
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
# Cancel superseded runs on the same ref. Safe here — app-deploy.yml sets no group of its own.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
ci:
|
|
19
|
+
uses: moku-labs/ci/.github/workflows/app-deploy.yml@v1
|
|
20
|
+
with:
|
|
21
|
+
test_script: test:coverage
|
|
22
|
+
build_worker_script: build:worker # client build first — the worker dry-run reads dist/client
|
|
23
|
+
migrate_script: migrate:remote # wrangler d1 migrations apply <db> --remote
|
|
24
|
+
deploy_script: deploy # wrangler deploy
|
|
25
|
+
# Reusable workflows do NOT inherit secrets — pass each one explicitly.
|
|
26
|
+
secrets:
|
|
27
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
28
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Thin caller. All logic lives in moku-labs/ci, pinned to the moving @v1 major tag.
|
|
4
|
+
# Required status checks land as "ci / lint", "ci / types", "ci / test", "ci / build"
|
|
5
|
+
# (GitHub prefixes a reusable workflow's jobs with the CALLER's job id) — see rulesets/main.json.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
# No `workflow_call:` needed — publish.yml reaches the same checks through
|
|
11
|
+
# package-release.yml, which calls package-ci.yml itself.
|
|
12
|
+
|
|
13
|
+
# NO concurrency block here on purpose: package-ci.yml already scopes its group by
|
|
14
|
+
# github.workflow (= this workflow's name), and a caller group with the same value would
|
|
15
|
+
# deadlock against its own child.
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
ci:
|
|
22
|
+
uses: moku-labs/ci/.github/workflows/package-ci.yml@v1
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# FALLBACK variant of publish.yml — use it if npm rejects a publish executed inside a
|
|
4
|
+
# reusable workflow owned by another repo (see README "Known risk"). Copy this over
|
|
5
|
+
# publish.yml (keep the filename — it is the Trusted Publishing contract).
|
|
6
|
+
#
|
|
7
|
+
# The central workflow still owns check → release → package. Only the credentialed
|
|
8
|
+
# `npm publish` runs HERE, in this repo's own publish.yml, so the OIDC claim's
|
|
9
|
+
# job_workflow_ref points at <owner>/<repo>/.github/workflows/publish.yml.
|
|
10
|
+
on:
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
inputs:
|
|
13
|
+
release_type:
|
|
14
|
+
description: Semver bump for this release
|
|
15
|
+
type: choice
|
|
16
|
+
required: true
|
|
17
|
+
options: [patch, minor, major, prerelease]
|
|
18
|
+
release:
|
|
19
|
+
types: [published]
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: publish-${{ github.ref }}
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
release:
|
|
30
|
+
uses: moku-labs/ci/.github/workflows/package-release.yml@v1
|
|
31
|
+
permissions:
|
|
32
|
+
contents: write # release job only; no id-token — nothing central publishes now
|
|
33
|
+
with:
|
|
34
|
+
release_type: ${{ inputs.release_type }}
|
|
35
|
+
publish: false # the central publish job is skipped; the local one below takes over
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
needs: release
|
|
39
|
+
if: |
|
|
40
|
+
always() && needs.release.outputs.artifact_name != '' &&
|
|
41
|
+
(
|
|
42
|
+
(github.event_name == 'workflow_dispatch' && needs.release.outputs.tag != '') ||
|
|
43
|
+
github.event_name == 'release'
|
|
44
|
+
)
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write # ONLY this job — tokenless Trusted Publishing via OIDC
|
|
48
|
+
contents: read
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
|
51
|
+
with:
|
|
52
|
+
ref: ${{ github.event_name == 'workflow_dispatch' && needs.release.outputs.tag || github.ref }}
|
|
53
|
+
persist-credentials: false
|
|
54
|
+
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
|
55
|
+
with:
|
|
56
|
+
node-version: 24
|
|
57
|
+
registry-url: https://registry.npmjs.org
|
|
58
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
59
|
+
with:
|
|
60
|
+
name: ${{ needs.release.outputs.artifact_name }}
|
|
61
|
+
path: dist-pack
|
|
62
|
+
- name: Assert npm floor for Trusted Publishing (fail closed)
|
|
63
|
+
run: |
|
|
64
|
+
set -euo pipefail
|
|
65
|
+
# Rely on the npm bundled with Node 24 — never `npm install -g npm@latest` next to
|
|
66
|
+
# a publish credential. Assert the floor instead.
|
|
67
|
+
have="$(npm --version)"; need="11.5.1"
|
|
68
|
+
[ "$(printf '%s\n%s\n' "$need" "$have" | sort -V | head -1)" = "$need" ] \
|
|
69
|
+
|| { echo "npm $have < $need (Trusted Publishing floor)"; exit 1; }
|
|
70
|
+
- name: Verify the ref matches package.json, then publish
|
|
71
|
+
env:
|
|
72
|
+
EVENT: ${{ github.event_name }}
|
|
73
|
+
DISPATCH_VERSION: ${{ needs.release.outputs.version }}
|
|
74
|
+
REF_NAME: ${{ github.ref_name }}
|
|
75
|
+
run: |
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
pkg="$(node -p "require('./package.json').version")"
|
|
78
|
+
if [ "$EVENT" = "workflow_dispatch" ]; then want="$DISPATCH_VERSION"; else want="${REF_NAME#v}"; fi
|
|
79
|
+
[ -n "$want" ] || { echo "empty version/ref — refusing to publish"; exit 1; }
|
|
80
|
+
[ "$pkg" = "$want" ] || { echo "package.json $pkg != ref $want"; exit 1; }
|
|
81
|
+
# Prereleases go to dist-tag 'next' so they never clobber 'latest'.
|
|
82
|
+
case "$pkg" in *-*) tag=next ;; *) tag=latest ;; esac
|
|
83
|
+
# Leading "./" is REQUIRED: a bare path is parsed by npm as an "owner/repo" spec.
|
|
84
|
+
npm publish ./dist-pack/*.tgz --tag "$tag" --access public
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Thin caller. The FILENAME is a contract: npm Trusted Publishing is registered against
|
|
4
|
+
# "publish.yml" for this repo — renaming it breaks tokenless publishing.
|
|
5
|
+
on:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
release_type:
|
|
9
|
+
description: Semver bump for this release
|
|
10
|
+
type: choice
|
|
11
|
+
required: true
|
|
12
|
+
options: [patch, minor, major, prerelease]
|
|
13
|
+
release:
|
|
14
|
+
types: [published]
|
|
15
|
+
|
|
16
|
+
# Distinct literal group ("publish-…") — NOT github.workflow. The reused package-ci.yml
|
|
17
|
+
# computes its group from the CALLER's github.workflow = "Release", i.e. "Release-<ref>".
|
|
18
|
+
# If this run also used "Release-<ref>" it would hold that slot for its whole duration while
|
|
19
|
+
# its own child waits for the same slot → deadlock, and the reusable workflow never starts.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: publish-${{ github.ref }}
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read # least privilege at the top; the release job elevates below
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
release:
|
|
29
|
+
uses: moku-labs/ci/.github/workflows/package-release.yml@v1
|
|
30
|
+
# A called workflow can NEVER elevate beyond what the caller grants. These two are the
|
|
31
|
+
# ceiling for the whole pipeline; inside, each job takes only the one it needs.
|
|
32
|
+
permissions:
|
|
33
|
+
contents: write # release job: push refs/tags/* + create the GitHub release
|
|
34
|
+
id-token: write # publish job: tokenless OIDC Trusted Publishing
|
|
35
|
+
with:
|
|
36
|
+
release_type: ${{ inputs.release_type }}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moku-labs/ci",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Central CI and release for the moku family: reusable workflows, caller examples and the moku-release CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": ["./dist/release.mjs", "./src/index.ts"],
|
|
7
|
+
"bin": {
|
|
8
|
+
"moku-release": "./dist/release.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist", "examples", "rulesets", "LICENSE", "README.md"],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24.0.0",
|
|
13
|
+
"bun": ">=1.3.14"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/moku-labs/ci.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@biomejs/biome": "2.4.16",
|
|
26
|
+
"@moku-labs/common": "0.3.0",
|
|
27
|
+
"@types/bun": "1.3.14",
|
|
28
|
+
"@vitest/coverage-istanbul": "4.0.18",
|
|
29
|
+
"eslint": "9.39.3",
|
|
30
|
+
"eslint-config-biome": "2.1.3",
|
|
31
|
+
"eslint-plugin-jsdoc": "62.6.0",
|
|
32
|
+
"eslint-plugin-sonarjs": "4.0.0",
|
|
33
|
+
"eslint-plugin-unicorn": "63.0.0",
|
|
34
|
+
"globals": "17.4.0",
|
|
35
|
+
"jiti": "2.6.1",
|
|
36
|
+
"publint": "0.3.21",
|
|
37
|
+
"tsdown": "0.22.1",
|
|
38
|
+
"typescript": "6.0.3",
|
|
39
|
+
"typescript-eslint": "8.58.0",
|
|
40
|
+
"vitest": "4.0.18"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"validate": "publint",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"lint": "biome check . && eslint .",
|
|
47
|
+
"lint:fix": "biome check --write . && eslint --fix .",
|
|
48
|
+
"format": "biome format --write .",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"release:setup": "moku-release setup",
|
|
51
|
+
"release:doctor": "moku-release doctor",
|
|
52
|
+
"release": "moku-release"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "protect-main",
|
|
3
|
+
"target": "branch",
|
|
4
|
+
"enforcement": "active",
|
|
5
|
+
"bypass_actors": [],
|
|
6
|
+
"conditions": { "ref_name": { "include": ["refs/heads/main"], "exclude": [] } },
|
|
7
|
+
"rules": [
|
|
8
|
+
{ "type": "deletion" },
|
|
9
|
+
{ "type": "non_fast_forward" },
|
|
10
|
+
{
|
|
11
|
+
"type": "pull_request",
|
|
12
|
+
"parameters": {
|
|
13
|
+
"required_approving_review_count": 0,
|
|
14
|
+
"dismiss_stale_reviews_on_push": true,
|
|
15
|
+
"require_code_owner_review": false,
|
|
16
|
+
"require_last_push_approval": false,
|
|
17
|
+
"required_review_thread_resolution": false
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "required_status_checks",
|
|
22
|
+
"parameters": {
|
|
23
|
+
"strict_required_status_checks_policy": true,
|
|
24
|
+
"required_status_checks": [
|
|
25
|
+
{ "context": "ci / lint" },
|
|
26
|
+
{ "context": "ci / types" },
|
|
27
|
+
{ "context": "ci / test" },
|
|
28
|
+
{ "context": "ci / build" }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|