Repository navigation
155 lines (137 loc) · 5.77 KB
/
Copy pathci.yml
File metadata and controls
155 lines (137 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
# A newer push to the same branch cancels the previous run.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
installer:
name: Installer (typecheck + unit tests)
runs-on: ubuntu-latest
defaults:
run:
working-directory: .cursor-plugin/scripts
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
# The shell-loader tests spawn zsh and fish, neither of which ships on
# ubuntu-latest; without them those cases skip themselves and the loaders
# go untested. fish lives in universe, enabled by default on this image.
- name: Install the shells under test
run: sudo apt-get update && sudo apt-get install -y zsh fish
working-directory: .
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Typecheck
run: bunx tsc --noEmit
# No test may skip here: a skip means a shell is missing from the runner,
# which is exactly the silent coverage loss the step above prevents.
- name: Unit tests
run: |
set -euo pipefail
bun test 2>&1 | tee /tmp/bun-test.log
if grep -qE '^ *[0-9]+ skip' /tmp/bun-test.log; then
echo "::error::a unit test skipped on CI — a required shell is missing from the runner"
grep -E '\(skip\)' /tmp/bun-test.log || true
exit 1
fi
integration:
name: Install flows (sandboxed HOME)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install installer dependencies
run: bun install --frozen-lockfile
working-directory: .cursor-plugin/scripts
# Every suite drives install.sh against a throwaway HOME, so nothing
# touches the runner's own configuration.
- name: Shell test suites
run: |
set -euo pipefail
failed=0
for suite in tests/*.test.sh; do
echo "::group::$suite"
if bash "$suite"; then
echo "PASS $suite"
else
echo "::error file=$suite::suite failed"
failed=1
fi
echo "::endgroup::"
done
exit "$failed"
- name: Dry run writes nothing under ~/.cursor
run: |
set -euo pipefail
SANDBOX="$(mktemp -d)"
HOME="$SANDBOX" ./install.sh --dry-run
if [ -e "$SANDBOX/.cursor" ]; then
echo "::error::--dry-run created $SANDBOX/.cursor"
find "$SANDBOX/.cursor" | head -20
exit 1
fi
echo "PASS: dry-run left ~/.cursor untouched"
- name: Real install deploys every plugin
run: |
set -euo pipefail
SANDBOX="$(mktemp -d)"
HOME="$SANDBOX" ./install.sh --skip-env < /dev/null
expected=$(node -e "console.log(require('./.cursor-plugin/marketplace.json').plugins.length)")
actual=$(ls -1 "$SANDBOX/.cursor/plugins/local" | wc -l | tr -d ' ')
echo "plugins: $actual / $expected"
[ "$actual" = "$expected" ] || { echo "::error::plugin count mismatch"; exit 1; }
test -f "$SANDBOX/.cursor/rules/fuse-global.mdc" || { echo "::error::global user rule missing"; exit 1; }
test -f "$SANDBOX/.cursor/.fusengine-global/receipt.json" || { echo "::error::receipt missing"; exit 1; }
# The deployed loader must start from its destination, with its src/ tree.
HOME="$SANDBOX" bun "$SANDBOX/.cursor/.fusengine-global/scripts/hooks-loader.ts" sessionStart < /dev/null
echo "PASS: install deployed $actual plugins, rule, receipt and a runnable loader"
plugins:
name: Plugin manifests and hooks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Every plugin.json and hooks.json parses
run: |
set -euo pipefail
for f in .cursor-plugin/marketplace.json \
.cursor-plugin/plugins/*/.cursor-plugin/plugin.json \
.cursor-plugin/plugins/*/hooks/hooks.json; do
node -e "JSON.parse(require('fs').readFileSync('$f','utf8'))" \
|| { echo "::error file=$f::invalid JSON"; exit 1; }
done
echo "PASS: all manifests parse"
- name: Hook commands stay relative, wrappers executable
run: |
set -euo pipefail
if grep -rn 'npx -y @fusengine/harness\|bun \$HOME' .cursor-plugin/plugins/*/hooks/hooks.json; then
echo "::error::hook commands must call ./scripts/hook.sh (relative), per the Cursor submission checklist"
exit 1
fi
for w in .cursor-plugin/plugins/*/scripts/hook.sh; do
sh -n "$w" || { echo "::error file=$w::syntax error"; exit 1; }
test -x "$w" || { echo "::error file=$w::not executable"; exit 1; }
done
echo "PASS: $(ls -1 .cursor-plugin/plugins/*/scripts/hook.sh | wc -l | tr -d ' ') wrappers valid"
- name: Entry points resolve
run: |
set -euo pipefail
bash -n install.sh
bash -n verify.sh
bash -n verify-project.sh
for target in .cursor-plugin/scripts/install-hooks.ts \
.cursor-plugin/scripts/hooks-loader.ts \
.cursor-plugin/scripts/verify.ts \
.cursor-plugin/scripts/src/services/global-install.ts \
.cursor-plugin/scripts/src/services/project-install.ts; do
test -f "$target" || { echo "::error::dead entry point: $target"; exit 1; }
done
echo "PASS: shell entry points valid, every target exists"