@polymorphism-tech/morph-spec 9.1.0 → 9.3.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/CLAUDE.md +14 -78
- package/README.md +2 -2
- package/bin/morph-spec.js +14 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +7 -10
- package/framework/agents.json +15 -12
- package/framework/commands/morph-status.md +2 -2
- package/framework/docs/CLI.md +24 -16
- package/framework/evals/review-checklist.md +15 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/state-sync.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +23 -0
- package/framework/hooks/claude-code/statusline.py +3 -2
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/gate-event.js +18 -0
- package/framework/hooks/shared/next-step.js +107 -0
- package/framework/hooks/shared/slice-progress.js +18 -5
- package/framework/hooks/shared/state-writer.js +6 -1
- package/framework/mcp/registry.json +31 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/schemas/feature.schema.json +1 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +6 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +8 -6
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +2 -1
- package/framework/standards/STANDARDS.json +2 -2
- package/framework/standards/STANDARDS.meta.json +1 -1
- package/framework/standards/ai-agents/setup.md +2 -0
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/integration/mcp/mcp-tools.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/observability/cost.js +12 -0
- package/src/commands/project/status.js +6 -68
- package/src/commands/state/advance.js +1 -1
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/gate-check.js +2 -2
- package/src/commands/state/gate-decision.js +4 -1
- package/src/commands/state/next.js +24 -0
- package/src/commands/state/report-check.js +3 -1
- package/src/commands/validation/mutate.js +9 -6
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +4 -2
- package/src/commands/validation/verify.js +21 -16
- package/src/core/state/state-manager.js +4 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/FeatureDetail.jsx +6 -2
- package/src/dashboard/scanner/features-scanner.js +5 -2
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ci/workflow-scaffold.js +12 -0
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/generators/handoff-generator.js +11 -25
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/next-step.js +10 -0
- package/src/lib/plan/checks.js +29 -2
- package/src/lib/plan/plan-parser.js +6 -2
- package/src/lib/review-reports.js +6 -3
- package/src/lib/risk-detector.js +20 -7
- package/src/lib/stack/stack-scan.js +13 -1
- package/src/lib/telemetry/metrics.js +46 -0
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/validation-runner.js +6 -38
- package/src/lib/validators/verify-runner.js +37 -22
- package/src/utils/agents-installer.js +24 -1
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
|
@@ -17,7 +17,7 @@ Vitest + React Testing Library for component tests. MSW for API mocking. Co-loca
|
|
|
17
17
|
- ALWAYS use `@testing-library/user-event` for interactions — not `fireEvent`
|
|
18
18
|
- ALWAYS mock the API layer with MSW — never mock `fetch` directly
|
|
19
19
|
- NEVER test implementation details — test what the user sees
|
|
20
|
-
-
|
|
20
|
+
- ONLY test a component when a plan scenario with `risco:` lives in it — a component is not a unit of the budget
|
|
21
21
|
|
|
22
22
|
## Test File Co-location
|
|
23
23
|
|
|
@@ -103,9 +103,13 @@ export function QueryClientWrapper({ children }: { children: React.ReactNode })
|
|
|
103
103
|
|
|
104
104
|
## Test Budget
|
|
105
105
|
|
|
106
|
-
The plan (`morph-plan`)
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
Tests are paid for by **risk, not by new code**. The plan (`morph-plan`) marks each scenario
|
|
107
|
+
with `prova:` and, for a test, a `risco:` (business rule, money, external contract, concurrency,
|
|
108
|
+
data integrity, regression). The budget is **one test per risky scenario, plus the regression test of
|
|
109
|
+
the bug being fixed**; going above it takes a one-line justification on the scenario. A scenario
|
|
110
|
+
without risk — CRUD, DTO, mapping, wiring, constants, layout — is `prova: leitura`: the Gate 3
|
|
111
|
+
verifier proves it by reading the code, and it gets no test. Rendering, layout and copy are
|
|
112
|
+
`leitura` or `e2e`, never a unit test per component.
|
|
109
113
|
|
|
110
114
|
- **Table before copy.** Scenarios of the same behavior — edge cases included — are rows of one
|
|
111
115
|
`it.each`/`test.each`, never N `it()` blocks that differ by one value.
|
|
@@ -116,8 +120,8 @@ happy path + one error path per component above is that budget applied to a comp
|
|
|
116
120
|
- **Shared helpers by default.** Reuse the existing wrappers (`test/helpers.tsx`), MSW handlers and
|
|
117
121
|
factories. Before writing a new one, look for it (`morph-spec graph explain <Name>`; fallback
|
|
118
122
|
without a graph: `grep -rn`).
|
|
119
|
-
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and
|
|
120
|
-
(
|
|
123
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and a risky
|
|
124
|
+
scenario (a formatter of money, a reducer with rules, a schema) gets its test, in whatever order the code comes out. The
|
|
121
125
|
implementer does not run red→green cycles nor mutate production code to watch a test fail; it
|
|
122
126
|
runs its own filter once before the report, and the Gate 3 mutation proof shows the test bites.
|
|
123
127
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Keywords:** mcp, model-context-protocol, mcp-tools, context7, playwright, mcp-client
|
|
6
6
|
> **Load When:** an MCP tool is used in any morph-spec phase
|
|
7
7
|
|
|
8
|
-
**Verified against:** Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `neonctl
|
|
8
|
+
**Verified against:** Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `neonctl`; Microsoft Learn MCP (`https://learn.microsoft.com/api/mcp`, tools listed by `tools/list` on 2026-10-17). Last-verified: 2026-10-17.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -138,6 +138,7 @@ Fallback: manual image creation, stock photos, or placeholder divs.
|
|
|
138
138
|
|------|----------|---------|
|
|
139
139
|
| **Neon CLI (`neonctl`) / psql** | Database schema, tables, relationships, RLS | `psql <DB_URL> -c "SELECT ..."` |
|
|
140
140
|
| **Context7** | Library docs for architecture decisions | `mcp__context7__query-docs()` |
|
|
141
|
+
| **Microsoft Learn** (stack .NET) | Official docs for a .NET/EF Core/MAF decision (limits, what is new, migration) | `mcp__microsoft-learn__microsoft_docs_search({ query })` |
|
|
141
142
|
| **GitHub CLI (`gh`)** | Check existing code patterns, PRs | `gh search code` (Bash) |
|
|
142
143
|
|
|
143
144
|
```bash
|
|
@@ -213,6 +214,7 @@ gh issue create --title "F1: Create Entity Lead" \
|
|
|
213
214
|
| **Neon CLI (`neonctl`) / psql** | Run migrations, create RLS policies | `dotnet ef database update` |
|
|
214
215
|
| **GitHub CLI (`gh`)** | Create PR, push branches | `gh pr create` (Bash) |
|
|
215
216
|
| **Context7** | Look up API usage during coding | `mcp__context7__query-docs()` |
|
|
217
|
+
| **Microsoft Learn** (stack .NET) | Current .NET 10 / EF Core / ASP.NET Core / MAF API: search, then fetch the page when the excerpt is cut | `mcp__microsoft-learn__microsoft_docs_search({ query })` → `microsoft_docs_fetch({ url })`; `microsoft_code_sample_search` for snippets |
|
|
216
218
|
| **Playwright** | Smoke test deployed features, verify UI | `mcp__playwright__browser_navigate()` |
|
|
217
219
|
| **Vercel** | Deploy, check logs, manage env vars | `mcp__vercel__list_projects()` |
|
|
218
220
|
| **Azure CLI** | Resource management, deployments | `az` commands |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<templateSet group="MORPH Templates">
|
|
2
|
+
<template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
|
|
3
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
4
|
+
<context>
|
|
5
|
+
<option name="OTHER" value="true" />
|
|
6
|
+
</context>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
|
|
10
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
11
|
+
<context>
|
|
12
|
+
<option name="OTHER" value="true" />
|
|
13
|
+
</context>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
|
|
17
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
18
|
+
<context>
|
|
19
|
+
<option name="OTHER" value="true" />
|
|
20
|
+
</context>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
|
|
24
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
25
|
+
<context>
|
|
26
|
+
<option name="OTHER" value="true" />
|
|
27
|
+
</context>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
|
|
31
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
32
|
+
<context>
|
|
33
|
+
<option name="OTHER" value="true" />
|
|
34
|
+
</context>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<template name="morph-if" value="{{#if $CONDITION$}} $END$ {{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
|
|
38
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
39
|
+
<context>
|
|
40
|
+
<option name="OTHER" value="true" />
|
|
41
|
+
</context>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template name="morph-if-else" value="{{#if $CONDITION$}} $TRUE_BRANCH$ {{else}} $FALSE_BRANCH$ {{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
|
|
45
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
46
|
+
<variable name="TRUE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
47
|
+
<variable name="FALSE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
48
|
+
<context>
|
|
49
|
+
<option name="OTHER" value="true" />
|
|
50
|
+
</context>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<template name="morph-each" value="{{#each $ITEMS$}} {{this.$PROPERTY$}} {{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
|
|
54
|
+
<variable name="ITEMS" expression="" defaultValue=""items"" alwaysStopAt="true" />
|
|
55
|
+
<variable name="PROPERTY" expression="" defaultValue=""name"" alwaysStopAt="true" />
|
|
56
|
+
<context>
|
|
57
|
+
<option name="OTHER" value="true" />
|
|
58
|
+
</context>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services; public class {{pascalCase FEATURE_NAME}}Service( I{{pascalCase FEATURE_NAME}}Repository repository, ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service { public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
|
|
62
|
+
<context>
|
|
63
|
+
<option name="C_SHARP" value="true" />
|
|
64
|
+
</context>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository { private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>(); public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
|
|
68
|
+
<context>
|
|
69
|
+
<option name="C_SHARP" value="true" />
|
|
70
|
+
</context>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template name="morph-component" value="@page "/{{kebabCase FEATURE_NAME}}" @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service @inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger <PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle> <div class="container-fluid"> $END$ </div> @code { }" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
|
|
74
|
+
<context>
|
|
75
|
+
<option name="HTML" value="true" />
|
|
76
|
+
</context>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "{{pascalCase FEATURE_NAME}}s", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), $END$ }, constraints: table => { table.PrimaryKey("PK_{{pascalCase FEATURE_NAME}}s", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable(name: "{{pascalCase FEATURE_NAME}}s"); } }" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
|
|
80
|
+
<context>
|
|
81
|
+
<option name="C_SHARP" value="true" />
|
|
82
|
+
</context>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} { id: number; $FIELD1$ } export interface Create{{pascalCase FEATURE_NAME}}Request { $FIELD2$ } export interface Update{{pascalCase FEATURE_NAME}}Request { $END$ }" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
|
|
86
|
+
<variable name="FIELD1" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
87
|
+
<variable name="FIELD2" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
88
|
+
<context>
|
|
89
|
+
<option name="TypeScript" value="true" />
|
|
90
|
+
</context>
|
|
91
|
+
</template>
|
|
92
|
+
</templateSet>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"MORPH Template - PascalCase": {
|
|
3
|
+
"prefix": "morph-pascal",
|
|
4
|
+
"body": ["{{pascalCase ${1:FEATURE_NAME}}}"],
|
|
5
|
+
"description": "Convert to PascalCase (UserAuth)"
|
|
6
|
+
},
|
|
7
|
+
"MORPH Template - camelCase": {
|
|
8
|
+
"prefix": "morph-camel",
|
|
9
|
+
"body": ["{{camelCase ${1:FEATURE_NAME}}}"],
|
|
10
|
+
"description": "Convert to camelCase (userAuth)"
|
|
11
|
+
},
|
|
12
|
+
"MORPH Template - snake_case": {
|
|
13
|
+
"prefix": "morph-snake",
|
|
14
|
+
"body": ["{{snakeCase ${1:FEATURE_NAME}}}"],
|
|
15
|
+
"description": "Convert to snake_case (user_auth)"
|
|
16
|
+
},
|
|
17
|
+
"MORPH Template - Title Case": {
|
|
18
|
+
"prefix": "morph-title",
|
|
19
|
+
"body": ["{{titleCase ${1:FEATURE_NAME}}}"],
|
|
20
|
+
"description": "Convert to Title Case (User Auth)"
|
|
21
|
+
},
|
|
22
|
+
"MORPH Template - kebab-case": {
|
|
23
|
+
"prefix": "morph-kebab",
|
|
24
|
+
"body": ["{{kebabCase ${1:FEATURE_NAME}}}"],
|
|
25
|
+
"description": "Convert to kebab-case (user-auth)"
|
|
26
|
+
},
|
|
27
|
+
"MORPH Template - UPPER_SNAKE_CASE": {
|
|
28
|
+
"prefix": "morph-upper-snake",
|
|
29
|
+
"body": ["{{upperSnakeCase ${1:FEATURE_NAME}}}"],
|
|
30
|
+
"description": "Convert to UPPER_SNAKE_CASE (USER_AUTH)"
|
|
31
|
+
},
|
|
32
|
+
"MORPH Template - Conditional If": {
|
|
33
|
+
"prefix": "morph-if",
|
|
34
|
+
"body": [
|
|
35
|
+
"{{#if ${1:condition}}}",
|
|
36
|
+
" $2",
|
|
37
|
+
"{{/if}}"
|
|
38
|
+
],
|
|
39
|
+
"description": "Handlebars if block"
|
|
40
|
+
},
|
|
41
|
+
"MORPH Template - Conditional If-Else": {
|
|
42
|
+
"prefix": "morph-if-else",
|
|
43
|
+
"body": [
|
|
44
|
+
"{{#if ${1:condition}}}",
|
|
45
|
+
" $2",
|
|
46
|
+
"{{else}}",
|
|
47
|
+
" $3",
|
|
48
|
+
"{{/if}}"
|
|
49
|
+
],
|
|
50
|
+
"description": "Handlebars if-else block"
|
|
51
|
+
},
|
|
52
|
+
"MORPH Template - Each Loop": {
|
|
53
|
+
"prefix": "morph-each",
|
|
54
|
+
"body": [
|
|
55
|
+
"{{#each ${1:items}}}",
|
|
56
|
+
" {{this.$2}}",
|
|
57
|
+
"{{/each}}"
|
|
58
|
+
],
|
|
59
|
+
"description": "Handlebars each loop"
|
|
60
|
+
},
|
|
61
|
+
"MORPH Template - Unless": {
|
|
62
|
+
"prefix": "morph-unless",
|
|
63
|
+
"body": [
|
|
64
|
+
"{{#unless ${1:condition}}}",
|
|
65
|
+
" $2",
|
|
66
|
+
"{{/unless}}"
|
|
67
|
+
],
|
|
68
|
+
"description": "Handlebars unless block"
|
|
69
|
+
},
|
|
70
|
+
"MORPH Template - .NET Service Class": {
|
|
71
|
+
"prefix": "morph-service",
|
|
72
|
+
"body": [
|
|
73
|
+
"namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services;",
|
|
74
|
+
"",
|
|
75
|
+
"public class {{pascalCase FEATURE_NAME}}Service(",
|
|
76
|
+
" I{{pascalCase FEATURE_NAME}}Repository repository,",
|
|
77
|
+
" ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service",
|
|
78
|
+
"{",
|
|
79
|
+
" public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
|
|
80
|
+
" {",
|
|
81
|
+
" $1",
|
|
82
|
+
" }",
|
|
83
|
+
"}"
|
|
84
|
+
],
|
|
85
|
+
"description": "MORPH .NET Service class template"
|
|
86
|
+
},
|
|
87
|
+
"MORPH Template - .NET Repository Class": {
|
|
88
|
+
"prefix": "morph-repository",
|
|
89
|
+
"body": [
|
|
90
|
+
"public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository",
|
|
91
|
+
"{",
|
|
92
|
+
" private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>();",
|
|
93
|
+
"",
|
|
94
|
+
" public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
|
|
95
|
+
" {",
|
|
96
|
+
" $1",
|
|
97
|
+
" }",
|
|
98
|
+
"}"
|
|
99
|
+
],
|
|
100
|
+
"description": "MORPH .NET Repository class template"
|
|
101
|
+
},
|
|
102
|
+
"MORPH Template - Blazor Component": {
|
|
103
|
+
"prefix": "morph-component",
|
|
104
|
+
"body": [
|
|
105
|
+
"@page \"/{{kebabCase FEATURE_NAME}}\"",
|
|
106
|
+
"@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service",
|
|
107
|
+
"@inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger",
|
|
108
|
+
"",
|
|
109
|
+
"<PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle>",
|
|
110
|
+
"",
|
|
111
|
+
"<div class=\"container-fluid\">",
|
|
112
|
+
" $1",
|
|
113
|
+
"</div>",
|
|
114
|
+
"",
|
|
115
|
+
"@code {",
|
|
116
|
+
" $2",
|
|
117
|
+
"}"
|
|
118
|
+
],
|
|
119
|
+
"description": "MORPH Blazor component template"
|
|
120
|
+
},
|
|
121
|
+
"MORPH Template - EF Core Migration": {
|
|
122
|
+
"prefix": "morph-migration",
|
|
123
|
+
"body": [
|
|
124
|
+
"public partial class Add{{pascalCase FEATURE_NAME}} : Migration",
|
|
125
|
+
"{",
|
|
126
|
+
" protected override void Up(MigrationBuilder migrationBuilder)",
|
|
127
|
+
" {",
|
|
128
|
+
" migrationBuilder.CreateTable(",
|
|
129
|
+
" name: \"{{pascalCase FEATURE_NAME}}s\",",
|
|
130
|
+
" columns: table => new",
|
|
131
|
+
" {",
|
|
132
|
+
" Id = table.Column<int>(type: \"int\", nullable: false)",
|
|
133
|
+
" .Annotation(\"SqlServer:Identity\", \"1, 1\"),",
|
|
134
|
+
" $1",
|
|
135
|
+
" },",
|
|
136
|
+
" constraints: table =>",
|
|
137
|
+
" {",
|
|
138
|
+
" table.PrimaryKey(\"PK_{{pascalCase FEATURE_NAME}}s\", x => x.Id);",
|
|
139
|
+
" });",
|
|
140
|
+
" }",
|
|
141
|
+
"",
|
|
142
|
+
" protected override void Down(MigrationBuilder migrationBuilder)",
|
|
143
|
+
" {",
|
|
144
|
+
" migrationBuilder.DropTable(name: \"{{pascalCase FEATURE_NAME}}s\");",
|
|
145
|
+
" }",
|
|
146
|
+
"}"
|
|
147
|
+
],
|
|
148
|
+
"description": "MORPH EF Core migration template"
|
|
149
|
+
},
|
|
150
|
+
"MORPH Template - Hangfire Job": {
|
|
151
|
+
"prefix": "morph-job",
|
|
152
|
+
"body": [
|
|
153
|
+
"public class {{pascalCase FEATURE_NAME}}ProcessorJob(",
|
|
154
|
+
" I{{pascalCase FEATURE_NAME}}Service service,",
|
|
155
|
+
" ILogger<{{pascalCase FEATURE_NAME}}ProcessorJob> logger) : I{{pascalCase FEATURE_NAME}}ProcessorJob",
|
|
156
|
+
"{",
|
|
157
|
+
" [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]",
|
|
158
|
+
" [Queue(\"default\")]",
|
|
159
|
+
" [JobDisplayName(\"{{pascalCase FEATURE_NAME}} Processing - ID: {0}\")]",
|
|
160
|
+
" public async Task ExecuteAsync(int id, CancellationToken cancellationToken)",
|
|
161
|
+
" {",
|
|
162
|
+
" $1",
|
|
163
|
+
" }",
|
|
164
|
+
"}"
|
|
165
|
+
],
|
|
166
|
+
"description": "MORPH Hangfire job template"
|
|
167
|
+
},
|
|
168
|
+
"MORPH Template - TypeScript Contract": {
|
|
169
|
+
"prefix": "morph-ts-contract",
|
|
170
|
+
"body": [
|
|
171
|
+
"export interface {{pascalCase FEATURE_NAME}} {",
|
|
172
|
+
" id: number;",
|
|
173
|
+
" $1",
|
|
174
|
+
"}",
|
|
175
|
+
"",
|
|
176
|
+
"export interface Create{{pascalCase FEATURE_NAME}}Request {",
|
|
177
|
+
" $2",
|
|
178
|
+
"}",
|
|
179
|
+
"",
|
|
180
|
+
"export interface Update{{pascalCase FEATURE_NAME}}Request {",
|
|
181
|
+
" $3",
|
|
182
|
+
"}"
|
|
183
|
+
],
|
|
184
|
+
"description": "MORPH TypeScript contract template"
|
|
185
|
+
}
|
|
186
|
+
}
|