@juangcarmona/lighthouse 0.5.0 → 0.7.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 +8 -2
- package/assets/dotnet-template/Directory.Build.props +22 -0
- package/assets/dotnet-template/Directory.Packages.props +16 -0
- package/assets/dotnet-template/Lighthouse.Analyzers/BackendContractAnalyzer.cs +72 -0
- package/assets/dotnet-template/Lighthouse.Analyzers/Lighthouse.Analyzers.csproj +12 -0
- package/assets/dotnet-template/Lighthouse.Analyzers/packages.lock.json +121 -0
- package/assets/dotnet-template/Lighthouse.Cqrs/Handlers.cs +11 -0
- package/assets/dotnet-template/Lighthouse.Cqrs/Lighthouse.Cqrs.csproj +5 -0
- package/assets/dotnet-template/Lighthouse.Cqrs/packages.lock.json +6 -0
- package/assets/dotnet-template/Lighthouse.slnx +17 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/CatalogModule.cs +24 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/Features/Entries/CreateEntry.cs +47 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/Features/Entries/EntryStore.cs +17 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/Features/Entries/GetEntry.cs +30 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/Lighthouse.Example.Modules.Catalog.csproj +12 -0
- package/assets/dotnet-template/examples/src/modules/Lighthouse.Example.Modules.Catalog/packages.lock.json +10 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.Api/Lighthouse.Example.Api.csproj +6 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.Api/Program.cs +25 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.Api/Properties/launchSettings.json +10 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.Api/packages.lock.json +86 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.AppHost/Lighthouse.Example.AppHost.csproj +13 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.AppHost/Program.cs +3 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.AppHost/Properties/launchSettings.json +15 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.AppHost/aspire.config.json +5 -0
- package/assets/dotnet-template/examples/src/root/Lighthouse.Example.AppHost/packages.lock.json +689 -0
- package/assets/dotnet-template/global.json +6 -0
- package/assets/dotnet-template/tests/Lighthouse.Analyzers.Tests/BackendContractTests.cs +102 -0
- package/assets/dotnet-template/tests/Lighthouse.Analyzers.Tests/Lighthouse.Analyzers.Tests.csproj +9 -0
- package/assets/dotnet-template/tests/Lighthouse.Analyzers.Tests/packages.lock.json +125 -0
- package/assets/dotnet-template/tests/Lighthouse.Example.Tests/CatalogTests.cs +111 -0
- package/assets/dotnet-template/tests/Lighthouse.Example.Tests/Lighthouse.Example.Tests.csproj +9 -0
- package/assets/dotnet-template/tests/Lighthouse.Example.Tests/packages.lock.json +490 -0
- package/assets/profiles/catalog.json +1050 -0
- package/assets/profiles/dotnet/README.md +32 -0
- package/assets/profiles/dotnet/SKILL.md +18 -0
- package/assets/profiles/dotnet/adoption.md +39 -0
- package/assets/profiles/dotnet/architecture.md +37 -0
- package/assets/profiles/dotnet/guardrails.md +32 -0
- package/assets/profiles/dotnet/practices.md +36 -0
- package/assets/profiles/dotnet/testing-and-development.md +29 -0
- package/assets/profiles/dotnet/use-cases.md +29 -0
- package/assets/profiles/mobile-expo/README.md +19 -0
- package/assets/profiles/mobile-expo/SKILL.md +14 -0
- package/assets/profiles/mobile-expo/adoption.md +11 -0
- package/assets/profiles/mobile-expo/guardrails.md +14 -0
- package/assets/profiles/mobile-expo/testing.md +13 -0
- package/assets/profiles/mobile-maui/README.md +19 -0
- package/assets/profiles/mobile-maui/SKILL.md +14 -0
- package/assets/profiles/mobile-maui/adoption.md +11 -0
- package/assets/profiles/mobile-maui/guardrails.md +13 -0
- package/assets/profiles/mobile-maui/testing.md +13 -0
- package/assets/profiles/pwa-capability/README.md +19 -0
- package/assets/profiles/pwa-capability/SKILL.md +14 -0
- package/assets/profiles/pwa-capability/adoption.md +9 -0
- package/assets/profiles/pwa-capability/guardrails.md +13 -0
- package/assets/profiles/pwa-capability/offline.md +11 -0
- package/assets/profiles/pwa-capability/testing.md +15 -0
- package/assets/profiles/react-web-platform/README.md +24 -0
- package/assets/profiles/react-web-platform/SKILL.md +18 -0
- package/assets/profiles/react-web-platform/adoption.md +17 -0
- package/assets/profiles/react-web-platform/guardrails.md +17 -0
- package/assets/profiles/react-web-platform/structure.md +36 -0
- package/assets/profiles/react-web-platform/testing.md +15 -0
- package/assets/profiles/web-bff/README.md +22 -0
- package/assets/profiles/web-bff/SKILL.md +16 -0
- package/assets/profiles/web-bff/adoption.md +9 -0
- package/assets/profiles/web-bff/security.md +16 -0
- package/assets/profiles/web-bff/testing.md +14 -0
- package/assets/schemas/lighthouse-manifest.schema.json +10 -7
- package/assets/schemas/profile-catalog.schema.json +329 -0
- package/package.json +5 -2
- package/src/create-dotnet.mjs +112 -0
- package/src/lighthouse.mjs +109 -14
- package/src/profiles.mjs +206 -0
- package/src/verify-dotnet.mjs +85 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# React web structure
|
|
2
|
+
|
|
3
|
+
Keep the repository source under `src/` and keep the primary architecture feature-oriented.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
src/
|
|
7
|
+
app/ routes and application shell
|
|
8
|
+
features/
|
|
9
|
+
<bounded-context>/
|
|
10
|
+
<feature>/
|
|
11
|
+
screen/
|
|
12
|
+
api/
|
|
13
|
+
queries/
|
|
14
|
+
mutations/
|
|
15
|
+
components/
|
|
16
|
+
types/
|
|
17
|
+
tests/
|
|
18
|
+
shared/
|
|
19
|
+
http/
|
|
20
|
+
auth/
|
|
21
|
+
query/
|
|
22
|
+
errors/
|
|
23
|
+
telemetry/
|
|
24
|
+
api/
|
|
25
|
+
i18n/
|
|
26
|
+
design-system/
|
|
27
|
+
tests/ browser and cross-feature evidence
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Routes compose screens and policy; they do not become a second application layer.
|
|
31
|
+
|
|
32
|
+
Features follow bounded contexts and use cases. Extract shared code only when more than one feature has a real common contract.
|
|
33
|
+
|
|
34
|
+
The API client exposes typed contracts and maps transport failures into application errors without leaking transport details into screens.
|
|
35
|
+
|
|
36
|
+
The profile can run as a Vite SPA or another approved React framework when its output and verification commands remain explicit.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# React web testing
|
|
2
|
+
|
|
3
|
+
Use a short feedback loop: typecheck and unit tests first, then integration and browser evidence for the behavior that crosses boundaries.
|
|
4
|
+
|
|
5
|
+
| Layer | Purpose | Evidence |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Typecheck and lint | Catch static contract and style errors | CI output |
|
|
8
|
+
| Unit and component | Exercise feature behavior and interaction states | Vitest and Testing Library |
|
|
9
|
+
| Integration | Exercise API and BFF contracts where a boundary matters | Project-selected test harness |
|
|
10
|
+
| Browser | Prove login, navigation, query, mutation and failure states | Playwright with catalogued evidence |
|
|
11
|
+
| Accessibility | Catch critical semantic and keyboard regressions | axe plus human review when required |
|
|
12
|
+
|
|
13
|
+
The first vertical slice covers session, an authenticated screen, a query, a mutation, loading, empty, error and authentication failure.
|
|
14
|
+
|
|
15
|
+
Keep browser journeys few and valuable. Do not replace application tests with end-to-end tests or mock away the boundary being verified.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Lighthouse web BFF profile
|
|
2
|
+
|
|
3
|
+
Use this profile for authenticated web applications that need a same-origin browser session and a server-side boundary for tokens and API calls.
|
|
4
|
+
|
|
5
|
+
## Start here
|
|
6
|
+
|
|
7
|
+
| Need | Read |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| New private web application | [adoption](adoption.md) → [security](security.md) → [testing](testing.md) |
|
|
10
|
+
| Existing web application | [adoption](adoption.md) → [security](security.md) |
|
|
11
|
+
| Implement a session change | [security](security.md) → [testing](testing.md) |
|
|
12
|
+
| Diagnose an authentication failure | [security](security.md) |
|
|
13
|
+
|
|
14
|
+
## Defaults
|
|
15
|
+
|
|
16
|
+
Keep OAuth tokens outside browser JavaScript and use a Secure, HttpOnly session cookie with an explicit SameSite policy.
|
|
17
|
+
|
|
18
|
+
Use Authorization Code with PKCE, CSRF protection, logout and revocation, destination and route allowlists, correlation IDs, ProblemDetails and rate limiting.
|
|
19
|
+
|
|
20
|
+
Keep the BFF inside the ASP.NET Core host unless a product decision requires another deployment boundary.
|
|
21
|
+
|
|
22
|
+
Treat direct browser-to-API access as a recorded exception for public or otherwise suitable APIs.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lighthouse-web-bff
|
|
3
|
+
description: Apply the Lighthouse web BFF profile when a repository selects web-bff for an authenticated web application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lighthouse web BFF profile
|
|
7
|
+
|
|
8
|
+
Read the profile guide before changing an authenticated web adopter.
|
|
9
|
+
|
|
10
|
+
Keep OAuth tokens on the server side. Use Authorization Code with PKCE and a Secure, HttpOnly session cookie with an explicit SameSite policy.
|
|
11
|
+
|
|
12
|
+
Validate origin and CSRF state, allow only declared destinations and routes, and expose stable ProblemDetails with correlation IDs.
|
|
13
|
+
|
|
14
|
+
Implement logout, revocation, expiry and user switching as tested behavior. Keep rate limiting and observability at the BFF boundary.
|
|
15
|
+
|
|
16
|
+
Run the session, API, browser and security checks after a focused change. Treat a missing security check as a repair action.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Adopt the Lighthouse web BFF profile
|
|
2
|
+
|
|
3
|
+
Select `web-bff` with `react-web-platform` and `dotnet-backend` for an authenticated private web application.
|
|
4
|
+
|
|
5
|
+
Set the session, identity provider, callback routes, logout route, allowed destinations and CSRF policy before exposing an authenticated screen.
|
|
6
|
+
|
|
7
|
+
Start with one vertical slice: sign in, load the session, call one API query, submit one mutation, handle expiry and sign out.
|
|
8
|
+
|
|
9
|
+
Existing authentication code remains product-owned. Map its behavior to the profile and record a narrow exception when migration would change a security boundary.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Web BFF security contract
|
|
2
|
+
|
|
3
|
+
| Boundary | Contract | Required evidence |
|
|
4
|
+
| --- | --- | --- |
|
|
5
|
+
| Browser session | Secure, HttpOnly cookie with explicit SameSite policy | Cookie inspection and browser session test |
|
|
6
|
+
| Authorization | Authorization Code with PKCE | Provider configuration and callback test |
|
|
7
|
+
| CSRF | Origin and request-token policy for state-changing requests | Negative and positive mutation tests |
|
|
8
|
+
| Destinations | Explicit allowlist for outbound targets and routes | Rejected destination test |
|
|
9
|
+
| Proxying | Fixed routes and typed upstream calls | No arbitrary proxy path |
|
|
10
|
+
| Logout | Session removal and provider revocation when supported | Logout and expired-session test |
|
|
11
|
+
| Failure | Stable ProblemDetails and correlation ID | Error response and trace evidence |
|
|
12
|
+
| Abuse | Rate limiting at the boundary | Configured limit and response test |
|
|
13
|
+
|
|
14
|
+
Do not place access or refresh tokens in localStorage, sessionStorage or IndexedDB.
|
|
15
|
+
|
|
16
|
+
The BFF may be hosted inside the ASP.NET Core application host. A separate service requires a product and architecture decision with its own deployment evidence.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Web BFF testing
|
|
2
|
+
|
|
3
|
+
Test the session boundary before the browser journey: callback, cookie flags, CSRF, allowlists, logout, expiry and user switching.
|
|
4
|
+
|
|
5
|
+
| Layer | Purpose | Evidence |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Unit | Validate policy and mapping rules | Deterministic test output |
|
|
8
|
+
| Integration | Exercise the BFF and upstream contract | Real host or approved provider fixture |
|
|
9
|
+
| Browser | Prove sign in, authenticated query, mutation and sign out | Playwright evidence |
|
|
10
|
+
| Security review | Confirm storage, routes and failure handling | axe is complementary; security review remains explicit |
|
|
11
|
+
|
|
12
|
+
The first vertical slice must fail closed when the session is absent, expired or assigned to another user.
|
|
13
|
+
|
|
14
|
+
Keep provider credentials out of fixtures. Use a deterministic identity or approved test provider that exercises the same cookie and callback boundaries.
|
|
@@ -10,16 +10,19 @@
|
|
|
10
10
|
"schemaVersion": {"const": 1},
|
|
11
11
|
"baseline": {"const": "0.1.0"},
|
|
12
12
|
"product": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$"},
|
|
13
|
+
"application": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"required": ["shape", "stacks"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"shape": {"enum": ["unknown", "frontend", "backend", "full-stack", "service", "library", "tooling", "cli", "worker", "mobile"]},
|
|
19
|
+
"stacks": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
13
22
|
"profiles": {"type": "array", "items": {"enum": ["node-tooling", "dotnet-backend", "visual-theme"]}, "uniqueItems": true},
|
|
14
23
|
"integrations": {"type": "object", "additionalProperties": false, "required": ["productShape", "arcShape"], "properties": {"productShape": {"$ref": "#/$defs/pin"}, "arcShape": {"$ref": "#/$defs/pin"}}},
|
|
15
24
|
"packages": {"type": "object", "additionalProperties": false, "required": ["sdlc"], "properties": {"sdlc": {"type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": {"name": {"const": "@juangcarmona/lighthouse-sdlc"}, "version": {"$ref": "#/$defs/pin"}}}, "ui": {"type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": {"name": {"const": "@juangcarmona/lighthouse-ui"}, "version": {"$ref": "#/$defs/pin"}}}}},
|
|
16
|
-
"managed": {"
|
|
17
|
-
{"const": ["LIGHTHOUSE.md"]},
|
|
18
|
-
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml"]},
|
|
19
|
-
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml", ".lighthouse/sdlc.json"]},
|
|
20
|
-
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml", ".lighthouse/sdlc.json", ".lighthouse/upgrade.json"]},
|
|
21
|
-
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml", ".lighthouse/sdlc.json", ".lighthouse/upgrade.json", "docs/design/DESIGN.md", ".agents/skills/lighthouse-ui/SKILL.md"]}
|
|
22
|
-
]},
|
|
25
|
+
"managed": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}, "uniqueItems": true},
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "object",
|
|
25
28
|
"additionalProperties": false,
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/juangcarmona/lighthouse/schemas/profile-catalog.schema.json",
|
|
4
|
+
"title": "Lighthouse profile catalog",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"$schema",
|
|
9
|
+
"schemaVersion",
|
|
10
|
+
"profiles"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"$schema": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"schemaVersion": {
|
|
17
|
+
"const": 1
|
|
18
|
+
},
|
|
19
|
+
"profiles": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"$ref": "#/$defs/profile"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"$defs": {
|
|
27
|
+
"profile": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"required": [
|
|
31
|
+
"id",
|
|
32
|
+
"owner",
|
|
33
|
+
"purpose",
|
|
34
|
+
"appliesTo",
|
|
35
|
+
"surfaces",
|
|
36
|
+
"prerequisites",
|
|
37
|
+
"dependencies",
|
|
38
|
+
"incompatibleWith",
|
|
39
|
+
"libraries",
|
|
40
|
+
"structure",
|
|
41
|
+
"commands",
|
|
42
|
+
"guardrails",
|
|
43
|
+
"failureConsequence",
|
|
44
|
+
"repairHints",
|
|
45
|
+
"exceptions",
|
|
46
|
+
"combinations",
|
|
47
|
+
"consumerFixture",
|
|
48
|
+
"upgradeStrategy",
|
|
49
|
+
"verification"
|
|
50
|
+
],
|
|
51
|
+
"properties": {
|
|
52
|
+
"id": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
55
|
+
},
|
|
56
|
+
"owner": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 1
|
|
59
|
+
},
|
|
60
|
+
"purpose": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"minLength": 1
|
|
63
|
+
},
|
|
64
|
+
"appliesTo": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"required": [
|
|
68
|
+
"shapes",
|
|
69
|
+
"stacks"
|
|
70
|
+
],
|
|
71
|
+
"properties": {
|
|
72
|
+
"shapes": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
"minItems": 1,
|
|
78
|
+
"uniqueItems": true
|
|
79
|
+
},
|
|
80
|
+
"stacks": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "string"
|
|
84
|
+
},
|
|
85
|
+
"uniqueItems": true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"surfaces": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1
|
|
94
|
+
},
|
|
95
|
+
"minItems": 1,
|
|
96
|
+
"uniqueItems": true
|
|
97
|
+
},
|
|
98
|
+
"verification": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"required": [
|
|
102
|
+
"layers"
|
|
103
|
+
],
|
|
104
|
+
"properties": {
|
|
105
|
+
"layers": {
|
|
106
|
+
"type": "array",
|
|
107
|
+
"items": {
|
|
108
|
+
"$ref": "#/$defs/layer"
|
|
109
|
+
},
|
|
110
|
+
"minItems": 1,
|
|
111
|
+
"uniqueItems": true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"prerequisites": {
|
|
116
|
+
"type": "array",
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"minLength": 1
|
|
120
|
+
},
|
|
121
|
+
"uniqueItems": true
|
|
122
|
+
},
|
|
123
|
+
"dependencies": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"items": {
|
|
126
|
+
"$ref": "#/$defs/profileId"
|
|
127
|
+
},
|
|
128
|
+
"uniqueItems": true
|
|
129
|
+
},
|
|
130
|
+
"incompatibleWith": {
|
|
131
|
+
"type": "array",
|
|
132
|
+
"items": {
|
|
133
|
+
"$ref": "#/$defs/profileId"
|
|
134
|
+
},
|
|
135
|
+
"uniqueItems": true
|
|
136
|
+
},
|
|
137
|
+
"libraries": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"minLength": 1
|
|
142
|
+
},
|
|
143
|
+
"uniqueItems": true
|
|
144
|
+
},
|
|
145
|
+
"structure": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"minLength": 1
|
|
150
|
+
},
|
|
151
|
+
"uniqueItems": true
|
|
152
|
+
},
|
|
153
|
+
"commands": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"items": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"minLength": 1
|
|
158
|
+
},
|
|
159
|
+
"uniqueItems": true
|
|
160
|
+
},
|
|
161
|
+
"guardrails": {
|
|
162
|
+
"type": "array",
|
|
163
|
+
"items": {
|
|
164
|
+
"$ref": "#/$defs/guardrail"
|
|
165
|
+
},
|
|
166
|
+
"uniqueItems": true
|
|
167
|
+
},
|
|
168
|
+
"failureConsequence": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"minLength": 1
|
|
171
|
+
},
|
|
172
|
+
"repairHints": {
|
|
173
|
+
"type": "array",
|
|
174
|
+
"items": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1
|
|
177
|
+
},
|
|
178
|
+
"minItems": 1
|
|
179
|
+
},
|
|
180
|
+
"exceptions": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"minLength": 1
|
|
185
|
+
},
|
|
186
|
+
"minItems": 1
|
|
187
|
+
},
|
|
188
|
+
"combinations": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"items": {
|
|
191
|
+
"$ref": "#/$defs/combination"
|
|
192
|
+
},
|
|
193
|
+
"uniqueItems": true
|
|
194
|
+
},
|
|
195
|
+
"consumerFixture": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"minLength": 1
|
|
198
|
+
},
|
|
199
|
+
"upgradeStrategy": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"minLength": 1
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"layer": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"additionalProperties": false,
|
|
208
|
+
"required": [
|
|
209
|
+
"id",
|
|
210
|
+
"level",
|
|
211
|
+
"kind",
|
|
212
|
+
"verification"
|
|
213
|
+
],
|
|
214
|
+
"properties": {
|
|
215
|
+
"id": {
|
|
216
|
+
"enum": [
|
|
217
|
+
"static",
|
|
218
|
+
"unit",
|
|
219
|
+
"architecture",
|
|
220
|
+
"integration",
|
|
221
|
+
"functional",
|
|
222
|
+
"browser-e2e",
|
|
223
|
+
"operational"
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
"level": {
|
|
227
|
+
"enum": [
|
|
228
|
+
"required",
|
|
229
|
+
"recommended",
|
|
230
|
+
"optional"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"kind": {
|
|
234
|
+
"enum": [
|
|
235
|
+
"hard",
|
|
236
|
+
"soft"
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
"verification": {
|
|
240
|
+
"enum": [
|
|
241
|
+
"deterministic",
|
|
242
|
+
"semantic",
|
|
243
|
+
"human"
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
"command": {
|
|
247
|
+
"type": [
|
|
248
|
+
"string",
|
|
249
|
+
"null"
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"profileId": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
257
|
+
},
|
|
258
|
+
"guardrail": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"additionalProperties": false,
|
|
261
|
+
"required": [
|
|
262
|
+
"id",
|
|
263
|
+
"level",
|
|
264
|
+
"kind",
|
|
265
|
+
"verification",
|
|
266
|
+
"command",
|
|
267
|
+
"description"
|
|
268
|
+
],
|
|
269
|
+
"properties": {
|
|
270
|
+
"id": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"minLength": 1
|
|
273
|
+
},
|
|
274
|
+
"level": {
|
|
275
|
+
"enum": [
|
|
276
|
+
"required",
|
|
277
|
+
"recommended",
|
|
278
|
+
"optional"
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
"kind": {
|
|
282
|
+
"enum": [
|
|
283
|
+
"hard",
|
|
284
|
+
"soft"
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
"verification": {
|
|
288
|
+
"enum": [
|
|
289
|
+
"deterministic",
|
|
290
|
+
"semantic",
|
|
291
|
+
"human"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
"command": {
|
|
295
|
+
"type": [
|
|
296
|
+
"string",
|
|
297
|
+
"null"
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
"description": {
|
|
301
|
+
"type": "string",
|
|
302
|
+
"minLength": 1
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"combination": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"additionalProperties": false,
|
|
309
|
+
"required": [
|
|
310
|
+
"with",
|
|
311
|
+
"purpose"
|
|
312
|
+
],
|
|
313
|
+
"properties": {
|
|
314
|
+
"with": {
|
|
315
|
+
"type": "array",
|
|
316
|
+
"items": {
|
|
317
|
+
"$ref": "#/$defs/profileId"
|
|
318
|
+
},
|
|
319
|
+
"minItems": 1,
|
|
320
|
+
"uniqueItems": true
|
|
321
|
+
},
|
|
322
|
+
"purpose": {
|
|
323
|
+
"type": "string",
|
|
324
|
+
"minLength": 1
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juangcarmona/lighthouse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lighthouse adoption and verification CLI",
|
|
6
6
|
"repository": {
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
"@arcshape/cli": "0.1.3",
|
|
24
24
|
"@juangcarmona/lighthouse-ui": "0.2.0",
|
|
25
25
|
"@prodshape/cli": "0.22.0",
|
|
26
|
-
"@juangcarmona/lighthouse-sdlc": "0.
|
|
26
|
+
"@juangcarmona/lighthouse-sdlc": "0.6.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src/lighthouse.mjs",
|
|
30
30
|
"src/guardrails.mjs",
|
|
31
31
|
"assets",
|
|
32
32
|
"README.md",
|
|
33
|
+
"src/profiles.mjs",
|
|
34
|
+
"src/create-dotnet.mjs",
|
|
35
|
+
"src/verify-dotnet.mjs",
|
|
33
36
|
"upgrades"
|
|
34
37
|
],
|
|
35
38
|
"bin": {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
|
|
7
|
+
const packageRoot = fileURLToPath(new URL('../', import.meta.url));
|
|
8
|
+
const templateRoot = resolve(packageRoot, 'assets/dotnet-template');
|
|
9
|
+
const cliPackage = JSON.parse(await readFile(resolve(packageRoot, 'package.json'), 'utf8'));
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const sdlcContract = JSON.parse(await readFile(require.resolve('@juangcarmona/lighthouse-sdlc/contract.json'), 'utf8'));
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const target = args[0] ? resolve(args[0]) : null;
|
|
14
|
+
const name = args.includes('--name') ? args[args.indexOf('--name') + 1] : null;
|
|
15
|
+
|
|
16
|
+
if (!target || !name || !/^[A-Z][A-Za-z0-9]{1,47}$/.test(name) || args.length !== 3 || args[1] !== '--name') {
|
|
17
|
+
process.stderr.write('Usage: lighthouse create-dotnet <empty-target> --name Workshop\nUse a PascalCase product identifier of 2–48 letters/digits.\n');
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let existing = false;
|
|
22
|
+
try {
|
|
23
|
+
const info = await lstat(target);
|
|
24
|
+
if (!info.isDirectory() || info.isSymbolicLink() || (await readdir(target)).length > 0) throw new Error('The target must be a new directory or an empty directory.');
|
|
25
|
+
existing = true;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code !== 'ENOENT') throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const transform = (text) => text
|
|
31
|
+
.replaceAll('JuanGCarmona.Lighthouse.Example', `JuanGCarmona.${name}`)
|
|
32
|
+
.replaceAll('Lighthouse.Example', name)
|
|
33
|
+
.replaceAll('Lighthouse_Example', name)
|
|
34
|
+
.replaceAll('../../../../Lighthouse.Cqrs/', '../../dotnet/Lighthouse.Cqrs/')
|
|
35
|
+
.replaceAll('../../../../Lighthouse.Analyzers/', '../../dotnet/Lighthouse.Analyzers/')
|
|
36
|
+
.replaceAll('../../Lighthouse.Analyzers/', '../../../src/dotnet/Lighthouse.Analyzers/')
|
|
37
|
+
.replaceAll('../../examples/src/root/', '../../../src/root/');
|
|
38
|
+
|
|
39
|
+
async function copy(from, to) {
|
|
40
|
+
const info = await lstat(from);
|
|
41
|
+
if (info.isDirectory()) {
|
|
42
|
+
await mkdir(to, { recursive: true });
|
|
43
|
+
for (const entry of await readdir(from)) {
|
|
44
|
+
if (['bin', 'obj'].includes(entry)) continue;
|
|
45
|
+
await copy(join(from, entry), join(to, entry.replaceAll('Lighthouse.Example', name)));
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!info.isFile()) throw new Error('Starter sources must be ordinary files and directories.');
|
|
50
|
+
await mkdir(dirname(to), { recursive: true });
|
|
51
|
+
await writeFile(to, transform(await readFile(from, 'utf8')));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stage = await mkdtemp(join(dirname(target), '.lighthouse-create-dotnet-'));
|
|
55
|
+
try {
|
|
56
|
+
for (const [from, to] of [
|
|
57
|
+
['Lighthouse.Analyzers', 'src/dotnet/Lighthouse.Analyzers'],
|
|
58
|
+
['Lighthouse.Cqrs', 'src/dotnet/Lighthouse.Cqrs'],
|
|
59
|
+
['examples/src', 'src'],
|
|
60
|
+
['tests', 'tests/dotnet'],
|
|
61
|
+
['Directory.Build.props', 'Directory.Build.props'],
|
|
62
|
+
['Directory.Packages.props', 'Directory.Packages.props'],
|
|
63
|
+
['global.json', 'global.json'],
|
|
64
|
+
['Lighthouse.slnx', `${name}.slnx`],
|
|
65
|
+
]) await copy(resolve(templateRoot, from), resolve(stage, to));
|
|
66
|
+
|
|
67
|
+
const solutionFile = resolve(stage, `${name}.slnx`);
|
|
68
|
+
const solutionText = (await readFile(solutionFile, 'utf8'))
|
|
69
|
+
.replaceAll('examples/src/', 'src/')
|
|
70
|
+
.replaceAll('Lighthouse.Analyzers/', 'src/dotnet/Lighthouse.Analyzers/')
|
|
71
|
+
.replaceAll('Lighthouse.Cqrs/', 'src/dotnet/Lighthouse.Cqrs/')
|
|
72
|
+
.replaceAll('tests/', 'tests/dotnet/');
|
|
73
|
+
const projects = [...solutionText.matchAll(/<Project Path="[^"]+"\s*\/>/g)].map((match) => match[0]);
|
|
74
|
+
await writeFile(solutionFile, `<Solution>\n${projects.map((project) => ` ${project}`).join('\n')}\n</Solution>\n`);
|
|
75
|
+
|
|
76
|
+
await copy(resolve(packageRoot, 'src/lighthouse.mjs'), resolve(stage, 'scripts/lighthouse.mjs'));
|
|
77
|
+
await copy(resolve(packageRoot, 'src/guardrails.mjs'), resolve(stage, 'scripts/guardrails.mjs'));
|
|
78
|
+
await copy(resolve(packageRoot, 'src/profiles.mjs'), resolve(stage, 'scripts/profiles.mjs'));
|
|
79
|
+
await copy(resolve(packageRoot, 'src/verify-dotnet.mjs'), resolve(stage, 'scripts/verify-dotnet.mjs'));
|
|
80
|
+
await copy(resolve(packageRoot, 'assets/guardrails'), resolve(stage, 'assets/guardrails'));
|
|
81
|
+
await copy(resolve(packageRoot, 'assets/profiles'), resolve(stage, 'assets/profiles'));
|
|
82
|
+
await copy(resolve(packageRoot, 'assets/schemas'), resolve(stage, 'schemas'));
|
|
83
|
+
|
|
84
|
+
await writeFile(resolve(stage, 'lighthouse.json'), `${JSON.stringify({
|
|
85
|
+
$schema: './schemas/lighthouse-manifest.schema.json', schemaVersion: 1, baseline: '0.1.0',
|
|
86
|
+
product: name, application: { shape: 'backend', stacks: ['dotnet'] }, profiles: ['dotnet-backend'],
|
|
87
|
+
integrations: { productShape: null, arcShape: null },
|
|
88
|
+
packages: { sdlc: { name: '@juangcarmona/lighthouse-sdlc', version: sdlcContract.version } },
|
|
89
|
+
managed: ['LIGHTHOUSE.md', 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', '.lighthouse/upgrade.json', 'docs/profiles/dotnet/README.md', 'docs/profiles/dotnet/adoption.md', 'docs/profiles/dotnet/architecture.md', 'docs/profiles/dotnet/guardrails.md', 'docs/profiles/dotnet/practices.md', 'docs/profiles/dotnet/testing-and-development.md', 'docs/profiles/dotnet/use-cases.md', '.agents/skills/lighthouse-dotnet/SKILL.md'],
|
|
90
|
+
repository: {
|
|
91
|
+
layout: ['src', 'docs', 'infra', 'scripts'], publicSite: { enabled: true },
|
|
92
|
+
conventions: { commits: 'conventional', branchPattern: '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$' },
|
|
93
|
+
},
|
|
94
|
+
}, null, 2)}\n`);
|
|
95
|
+
await writeFile(resolve(stage, 'package.json'), `${JSON.stringify({
|
|
96
|
+
name: `@juangcarmona/${name.toLowerCase()}`, private: true, type: 'module', engines: { node: '>=22' },
|
|
97
|
+
scripts: { verify: 'node scripts/lighthouse.mjs verify && node scripts/verify-dotnet.mjs' },
|
|
98
|
+
}, null, 2)}\n`);
|
|
99
|
+
await writeFile(resolve(stage, 'README.md'), `# ${name}\n\nBackend seeded from Lighthouse ${cliPackage.version}. Application and tooling sources are product-owned after creation.\n\nRun dotnet restore ${name}.slnx once to create lockfiles, then npm run verify. Commit lockfiles.\n\nStart dotnet run --project src/root/${name}.Api or the optional src/root/${name}.AppHost. Configure authentication using a trusted OIDC provider for manual API access. Test identities exist only in tests.\n\nThe sample catalog uses disposable in-memory storage. Add real persistence, data isolation, telemetry and deployment policy according to product requirements. Keep each command/query's endpoint, DTOs, validation and internal sealed handler together.\n`);
|
|
100
|
+
await writeFile(resolve(stage, 'AGENTS.md'), `# ${name} engineering guidance\n\nUse the Lighthouse .NET profile from the pinned baseline revision. Keep Minimal API commands/queries as complete single-file slices. Handlers remain internal sealed and independent of HTTP. Modules use sibling Contracts assemblies. Run npm run verify.\n\nApplication and tooling source is product-owned. Do not hand-edit LIGHTHOUSE.md. Record narrow, expiring exceptions for suppressed diagnostics.\n`);
|
|
101
|
+
await mkdir(resolve(stage, '.github/workflows'), { recursive: true });
|
|
102
|
+
await writeFile(resolve(stage, '.github/workflows/ci.yml'), `name: Backend verification\non: [push, pull_request]\npermissions:\n contents: read\njobs:\n dotnet:\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n - uses: actions/setup-dotnet@v4\n with:\n global-json-file: global.json\n - run: npm run verify\n`);
|
|
103
|
+
|
|
104
|
+
const initialized = (await import('node:child_process')).spawnSync(process.execPath, [resolve(stage, 'scripts/lighthouse.mjs'), 'init'], { cwd: stage, encoding: 'utf8' });
|
|
105
|
+
if (initialized.status !== 0) throw new Error(initialized.stderr || 'Projection generation failed.');
|
|
106
|
+
if (existing) await rmdir(target);
|
|
107
|
+
await rename(stage, target);
|
|
108
|
+
process.stdout.write(`Created ${name} in ${target}. Run dotnet restore ${name}.slnx, then npm run verify.\n`);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
await rm(stage, { recursive: true, force: true });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|