Skip to content

Commit a2fbc64

Browse files
authored
Chore/furystack consolidations (#3)
* furystack consolidations * chore: FuryStack consolidation for stack-craft - Rename prettier scripts to format/format:check - Fix all boilerplate references to stack-craft: - Package name and repository URL - API type renamed from BoilerplateApi to StackCraftApi - API client renamed from BoilerplateApiClient to StackCraftApiClient - Schema file renamed - Service name and description updated - Header title updated - HTML title updated - Environment options updated - Add changelog plugin configuration - Remove old version files (referenced wrong package name) - Update cursor rules documentation - Update GitHub Actions to use setup-node@v4 * ci: add deployment infrastructure and boilerplate API - Add check-changelog workflow for PR validation - Add publish-to-dockerhub workflow for container deployment - Add Dockerfile for containerized deployment - Add boilerplate API client and types - Update cursor rules formatting - Simplify check-version-bump workflow * deploy workflow fix
1 parent 31fbfe8 commit a2fbc64

30 files changed

Lines changed: 635 additions & 297 deletions

.cursor/rules/REST_SERVICE.mdc

Lines changed: 137 additions & 135 deletions
Large diffs are not rendered by default.

.cursor/rules/TESTING_GUIDELINES.mdc

Lines changed: 97 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The project uses Vitest for unit testing with workspace-based configuration:
1818

1919
```typescript
2020
// vitest.config.mts
21-
import { defineConfig } from 'vitest/config';
21+
import { defineConfig } from 'vitest/config'
2222

2323
export default defineConfig({
2424
test: {
@@ -48,7 +48,7 @@ export default defineConfig({
4848
},
4949
],
5050
},
51-
});
51+
})
5252
```
5353

5454
### Playwright for E2E Tests
@@ -57,8 +57,8 @@ E2E tests use Playwright with the service auto-started:
5757

5858
```typescript
5959
// playwright.config.ts
60-
import type { PlaywrightTestConfig } from '@playwright/test';
61-
import { devices } from '@playwright/test';
60+
import type { PlaywrightTestConfig } from '@playwright/test'
61+
import { devices } from '@playwright/test'
6262

6363
const config: PlaywrightTestConfig = {
6464
testDir: 'e2e',
@@ -76,7 +76,7 @@ const config: PlaywrightTestConfig = {
7676
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
7777
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
7878
],
79-
};
79+
}
8080
```
8181

8282
## Unit Testing with Vitest
@@ -98,97 +98,97 @@ service/src/
9898
Use `describe`, `it`, and `expect` from Vitest:
9999

100100
```typescript
101-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
101+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
102102

103103
describe('MyService', () => {
104104
describe('methodName', () => {
105105
it('should do something when condition', () => {
106106
// Arrange
107-
const input = 'test';
108-
107+
const input = 'test'
108+
109109
// Act
110-
const result = myFunction(input);
111-
110+
const result = myFunction(input)
111+
112112
// Assert
113-
expect(result).toBe('expected');
114-
});
115-
});
116-
});
113+
expect(result).toBe('expected')
114+
})
115+
})
116+
})
117117
```
118118

119119
### Testing Services
120120

121121
Test service methods with mocked dependencies:
122122

123123
```typescript
124-
import { describe, it, expect, vi, beforeEach } from 'vitest';
125-
import { Injector } from '@furystack/inject';
124+
import { describe, it, expect, vi, beforeEach } from 'vitest'
125+
import { Injector } from '@furystack/inject'
126126

127127
describe('SessionService', () => {
128-
let injector: Injector;
129-
let sessionService: SessionService;
130-
128+
let injector: Injector
129+
let sessionService: SessionService
130+
131131
beforeEach(() => {
132-
injector = new Injector();
132+
injector = new Injector()
133133
// Set up mocks
134134
const mockApiClient = {
135135
call: vi.fn(),
136-
};
137-
injector.setExplicitInstance(BoilerplateApiClient, mockApiClient);
138-
sessionService = injector.getInstance(SessionService);
139-
});
140-
136+
}
137+
injector.setExplicitInstance(StackCraftApiClient, mockApiClient)
138+
sessionService = injector.getInstance(SessionService)
139+
})
140+
141141
it('should initialize with unauthenticated state', async () => {
142-
expect(sessionService.state.getValue()).toBe('initializing');
143-
});
144-
});
142+
expect(sessionService.state.getValue()).toBe('initializing')
143+
})
144+
})
145145
```
146146

147147
### Mocking with Vitest
148148

149149
Use `vi.fn()` for function mocks and `vi.spyOn()` for spying:
150150

151151
```typescript
152-
import { vi } from 'vitest';
152+
import { vi } from 'vitest'
153153

154154
// Mock a function
155-
const mockFn = vi.fn().mockReturnValue('mocked');
155+
const mockFn = vi.fn().mockReturnValue('mocked')
156156

157157
// Mock with implementation
158-
const mockFn = vi.fn().mockImplementation((arg) => `result: ${arg}`);
158+
const mockFn = vi.fn().mockImplementation((arg) => `result: ${arg}`)
159159

160160
// Mock async function
161-
const mockAsync = vi.fn().mockResolvedValue({ data: 'test' });
161+
const mockAsync = vi.fn().mockResolvedValue({ data: 'test' })
162162

163163
// Spy on method
164-
const spy = vi.spyOn(service, 'method');
164+
const spy = vi.spyOn(service, 'method')
165165

166166
// Verify calls
167-
expect(mockFn).toHaveBeenCalled();
168-
expect(mockFn).toHaveBeenCalledWith('arg');
169-
expect(mockFn).toHaveBeenCalledTimes(2);
167+
expect(mockFn).toHaveBeenCalled()
168+
expect(mockFn).toHaveBeenCalledWith('arg')
169+
expect(mockFn).toHaveBeenCalledTimes(2)
170170
```
171171

172172
### Testing Observable Values
173173

174174
Test ObservableValue subscriptions:
175175

176176
```typescript
177-
import { ObservableValue } from '@furystack/utils';
177+
import { ObservableValue } from '@furystack/utils'
178178

179179
describe('ObservableValue', () => {
180180
it('should notify subscribers on value change', () => {
181-
const observable = new ObservableValue<string>('initial');
182-
const values: string[] = [];
183-
184-
const subscription = observable.subscribe((value) => values.push(value));
185-
observable.setValue('updated');
186-
187-
expect(values).toEqual(['initial', 'updated']);
188-
189-
subscription.dispose();
190-
});
191-
});
181+
const observable = new ObservableValue<string>('initial')
182+
const values: string[] = []
183+
184+
const subscription = observable.subscribe((value) => values.push(value))
185+
observable.setValue('updated')
186+
187+
expect(values).toEqual(['initial', 'updated'])
188+
189+
subscription.dispose()
190+
})
191+
})
192192
```
193193

194194
## E2E Testing with Playwright
@@ -209,26 +209,26 @@ e2e/
209209
Use Playwright's test API:
210210

211211
```typescript
212-
import { expect, test } from '@playwright/test';
212+
import { expect, test } from '@playwright/test'
213213

214214
test.describe('Feature Name', () => {
215215
test('should do something', async ({ page }) => {
216216
// Navigate
217-
await page.goto('/');
218-
217+
await page.goto('/')
218+
219219
// Find elements
220-
const element = page.locator('selector');
221-
220+
const element = page.locator('selector')
221+
222222
// Assert visibility
223-
await expect(element).toBeVisible();
224-
223+
await expect(element).toBeVisible()
224+
225225
// Interact
226-
await element.click();
227-
226+
await element.click()
227+
228228
// Assert result
229-
await expect(page.locator('.result')).toHaveText('Expected');
230-
});
231-
});
229+
await expect(page.locator('.result')).toHaveText('Expected')
230+
})
231+
})
232232
```
233233

234234
### Locating Shades Components
@@ -238,58 +238,58 @@ Use shadow DOM component names as selectors:
238238
```typescript
239239
test('should interact with Shades components', async ({ page }) => {
240240
// Locate by shadow DOM name
241-
const loginForm = page.locator('shade-login form');
242-
await expect(loginForm).toBeVisible();
243-
241+
const loginForm = page.locator('shade-login form')
242+
await expect(loginForm).toBeVisible()
243+
244244
// Locate inputs within components
245-
const usernameInput = loginForm.locator('input[name="userName"]');
246-
const passwordInput = loginForm.locator('input[name="password"]');
247-
245+
const usernameInput = loginForm.locator('input[name="userName"]')
246+
const passwordInput = loginForm.locator('input[name="password"]')
247+
248248
// Fill inputs
249-
await usernameInput.type('testuser');
250-
await passwordInput.type('password');
251-
249+
await usernameInput.type('testuser')
250+
await passwordInput.type('password')
251+
252252
// Click buttons
253-
const submitButton = page.locator('button', { hasText: 'Login' });
254-
await submitButton.click();
255-
});
253+
const submitButton = page.locator('button', { hasText: 'Login' })
254+
await submitButton.click()
255+
})
256256
```
257257

258258
### Authentication Flow Test
259259

260260
Example of testing login/logout:
261261

262262
```typescript
263-
import { expect, test } from '@playwright/test';
263+
import { expect, test } from '@playwright/test'
264264

265265
test.describe('Authentication', () => {
266266
test('Login and logout roundtrip', async ({ page }) => {
267-
await page.goto('/');
268-
267+
await page.goto('/')
268+
269269
// Wait for login form
270-
const loginForm = page.locator('shade-login form');
271-
await expect(loginForm).toBeVisible();
272-
270+
const loginForm = page.locator('shade-login form')
271+
await expect(loginForm).toBeVisible()
272+
273273
// Fill credentials
274-
await loginForm.locator('input[name="userName"]').type('testuser');
275-
await loginForm.locator('input[name="password"]').type('password');
276-
274+
await loginForm.locator('input[name="userName"]').type('testuser')
275+
await loginForm.locator('input[name="password"]').type('password')
276+
277277
// Submit
278-
await page.locator('button', { hasText: 'Login' }).click();
279-
278+
await page.locator('button', { hasText: 'Login' }).click()
279+
280280
// Verify logged in state
281-
const welcomeTitle = page.locator('hello-world div h2');
282-
await expect(welcomeTitle).toBeVisible();
283-
await expect(welcomeTitle).toHaveText('Hello, testuser !');
284-
281+
const welcomeTitle = page.locator('hello-world div h2')
282+
await expect(welcomeTitle).toBeVisible()
283+
await expect(welcomeTitle).toHaveText('Hello, testuser !')
284+
285285
// Logout
286-
const logoutButton = page.locator('shade-app-bar button >> text="Log Out"');
287-
await logoutButton.click();
288-
286+
const logoutButton = page.locator('shade-app-bar button >> text="Log Out"')
287+
await logoutButton.click()
288+
289289
// Verify logged out
290-
await expect(page.locator('shade-login form')).toBeVisible();
291-
});
292-
});
290+
await expect(page.locator('shade-login form')).toBeVisible()
291+
})
292+
})
293293
```
294294

295295
### Waiting for Elements
@@ -298,14 +298,14 @@ Use Playwright's auto-waiting or explicit waits:
298298

299299
```typescript
300300
// Auto-wait (recommended)
301-
await expect(element).toBeVisible();
301+
await expect(element).toBeVisible()
302302

303303
// Explicit wait
304-
await page.waitForSelector('selector');
305-
await page.waitForLoadState('networkidle');
304+
await page.waitForSelector('selector')
305+
await page.waitForLoadState('networkidle')
306306

307307
// Wait for response
308-
await page.waitForResponse('**/api/endpoint');
308+
await page.waitForResponse('**/api/endpoint')
309309
```
310310

311311
## Running Tests

.github/workflows/build-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ jobs:
1818
node-version: ${{ matrix.node-version }}
1919
- name: Install dependencies
2020
run: yarn install
21-
- name: Prettier check
22-
run: yarn prettier:check
21+
- name: Format check
22+
run: yarn format:check
2323
- name: Build
2424
run: yarn build
2525
env:
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Changelog checks
2+
on:
3+
push:
4+
branches-ignore:
5+
- 'release/**'
6+
- 'master'
7+
- 'develop'
8+
pull_request:
9+
branches:
10+
- develop
11+
jobs:
12+
check:
13+
name: Check changelog completion
14+
timeout-minutes: 5
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
- name: Use Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '24'
25+
- name: Check changelog entries
26+
run: yarn changelog check
27+
env:
28+
CI: true

.github/workflows/check-version-bump.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ jobs:
2121
with:
2222
fetch-depth: 0
2323
- name: Use Node.js ${{ matrix.node-version }}
24-
uses: actions/setup-node@v2
24+
uses: actions/setup-node@v4
2525
with:
2626
node-version: ${{ matrix.node-version }}
2727
- name: Check version bumps
28-
continue-on-error: true ## Set this to false once versioning has been set up
2928
run: yarn version check
3029
env:
3130
CI: true

0 commit comments

Comments
 (0)