create-backbone-template 0.1.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/README.md +33 -0
- package/bin/create-backbone-template.js +5 -0
- package/package.json +30 -0
- package/src/create-backbone-template.js +204 -0
- package/template/.agents/skills/agent-browser/SKILL.md +55 -0
- package/template/.agents/skills/create-plan/SKILL.md +52 -0
- package/template/.agents/skills/create-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/create-pr-presentation/SKILL.md +86 -0
- package/template/.agents/skills/create-pr-presentation/agents/openai.yaml +4 -0
- package/template/.agents/skills/implement-plan/SKILL.md +26 -0
- package/template/.agents/skills/implement-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/review-plan/SKILL.md +38 -0
- package/template/.agents/skills/review-plan/agents/openai.yaml +4 -0
- package/template/.env.schema +30 -0
- package/template/.env.test +6 -0
- package/template/.oxlintrc.json +67 -0
- package/template/.vscode/extensions.json +3 -0
- package/template/.vscode/settings.json +23 -0
- package/template/AGENTS.md +55 -0
- package/template/Cargo.lock +2648 -0
- package/template/Cargo.toml +29 -0
- package/template/Justfile +140 -0
- package/template/README.md +72 -0
- package/template/TODO.md +1 -0
- package/template/_gitignore +12 -0
- package/template/buf.gen.yaml +7 -0
- package/template/buf.yaml +10 -0
- package/template/client/.oxfmtrc.json +8 -0
- package/template/client/.oxlintrc.json +57 -0
- package/template/client/README.md +19 -0
- package/template/client/_gitignore +5 -0
- package/template/client/index.html +12 -0
- package/template/client/package.json +47 -0
- package/template/client/packages/design-system/package.json +19 -0
- package/template/client/packages/design-system/src/index.ts +2 -0
- package/template/client/packages/design-system-basic/package.json +18 -0
- package/template/client/packages/design-system-basic/src/button.stories.tsx +50 -0
- package/template/client/packages/design-system-basic/src/button.tsx +26 -0
- package/template/client/packages/design-system-basic/src/empty-state.stories.tsx +18 -0
- package/template/client/packages/design-system-basic/src/empty-state.tsx +17 -0
- package/template/client/packages/design-system-basic/src/form-field.stories.tsx +15 -0
- package/template/client/packages/design-system-basic/src/form-field.tsx +10 -0
- package/template/client/packages/design-system-basic/src/form.stories.tsx +27 -0
- package/template/client/packages/design-system-basic/src/form.tsx +9 -0
- package/template/client/packages/design-system-basic/src/heading.stories.tsx +14 -0
- package/template/client/packages/design-system-basic/src/heading.tsx +25 -0
- package/template/client/packages/design-system-basic/src/index.tsx +15 -0
- package/template/client/packages/design-system-basic/src/inline.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/inline.tsx +5 -0
- package/template/client/packages/design-system-basic/src/layout.stories.tsx +24 -0
- package/template/client/packages/design-system-basic/src/layout.tsx +14 -0
- package/template/client/packages/design-system-basic/src/loader.stories.tsx +8 -0
- package/template/client/packages/design-system-basic/src/loader.tsx +11 -0
- package/template/client/packages/design-system-basic/src/navigation.stories.tsx +16 -0
- package/template/client/packages/design-system-basic/src/navigation.tsx +18 -0
- package/template/client/packages/design-system-basic/src/notice.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/notice.tsx +5 -0
- package/template/client/packages/design-system-basic/src/stack.stories.tsx +17 -0
- package/template/client/packages/design-system-basic/src/stack.tsx +5 -0
- package/template/client/packages/design-system-basic/src/styles.css +254 -0
- package/template/client/packages/design-system-basic/src/text-input.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/text-input.tsx +5 -0
- package/template/client/packages/design-system-basic/src/text.stories.tsx +21 -0
- package/template/client/packages/design-system-basic/src/text.tsx +5 -0
- package/template/client/packages/design-system-contract/package.json +15 -0
- package/template/client/packages/design-system-contract/src/button.ts +10 -0
- package/template/client/packages/design-system-contract/src/empty-state.ts +9 -0
- package/template/client/packages/design-system-contract/src/form-field.ts +9 -0
- package/template/client/packages/design-system-contract/src/form.ts +9 -0
- package/template/client/packages/design-system-contract/src/heading.ts +9 -0
- package/template/client/packages/design-system-contract/src/index.ts +13 -0
- package/template/client/packages/design-system-contract/src/inline.ts +7 -0
- package/template/client/packages/design-system-contract/src/layout.ts +8 -0
- package/template/client/packages/design-system-contract/src/loader.ts +7 -0
- package/template/client/packages/design-system-contract/src/navigation.ts +13 -0
- package/template/client/packages/design-system-contract/src/notice.ts +8 -0
- package/template/client/packages/design-system-contract/src/stack.ts +8 -0
- package/template/client/packages/design-system-contract/src/text-input.ts +5 -0
- package/template/client/packages/design-system-contract/src/text.ts +9 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/external-ui-import.tsx +5 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/raw-dom-jsx.tsx +3 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/two-violations.tsx +7 -0
- package/template/client/packages/design-system-lint/fixtures/valid/design-system-only.tsx +13 -0
- package/template/client/packages/design-system-lint/package.json +23 -0
- package/template/client/packages/design-system-lint/src/check-design-system-architecture.ts +22 -0
- package/template/client/packages/design-system-lint/src/design-system-architecture.ts +286 -0
- package/template/client/packages/design-system-lint/src/oxlint-plugin.ts +11 -0
- package/template/client/packages/design-system-lint/src/page-architecture.ts +382 -0
- package/template/client/packages/design-system-lint/src/rules.ts +111 -0
- package/template/client/packages/design-system-lint/test/design-system-architecture.test.ts +243 -0
- package/template/client/packages/design-system-lint/test/oxlint-fixtures.test.ts +159 -0
- package/template/client/packages/design-system-lint/test/page-architecture.test.ts +175 -0
- package/template/client/packages/design-system-lint/test/rules.test.ts +65 -0
- package/template/client/packages/design-system-lint/tsconfig.json +29 -0
- package/template/client/src/App.tsx +77 -0
- package/template/client/src/design-system-components.test.tsx +75 -0
- package/template/client/src/gen/helloworld/v1/helloworld_pb.ts +63 -0
- package/template/client/src/main.tsx +18 -0
- package/template/client/src/pages/hello/hello-page.stories.tsx +20 -0
- package/template/client/src/pages/hello/hello-page.test.tsx +90 -0
- package/template/client/src/pages/hello/hello-page.tsx +126 -0
- package/template/client/src/pages/page.ts +20 -0
- package/template/client/src/testing/create-preview-events.test.ts +36 -0
- package/template/client/src/testing/create-preview-events.ts +30 -0
- package/template/client/src/vite-env.d.ts +1 -0
- package/template/client/tsconfig.json +32 -0
- package/template/client/vite.config.ts +21 -0
- package/template/client/vite.ladle.config.ts +5 -0
- package/template/e2e/.gherkin-lintrc +20 -0
- package/template/e2e/.oxfmtrc.json +15 -0
- package/template/e2e/.oxlintrc.json +37 -0
- package/template/e2e/_gitignore +4 -0
- package/template/e2e/features/helloworld.feature +10 -0
- package/template/e2e/package.json +42 -0
- package/template/e2e/playwright.config.ts +16 -0
- package/template/e2e/support/app-gherkin.ts +4 -0
- package/template/e2e/support/fixtures.ts +236 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-step.spec.ts +10 -0
- package/template/e2e/support/gherkin-fixtures/happy-path.spec.ts +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.feature +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-step.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/playwright.config.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/step-mismatch.spec.ts +9 -0
- package/template/e2e/support/gherkin-fixtures/valid-implementations.ts +23 -0
- package/template/e2e/support/gherkin-fixtures/valid-scenarios.feature +26 -0
- package/template/e2e/support/gherkin.test.ts +184 -0
- package/template/e2e/support/gherkin.ts +321 -0
- package/template/e2e/support/oxlint-plugin.test.ts +328 -0
- package/template/e2e/support/oxlint-plugin.ts +485 -0
- package/template/e2e/tests/helloworld.spec.ts +39 -0
- package/template/e2e/tsconfig.json +26 -0
- package/template/e2e/tsconfig.oxlint-plugin.json +12 -0
- package/template/package.json +9 -0
- package/template/pnpm-lock.yaml +10723 -0
- package/template/pnpm-workspace.yaml +8 -0
- package/template/pr-slide/README.md +95 -0
- package/template/pr-slide/package.json +23 -0
- package/template/pr-slide/src/cli.js +262 -0
- package/template/pr-slide/src/generate-pr-deck.js +833 -0
- package/template/pr-slide/src/git-context.js +91 -0
- package/template/pr-slide/src/presentation-paths.js +9 -0
- package/template/pr-slide/src/presentations.js +53 -0
- package/template/pr-slide/test/generate-pr-deck.test.js +118 -0
- package/template/pr-slide/test/presentation-paths.test.js +14 -0
- package/template/pr-slide/test/presentations.test.js +50 -0
- package/template/proto/helloworld/v1/helloworld.proto +15 -0
- package/template/scripts/run-e2e.sh +10 -0
- package/template/server/Cargo.toml +26 -0
- package/template/server/build.rs +9 -0
- package/template/server/dylint/backbone_server_lints/.cargo/config.toml +6 -0
- package/template/server/dylint/backbone_server_lints/Cargo.lock +1581 -0
- package/template/server/dylint/backbone_server_lints/Cargo.toml +21 -0
- package/template/server/dylint/backbone_server_lints/README.md +5 -0
- package/template/server/dylint/backbone_server_lints/_gitignore +1 -0
- package/template/server/dylint/backbone_server_lints/rust-toolchain +3 -0
- package/template/server/dylint/backbone_server_lints/src/lib.rs +612 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.rs +4 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.stderr +10 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.rs +303 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.stderr +6 -0
- package/template/server/dylint/backbone_server_lints/ui/main.rs +59 -0
- package/template/server/dylint/backbone_server_lints/ui/main.stderr +85 -0
- package/template/server/migrations/20260520120000_create_projects.sql +12 -0
- package/template/server/migrations/20260524160000_create_hello_world_inputs.sql +12 -0
- package/template/server/src/config.rs +27 -0
- package/template/server/src/db/hello_world.rs +34 -0
- package/template/server/src/db/hello_world_tests.rs +11 -0
- package/template/server/src/db/mod.rs +39 -0
- package/template/server/src/lib.rs +10 -0
- package/template/server/src/main.rs +43 -0
- package/template/server/src/rpc/greeter/mod.rs +31 -0
- package/template/server/src/rpc/greeter/say_hello.rs +27 -0
- package/template/server/src/rpc/mod.rs +8 -0
- package/template/server/src/state.rs +13 -0
- package/template/skills-lock.json +11 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "promise", "node"],
|
|
4
|
+
"jsPlugins": ["./support/dist/oxlint-plugin.js"],
|
|
5
|
+
"categories": {
|
|
6
|
+
"correctness": "deny",
|
|
7
|
+
"suspicious": "deny",
|
|
8
|
+
"perf": "deny",
|
|
9
|
+
"pedantic": "off",
|
|
10
|
+
"style": "off",
|
|
11
|
+
"restriction": "off",
|
|
12
|
+
"nursery": "off"
|
|
13
|
+
},
|
|
14
|
+
"options": {
|
|
15
|
+
"denyWarnings": true,
|
|
16
|
+
"reportUnusedDisableDirectives": "deny",
|
|
17
|
+
"typeAware": true
|
|
18
|
+
},
|
|
19
|
+
"rules": {
|
|
20
|
+
"typescript/no-unsafe-type-assertion": "off",
|
|
21
|
+
"unicorn/no-array-sort": "off"
|
|
22
|
+
},
|
|
23
|
+
"overrides": [
|
|
24
|
+
{
|
|
25
|
+
"files": ["**/*.test.ts"],
|
|
26
|
+
"rules": {
|
|
27
|
+
"typescript/no-floating-promises": "off"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"files": ["tests/**/*.spec.ts"],
|
|
32
|
+
"rules": {
|
|
33
|
+
"backbone-e2e/valid-gherkin-feature": "error"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Feature: Hello page
|
|
2
|
+
|
|
3
|
+
@id:hello.say-hello
|
|
4
|
+
Scenario: Say hello through the UI
|
|
5
|
+
Given the Rust server is healthy
|
|
6
|
+
And the visitor is on the hello page
|
|
7
|
+
Then they see the default hello message
|
|
8
|
+
When they ask to greet Playwright
|
|
9
|
+
Then they see the Playwright greeting
|
|
10
|
+
And the Playwright input is saved
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backbone-e2e",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "pnpm run test:support && playwright test",
|
|
8
|
+
"test:prepared": "pnpm run test:support:prepared && playwright test",
|
|
9
|
+
"test:browser": "playwright test",
|
|
10
|
+
"test:support": "pnpm run test:adapter && pnpm run test:oxlint-plugin",
|
|
11
|
+
"test:support:prepared": "pnpm run test:adapter && pnpm run test:oxlint-plugin:prepared",
|
|
12
|
+
"test:adapter": "node --disable-warning=ExperimentalWarning --experimental-strip-types --test support/gherkin.test.ts",
|
|
13
|
+
"test:oxlint-plugin": "pnpm run build:oxlint-plugin && node --disable-warning=ExperimentalWarning --experimental-strip-types --test support/oxlint-plugin.test.ts",
|
|
14
|
+
"test:oxlint-plugin:prepared": "node --disable-warning=ExperimentalWarning --experimental-strip-types --test support/oxlint-plugin.test.ts",
|
|
15
|
+
"build:oxlint-plugin": "tsc --project tsconfig.oxlint-plugin.json",
|
|
16
|
+
"format": "oxfmt .",
|
|
17
|
+
"format:check": "oxfmt --check .",
|
|
18
|
+
"lint": "pnpm run format:check && pnpm run lint:code && pnpm run lint:features",
|
|
19
|
+
"lint:prepared": "pnpm run format:check && pnpm run lint:code:prepared && pnpm run lint:features",
|
|
20
|
+
"lint:code": "pnpm run build:oxlint-plugin && oxlint --config .oxlintrc.json playwright.config.ts support/app-gherkin.ts support/fixtures.ts support/gherkin.ts support/gherkin.test.ts support/oxlint-plugin.ts support/oxlint-plugin.test.ts tests",
|
|
21
|
+
"lint:code:prepared": "oxlint --config .oxlintrc.json playwright.config.ts support/app-gherkin.ts support/fixtures.ts support/gherkin.ts support/gherkin.test.ts support/oxlint-plugin.ts support/oxlint-plugin.test.ts tests",
|
|
22
|
+
"lint:features": "gherkin-lint -c .gherkin-lintrc \"features/**/*.feature\"",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test:debug": "playwright test --max-failures=1 --headed --debug",
|
|
25
|
+
"test:headed": "playwright test --headed",
|
|
26
|
+
"test:ui": "playwright test --ui",
|
|
27
|
+
"report": "playwright show-report"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@cucumber/gherkin": "^39.1.0",
|
|
31
|
+
"@cucumber/messages": "^32.3.1",
|
|
32
|
+
"@playwright/test": "latest",
|
|
33
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
34
|
+
"@types/node": "^25.9.1",
|
|
35
|
+
"better-sqlite3": "^12.10.0",
|
|
36
|
+
"gherkin-lint": "^4.2.4",
|
|
37
|
+
"oxfmt": "^0.51.0",
|
|
38
|
+
"oxlint": "^1.66.0",
|
|
39
|
+
"oxlint-tsgolint": "^0.22.1",
|
|
40
|
+
"typescript": "^6.0.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test"
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests",
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
7
|
+
use: {
|
|
8
|
+
trace: "on-first-retry",
|
|
9
|
+
},
|
|
10
|
+
projects: [
|
|
11
|
+
{
|
|
12
|
+
name: "chromium",
|
|
13
|
+
use: { ...devices["Desktop Chrome"] },
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
})
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process"
|
|
2
|
+
import { mkdirSync, rmSync } from "node:fs"
|
|
3
|
+
import net from "node:net"
|
|
4
|
+
import os from "node:os"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
import { test as base } from "@playwright/test"
|
|
8
|
+
import Database from "better-sqlite3"
|
|
9
|
+
|
|
10
|
+
type AppWorker = {
|
|
11
|
+
clientUrl: string
|
|
12
|
+
db: E2eDatabase
|
|
13
|
+
serverUrl: string
|
|
14
|
+
stop(): Promise<void>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type WorkerFixtures = {
|
|
18
|
+
app: AppWorker
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type AppFixtures = {
|
|
22
|
+
db: E2eDatabase
|
|
23
|
+
serverUrl: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const test = base.extend<AppFixtures, WorkerFixtures>({
|
|
27
|
+
app: [
|
|
28
|
+
// oxlint-disable-next-line no-empty-pattern -- Playwright worker fixtures require object destructuring here.
|
|
29
|
+
async ({}, use, workerInfo) => {
|
|
30
|
+
const app = await startAppWorker(workerInfo.workerIndex)
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await use(app)
|
|
34
|
+
} finally {
|
|
35
|
+
await app.stop()
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{ scope: "worker" },
|
|
39
|
+
],
|
|
40
|
+
baseURL: async ({ app }, use) => {
|
|
41
|
+
await use(app.clientUrl)
|
|
42
|
+
},
|
|
43
|
+
db: async ({ app }, use) => {
|
|
44
|
+
await use(app.db)
|
|
45
|
+
},
|
|
46
|
+
serverUrl: async ({ app }, use) => {
|
|
47
|
+
await use(app.serverUrl)
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export class E2eDatabase {
|
|
52
|
+
readonly #database: Database.Database
|
|
53
|
+
|
|
54
|
+
constructor(databasePath: string) {
|
|
55
|
+
this.#database = new Database(databasePath)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
close(): void {
|
|
59
|
+
this.#database.close()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
reset(): void {
|
|
63
|
+
this.#database.prepare("DELETE FROM hello_world_inputs").run()
|
|
64
|
+
this.#database.prepare("DELETE FROM projects").run()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
listHelloWorldInputs(): string[] {
|
|
68
|
+
return this.#database
|
|
69
|
+
.prepare("SELECT input FROM hello_world_inputs ORDER BY created_at, id")
|
|
70
|
+
.all()
|
|
71
|
+
.map(readHelloWorldInput)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function startAppWorker(workerIndex: number): Promise<AppWorker> {
|
|
76
|
+
const [serverPort, clientPort] = await Promise.all([freePort(), freePort()])
|
|
77
|
+
const rootDir = path.resolve("..")
|
|
78
|
+
const workerDir = path.join(os.tmpdir(), `backbone-e2e-${process.pid}-${workerIndex}`)
|
|
79
|
+
const databasePath = path.join(workerDir, "backbone.sqlite")
|
|
80
|
+
const databaseUrl = `sqlite://${databasePath}?mode=rwc`
|
|
81
|
+
|
|
82
|
+
rmSync(workerDir, { force: true, recursive: true })
|
|
83
|
+
mkdirSync(workerDir, { recursive: true })
|
|
84
|
+
|
|
85
|
+
const serverUrl = `http://127.0.0.1:${serverPort}`
|
|
86
|
+
const clientUrl = `http://127.0.0.1:${clientPort}`
|
|
87
|
+
const env = {
|
|
88
|
+
...process.env,
|
|
89
|
+
APP_ENV: "test",
|
|
90
|
+
DATABASE_URL: databaseUrl,
|
|
91
|
+
SERVER_HOST: "127.0.0.1",
|
|
92
|
+
SERVER_PORT: String(serverPort),
|
|
93
|
+
VITE_DEV_SERVER_PORT: String(clientPort),
|
|
94
|
+
VITE_SERVER_URL: serverUrl,
|
|
95
|
+
}
|
|
96
|
+
const server = spawnProcess("cargo", ["run", "-p", "server"], rootDir, env)
|
|
97
|
+
|
|
98
|
+
await waitForUrl(`${serverUrl}/health`, [server], "server")
|
|
99
|
+
|
|
100
|
+
const client = spawnProcess(
|
|
101
|
+
"pnpm",
|
|
102
|
+
[
|
|
103
|
+
"--filter",
|
|
104
|
+
"backbone-client",
|
|
105
|
+
"exec",
|
|
106
|
+
"vite",
|
|
107
|
+
"--host",
|
|
108
|
+
"127.0.0.1",
|
|
109
|
+
"--port",
|
|
110
|
+
String(clientPort),
|
|
111
|
+
"--strictPort",
|
|
112
|
+
],
|
|
113
|
+
rootDir,
|
|
114
|
+
env,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
await waitForUrl(clientUrl, [server, client], "client")
|
|
118
|
+
|
|
119
|
+
const db = new E2eDatabase(databasePath)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
clientUrl,
|
|
123
|
+
db,
|
|
124
|
+
serverUrl,
|
|
125
|
+
async stop() {
|
|
126
|
+
db.close()
|
|
127
|
+
await stopProcess(client)
|
|
128
|
+
await stopProcess(server)
|
|
129
|
+
rmSync(workerDir, { force: true, recursive: true })
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function spawnProcess(
|
|
135
|
+
command: string,
|
|
136
|
+
args: string[],
|
|
137
|
+
cwd: string,
|
|
138
|
+
env: NodeJS.ProcessEnv,
|
|
139
|
+
): ChildProcess {
|
|
140
|
+
const child = spawn(command, args, {
|
|
141
|
+
cwd,
|
|
142
|
+
env,
|
|
143
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
147
|
+
process.stdout.write(chunk)
|
|
148
|
+
})
|
|
149
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
150
|
+
process.stderr.write(chunk)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return child
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function stopProcess(child: ChildProcess): Promise<void> {
|
|
157
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
child.kill("SIGTERM")
|
|
162
|
+
|
|
163
|
+
await new Promise<void>((resolve) => {
|
|
164
|
+
child.once("exit", () => {
|
|
165
|
+
resolve()
|
|
166
|
+
})
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
169
|
+
child.kill("SIGKILL")
|
|
170
|
+
}
|
|
171
|
+
resolve()
|
|
172
|
+
}, 3000)
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function waitForUrl(url: string, processes: ChildProcess[], name: string): Promise<void> {
|
|
177
|
+
for (let attempt = 0; attempt < 150; attempt += 1) {
|
|
178
|
+
for (const child of processes) {
|
|
179
|
+
if (child.exitCode !== null) {
|
|
180
|
+
throw new Error(`${name} dependency exited with code ${child.exitCode}`)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// oxlint-disable-next-line no-await-in-loop -- Readiness polling is intentionally sequential.
|
|
186
|
+
const response = await fetch(url)
|
|
187
|
+
|
|
188
|
+
if (response.ok) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Keep polling until the process is ready or exits.
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// oxlint-disable-next-line no-await-in-loop -- Polling attempts need a delay between checks.
|
|
196
|
+
await delay(200)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new Error(`Timed out waiting for ${name} at ${url}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function freePort(): Promise<number> {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const server = net.createServer()
|
|
205
|
+
|
|
206
|
+
server.once("error", reject)
|
|
207
|
+
server.listen(0, "127.0.0.1", () => {
|
|
208
|
+
const address = server.address()
|
|
209
|
+
|
|
210
|
+
if (typeof address === "object" && address !== null) {
|
|
211
|
+
const port = address.port
|
|
212
|
+
server.close(() => {
|
|
213
|
+
resolve(port)
|
|
214
|
+
})
|
|
215
|
+
} else {
|
|
216
|
+
server.close(() => {
|
|
217
|
+
reject(new Error("Could not allocate a TCP port"))
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function readHelloWorldInput(row: unknown): string {
|
|
225
|
+
if (typeof row === "object" && row !== null && "input" in row && typeof row.input === "string") {
|
|
226
|
+
return row.input
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw new Error("Unexpected hello_world_inputs row shape")
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function delay(ms: number): Promise<void> {
|
|
233
|
+
await new Promise((resolve) => {
|
|
234
|
+
setTimeout(resolve, ms)
|
|
235
|
+
})
|
|
236
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { feature } from "../gherkin"
|
|
2
|
+
import { validImplementations } from "./valid-implementations"
|
|
3
|
+
|
|
4
|
+
feature("./valid-scenarios.feature", {
|
|
5
|
+
...validImplementations,
|
|
6
|
+
"fixture.extra-step": async ({ scenario }) => {
|
|
7
|
+
await scenario.step("Given the only documented step", async () => {})
|
|
8
|
+
await scenario.step("Then an extra implementation step runs", async () => {})
|
|
9
|
+
},
|
|
10
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { feature } from "../gherkin"
|
|
2
|
+
import { validImplementations } from "./valid-implementations"
|
|
3
|
+
|
|
4
|
+
const { "fixture.missing-implementation": _missingImplementation, ...implementations } =
|
|
5
|
+
validImplementations
|
|
6
|
+
|
|
7
|
+
feature("./valid-scenarios.feature", implementations)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { feature } from "../gherkin"
|
|
2
|
+
import { validImplementations } from "./valid-implementations"
|
|
3
|
+
|
|
4
|
+
feature("./valid-scenarios.feature", {
|
|
5
|
+
...validImplementations,
|
|
6
|
+
"fixture.step-mismatch": async ({ scenario }) => {
|
|
7
|
+
await scenario.step("Given a different step name", async () => {})
|
|
8
|
+
},
|
|
9
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ScenarioImplementation } from "../gherkin"
|
|
2
|
+
|
|
3
|
+
export const validImplementations: Record<string, ScenarioImplementation> = {
|
|
4
|
+
"fixture.happy-path": async ({ scenario }) => {
|
|
5
|
+
await scenario.step("Given the first step matches", async () => {})
|
|
6
|
+
await scenario.step("Then the second step matches", async () => {})
|
|
7
|
+
},
|
|
8
|
+
"fixture.step-mismatch": async ({ scenario }) => {
|
|
9
|
+
await scenario.step("Given the documented step name", async () => {})
|
|
10
|
+
},
|
|
11
|
+
"fixture.missing-step": async ({ scenario }) => {
|
|
12
|
+
await scenario.step("Given the documented step is not implemented", async () => {})
|
|
13
|
+
},
|
|
14
|
+
"fixture.extra-step": async ({ scenario }) => {
|
|
15
|
+
await scenario.step("Given the only documented step", async () => {})
|
|
16
|
+
},
|
|
17
|
+
"fixture.missing-implementation": async ({ scenario }) => {
|
|
18
|
+
await scenario.step("Given the scenario is not implemented", async () => {})
|
|
19
|
+
},
|
|
20
|
+
"fixture.extra-implementation": async ({ scenario }) => {
|
|
21
|
+
await scenario.step("Given the feature has one implementation", async () => {})
|
|
22
|
+
},
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Feature: Valid adapter fixture scenarios
|
|
2
|
+
|
|
3
|
+
@id:fixture.happy-path
|
|
4
|
+
Scenario: Matching steps pass
|
|
5
|
+
Given the first step matches
|
|
6
|
+
Then the second step matches
|
|
7
|
+
|
|
8
|
+
@id:fixture.step-mismatch
|
|
9
|
+
Scenario: Renamed implementation step fails
|
|
10
|
+
Given the documented step name
|
|
11
|
+
|
|
12
|
+
@id:fixture.missing-step
|
|
13
|
+
Scenario: Missing implementation step fails
|
|
14
|
+
Given the documented step is not implemented
|
|
15
|
+
|
|
16
|
+
@id:fixture.extra-step
|
|
17
|
+
Scenario: Extra implementation step fails
|
|
18
|
+
Given the only documented step
|
|
19
|
+
|
|
20
|
+
@id:fixture.missing-implementation
|
|
21
|
+
Scenario: Missing implementation fails
|
|
22
|
+
Given the scenario is not implemented
|
|
23
|
+
|
|
24
|
+
@id:fixture.extra-implementation
|
|
25
|
+
Scenario: Extra implementation fails
|
|
26
|
+
Given the feature has one implementation
|