@girardmedia/bootspring 3.3.2 → 3.4.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/assets/agents/accessibility-auditor.md +39 -0
- package/assets/agents/api-designer.md +40 -0
- package/assets/agents/auth-implementer.md +64 -0
- package/assets/agents/bug-hunter.md +42 -0
- package/assets/agents/bundle-analyzer.md +40 -0
- package/assets/agents/cache-optimizer.md +55 -0
- package/assets/agents/changelog-writer.md +55 -0
- package/assets/agents/ci-cd-builder.md +40 -0
- package/assets/agents/code-explainer.md +39 -0
- package/assets/agents/code-reviewer.md +39 -0
- package/assets/agents/cost-optimizer.md +57 -0
- package/assets/agents/cron-scheduler.md +51 -0
- package/assets/agents/data-seeder.md +56 -0
- package/assets/agents/database-architect.md +40 -0
- package/assets/agents/dependency-updater.md +40 -0
- package/assets/agents/deploy-checker.md +40 -0
- package/assets/agents/docker-optimizer.md +40 -0
- package/assets/agents/documentation-writer.md +40 -0
- package/assets/agents/email-builder.md +55 -0
- package/assets/agents/env-setup.md +40 -0
- package/assets/agents/error-handler.md +40 -0
- package/assets/agents/eslint-fixer.md +46 -0
- package/assets/agents/feature-flagger.md +69 -0
- package/assets/agents/git-detective.md +39 -0
- package/assets/agents/graphql-builder.md +60 -0
- package/assets/agents/incident-responder.md +59 -0
- package/assets/agents/log-analyzer.md +39 -0
- package/assets/agents/migration-planner.md +41 -0
- package/assets/agents/monorepo-navigator.md +39 -0
- package/assets/agents/nextjs-expert.md +57 -0
- package/assets/agents/notification-builder.md +56 -0
- package/assets/agents/onboarding-guide.md +39 -0
- package/assets/agents/performance-profiler.md +40 -0
- package/assets/agents/prisma-expert.md +57 -0
- package/assets/agents/rate-limiter.md +58 -0
- package/assets/agents/react-expert.md +58 -0
- package/assets/agents/refactorer.md +42 -0
- package/assets/agents/regex-builder.md +46 -0
- package/assets/agents/release-manager.md +40 -0
- package/assets/agents/s3-manager.md +58 -0
- package/assets/agents/schema-validator.md +40 -0
- package/assets/agents/search-builder.md +62 -0
- package/assets/agents/security-auditor.md +39 -0
- package/assets/agents/sitemap-generator.md +53 -0
- package/assets/agents/stripe-integrator.md +59 -0
- package/assets/agents/tailwind-expert.md +55 -0
- package/assets/agents/tech-debt-tracker.md +39 -0
- package/assets/agents/test-writer.md +42 -0
- package/assets/agents/type-fixer.md +45 -0
- package/assets/agents/webhook-builder.md +54 -0
- package/assets/rules/cpp.md +53 -0
- package/assets/rules/css.md +52 -0
- package/assets/rules/go.md +50 -0
- package/assets/rules/html.md +52 -0
- package/assets/rules/java.md +51 -0
- package/assets/rules/kotlin.md +50 -0
- package/assets/rules/php.md +51 -0
- package/assets/rules/python.md +51 -0
- package/assets/rules/ruby.md +51 -0
- package/assets/rules/rust.md +49 -0
- package/assets/rules/shell.md +52 -0
- package/assets/rules/sql.md +49 -0
- package/assets/rules/swift.md +50 -0
- package/assets/rules/typescript.md +52 -0
- package/assets/rules/yaml-json.md +51 -0
- package/assets/skills/accessibility.md +210 -0
- package/assets/skills/agent-patterns.md +387 -0
- package/assets/skills/ai-integration.md +263 -0
- package/assets/skills/animation-patterns.md +224 -0
- package/assets/skills/api-design.md +218 -0
- package/assets/skills/api-gateway.md +341 -0
- package/assets/skills/api-versioning.md +226 -0
- package/assets/skills/astro-patterns.md +233 -0
- package/assets/skills/auth-patterns.md +248 -0
- package/assets/skills/aws-patterns.md +171 -0
- package/assets/skills/background-jobs.md +162 -0
- package/assets/skills/browser-extensions.md +309 -0
- package/assets/skills/caching-patterns.md +253 -0
- package/assets/skills/ci-cd.md +251 -0
- package/assets/skills/cli-development.md +296 -0
- package/assets/skills/code-review.md +185 -0
- package/assets/skills/cron-patterns.md +327 -0
- package/assets/skills/data-fetching.md +231 -0
- package/assets/skills/database-migrations.md +346 -0
- package/assets/skills/database-patterns.md +219 -0
- package/assets/skills/debugging.md +281 -0
- package/assets/skills/design-system.md +289 -0
- package/assets/skills/django-patterns.md +182 -0
- package/assets/skills/docker-patterns.md +235 -0
- package/assets/skills/e2e-testing.md +287 -0
- package/assets/skills/edge-computing.md +268 -0
- package/assets/skills/electron-patterns.md +266 -0
- package/assets/skills/email-templates.md +206 -0
- package/assets/skills/error-handling.md +265 -0
- package/assets/skills/event-driven.md +232 -0
- package/assets/skills/express-patterns.md +239 -0
- package/assets/skills/fastapi-patterns.md +198 -0
- package/assets/skills/feature-flags.md +212 -0
- package/assets/skills/figma-to-code.md +298 -0
- package/assets/skills/file-upload.md +228 -0
- package/assets/skills/forms-patterns.md +264 -0
- package/assets/skills/gcp-patterns.md +189 -0
- package/assets/skills/git-workflow.md +187 -0
- package/assets/skills/golang-patterns.md +185 -0
- package/assets/skills/graphql-patterns.md +244 -0
- package/assets/skills/i18n-patterns.md +172 -0
- package/assets/skills/image-processing.md +350 -0
- package/assets/skills/java-springboot.md +226 -0
- package/assets/skills/kotlin-patterns.md +207 -0
- package/assets/skills/kubernetes-patterns.md +326 -0
- package/assets/skills/laravel-patterns.md +261 -0
- package/assets/skills/llm-fine-tuning.md +335 -0
- package/assets/skills/load-testing.md +303 -0
- package/assets/skills/logging-observability.md +228 -0
- package/assets/skills/markdown-processing.md +318 -0
- package/assets/skills/mcp-server-patterns.md +292 -0
- package/assets/skills/microservices.md +272 -0
- package/assets/skills/migration-patterns.md +239 -0
- package/assets/skills/mongodb-patterns.md +189 -0
- package/assets/skills/monorepo-patterns.md +287 -0
- package/assets/skills/nextjs-app-router.md +237 -0
- package/assets/skills/notification-patterns.md +348 -0
- package/assets/skills/oauth-patterns.md +246 -0
- package/assets/skills/payment-integration.md +222 -0
- package/assets/skills/pdf-generation.md +307 -0
- package/assets/skills/performance-optimization.md +277 -0
- package/assets/skills/php-patterns.md +210 -0
- package/assets/skills/prisma-patterns.md +241 -0
- package/assets/skills/prompt-engineering.md +193 -0
- package/assets/skills/pwa-patterns.md +247 -0
- package/assets/skills/python-patterns.md +158 -0
- package/assets/skills/python-testing.md +172 -0
- package/assets/skills/queue-patterns.md +295 -0
- package/assets/skills/rag-patterns.md +159 -0
- package/assets/skills/rate-limiting.md +319 -0
- package/assets/skills/react-components.md +201 -0
- package/assets/skills/react-native-patterns.md +299 -0
- package/assets/skills/real-time-patterns.md +181 -0
- package/assets/skills/redis-patterns.md +188 -0
- package/assets/skills/refactoring.md +218 -0
- package/assets/skills/regex-patterns.md +191 -0
- package/assets/skills/remix-patterns.md +262 -0
- package/assets/skills/responsive-design.md +199 -0
- package/assets/skills/ruby-rails-patterns.md +178 -0
- package/assets/skills/rust-patterns.md +211 -0
- package/assets/skills/search-patterns.md +227 -0
- package/assets/skills/security-hardening.md +237 -0
- package/assets/skills/seo-patterns.md +179 -0
- package/assets/skills/serverless-patterns.md +223 -0
- package/assets/skills/sql-optimization.md +154 -0
- package/assets/skills/state-management.md +254 -0
- package/assets/skills/storybook-patterns.md +330 -0
- package/assets/skills/svelte-patterns.md +258 -0
- package/assets/skills/swift-patterns.md +227 -0
- package/assets/skills/tailwind-patterns.md +272 -0
- package/assets/skills/tdd-workflow.md +199 -0
- package/assets/skills/terraform-patterns.md +270 -0
- package/assets/skills/testing-react.md +240 -0
- package/assets/skills/testing-vitest.md +232 -0
- package/assets/skills/typescript-strict.md +159 -0
- package/assets/skills/video-processing.md +340 -0
- package/assets/skills/vue-patterns.md +247 -0
- package/assets/skills/web-workers.md +327 -0
- package/assets/skills/webhooks-patterns.md +283 -0
- package/assets/skills/websocket-patterns.md +306 -0
- package/dist/cli/index.js +941 -958
- package/dist/core/index.d.ts +341 -11
- package/dist/core.js +58 -95
- package/dist/mcp/index.d.ts +33 -1
- package/dist/mcp-server.js +177 -255
- package/package.json +4 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forms-patterns
|
|
3
|
+
description: Form patterns with React Hook Form, Zod validation, multi-step forms, file uploads, and optimistic submission.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Form Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Apply when building user input forms in React applications. Use React Hook Form for performance, Zod for type-safe validation, multi-step wizards for complex flows, and optimistic submission for snappy UX. These patterns prevent common pitfalls: re-renders, unvalidated submissions, and poor error feedback.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### React Hook Form + Zod Validation
|
|
14
|
+
|
|
15
|
+
Type-safe forms with zero re-renders on input change:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { useForm } from "react-hook-form";
|
|
19
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
const signupSchema = z.object({
|
|
23
|
+
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
24
|
+
email: z.string().email("Invalid email address"),
|
|
25
|
+
password: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(8, "Password must be at least 8 characters")
|
|
28
|
+
.regex(/[A-Z]/, "Must contain an uppercase letter")
|
|
29
|
+
.regex(/[0-9]/, "Must contain a number"),
|
|
30
|
+
confirmPassword: z.string(),
|
|
31
|
+
}).refine((data) => data.password === data.confirmPassword, {
|
|
32
|
+
message: "Passwords do not match",
|
|
33
|
+
path: ["confirmPassword"],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
type SignupForm = z.infer<typeof signupSchema>;
|
|
37
|
+
|
|
38
|
+
function SignupPage() {
|
|
39
|
+
const {
|
|
40
|
+
register,
|
|
41
|
+
handleSubmit,
|
|
42
|
+
formState: { errors, isSubmitting },
|
|
43
|
+
} = useForm<SignupForm>({
|
|
44
|
+
resolver: zodResolver(signupSchema),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const onSubmit = async (data: SignupForm) => {
|
|
48
|
+
await fetch("/api/auth/signup", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify(data),
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<form onSubmit={handleSubmit(onSubmit)} noValidate>
|
|
57
|
+
<div>
|
|
58
|
+
<label htmlFor="name">Name</label>
|
|
59
|
+
<input id="name" {...register("name")} />
|
|
60
|
+
{errors.name && <p role="alert">{errors.name.message}</p>}
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<label htmlFor="email">Email</label>
|
|
64
|
+
<input id="email" type="email" {...register("email")} />
|
|
65
|
+
{errors.email && <p role="alert">{errors.email.message}</p>}
|
|
66
|
+
</div>
|
|
67
|
+
<div>
|
|
68
|
+
<label htmlFor="password">Password</label>
|
|
69
|
+
<input id="password" type="password" {...register("password")} />
|
|
70
|
+
{errors.password && <p role="alert">{errors.password.message}</p>}
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<label htmlFor="confirmPassword">Confirm Password</label>
|
|
74
|
+
<input id="confirmPassword" type="password" {...register("confirmPassword")} />
|
|
75
|
+
{errors.confirmPassword && <p role="alert">{errors.confirmPassword.message}</p>}
|
|
76
|
+
</div>
|
|
77
|
+
<button type="submit" disabled={isSubmitting}>
|
|
78
|
+
{isSubmitting ? "Creating account..." : "Sign Up"}
|
|
79
|
+
</button>
|
|
80
|
+
</form>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Multi-Step Form Wizard
|
|
86
|
+
|
|
87
|
+
Break complex forms into digestible steps:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const stepSchemas = [
|
|
91
|
+
z.object({ name: z.string().min(1), email: z.string().email() }),
|
|
92
|
+
z.object({ company: z.string().min(1), role: z.string().min(1) }),
|
|
93
|
+
z.object({ plan: z.enum(["free", "pro", "enterprise"]) }),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function MultiStepForm() {
|
|
97
|
+
const [step, setStep] = useState(0);
|
|
98
|
+
const [formData, setFormData] = useState({});
|
|
99
|
+
|
|
100
|
+
const currentSchema = stepSchemas[step];
|
|
101
|
+
const { register, handleSubmit, formState: { errors } } = useForm({
|
|
102
|
+
resolver: zodResolver(currentSchema),
|
|
103
|
+
defaultValues: formData,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const onStepSubmit = (data: Record<string, unknown>) => {
|
|
107
|
+
const merged = { ...formData, ...data };
|
|
108
|
+
setFormData(merged);
|
|
109
|
+
|
|
110
|
+
if (step < stepSchemas.length - 1) {
|
|
111
|
+
setStep(step + 1);
|
|
112
|
+
} else {
|
|
113
|
+
submitFinalForm(merged);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<form onSubmit={handleSubmit(onStepSubmit)}>
|
|
119
|
+
<div aria-label={`Step ${step + 1} of ${stepSchemas.length}`}>
|
|
120
|
+
<progress value={step + 1} max={stepSchemas.length} />
|
|
121
|
+
</div>
|
|
122
|
+
{step === 0 && (
|
|
123
|
+
<>
|
|
124
|
+
<input {...register("name")} placeholder="Name" />
|
|
125
|
+
<input {...register("email")} placeholder="Email" />
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
{step === 1 && (
|
|
129
|
+
<>
|
|
130
|
+
<input {...register("company")} placeholder="Company" />
|
|
131
|
+
<input {...register("role")} placeholder="Role" />
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
{step === 2 && (
|
|
135
|
+
<select {...register("plan")}>
|
|
136
|
+
<option value="free">Free</option>
|
|
137
|
+
<option value="pro">Pro</option>
|
|
138
|
+
<option value="enterprise">Enterprise</option>
|
|
139
|
+
</select>
|
|
140
|
+
)}
|
|
141
|
+
<div>
|
|
142
|
+
{step > 0 && <button type="button" onClick={() => setStep(step - 1)}>Back</button>}
|
|
143
|
+
<button type="submit">{step === stepSchemas.length - 1 ? "Submit" : "Next"}</button>
|
|
144
|
+
</div>
|
|
145
|
+
</form>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### File Upload with Preview
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
function FileUploadField({ name, control }: { name: string; control: Control }) {
|
|
154
|
+
const { field } = useController({ name, control });
|
|
155
|
+
const [preview, setPreview] = useState<string | null>(null);
|
|
156
|
+
|
|
157
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
158
|
+
const file = e.target.files?.[0];
|
|
159
|
+
if (!file) return;
|
|
160
|
+
|
|
161
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
162
|
+
alert("File must be under 5MB");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
field.onChange(file);
|
|
167
|
+
const reader = new FileReader();
|
|
168
|
+
reader.onload = () => setPreview(reader.result as string);
|
|
169
|
+
reader.readAsDataURL(file);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div>
|
|
174
|
+
<input type="file" accept="image/*" onChange={handleChange} />
|
|
175
|
+
{preview && <img src={preview} alt="Preview" style={{ maxWidth: 200 }} />}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Optimistic Submission
|
|
182
|
+
|
|
183
|
+
Show success immediately, roll back on failure:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
function TodoList() {
|
|
187
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
188
|
+
|
|
189
|
+
const addTodo = async (text: string) => {
|
|
190
|
+
const optimisticTodo: Todo = {
|
|
191
|
+
id: `temp-${Date.now()}`,
|
|
192
|
+
text,
|
|
193
|
+
completed: false,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Optimistic update
|
|
197
|
+
setTodos((prev) => [...prev, optimisticTodo]);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const created = await fetch("/api/todos", {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
body: JSON.stringify({ text }),
|
|
204
|
+
}).then((r) => r.json());
|
|
205
|
+
|
|
206
|
+
// Replace optimistic with real
|
|
207
|
+
setTodos((prev) =>
|
|
208
|
+
prev.map((t) => (t.id === optimisticTodo.id ? created : t))
|
|
209
|
+
);
|
|
210
|
+
} catch {
|
|
211
|
+
// Roll back
|
|
212
|
+
setTodos((prev) => prev.filter((t) => t.id !== optimisticTodo.id));
|
|
213
|
+
toast.error("Failed to add todo");
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Server-Side Validation Error Handling
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const onSubmit = async (data: SignupForm) => {
|
|
223
|
+
const res = await fetch("/api/auth/signup", {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: { "Content-Type": "application/json" },
|
|
226
|
+
body: JSON.stringify(data),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!res.ok) {
|
|
230
|
+
const body = await res.json();
|
|
231
|
+
// Map server errors to form fields
|
|
232
|
+
if (body.errors) {
|
|
233
|
+
for (const [field, message] of Object.entries(body.errors)) {
|
|
234
|
+
setError(field as keyof SignupForm, {
|
|
235
|
+
type: "server",
|
|
236
|
+
message: message as string,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
router.push("/dashboard");
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Examples
|
|
247
|
+
|
|
248
|
+
| Pattern | When | Result |
|
|
249
|
+
|---------|------|--------|
|
|
250
|
+
| RHF + Zod | Any form | Type-safe, zero unnecessary re-renders |
|
|
251
|
+
| Multi-step wizard | Onboarding, checkout | Digestible steps, per-step validation |
|
|
252
|
+
| File upload preview | Avatar, documents | Instant visual feedback before submit |
|
|
253
|
+
| Optimistic submission | Todos, comments, likes | Instant UX, graceful rollback |
|
|
254
|
+
| Server error mapping | Signup, settings | Field-level errors from API |
|
|
255
|
+
|
|
256
|
+
## Checklist
|
|
257
|
+
- [ ] Forms use React Hook Form with zodResolver for type-safe validation
|
|
258
|
+
- [ ] Every input has a visible label and accessible error message
|
|
259
|
+
- [ ] Submit button shows loading state and is disabled during submission
|
|
260
|
+
- [ ] Server validation errors mapped back to individual form fields
|
|
261
|
+
- [ ] Multi-step forms validate per step, not only on final submit
|
|
262
|
+
- [ ] File uploads validate size and type before upload
|
|
263
|
+
- [ ] Form state persists across page navigation (for long forms)
|
|
264
|
+
- [ ] `noValidate` on form element to prevent browser default validation
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gcp-patterns
|
|
3
|
+
description: GCP patterns for Cloud Run, Firestore, Cloud Functions, Pub/Sub, Cloud Storage, and IAM best practices.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GCP Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Apply when building on Google Cloud Platform. Covers Cloud Run for containerized services, Firestore for document databases, Cloud Functions for event-driven compute, Pub/Sub for messaging, Cloud Storage for objects, and IAM for access control. Use this skill when designing GCP architectures, migrating workloads, or optimizing existing deployments.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### Cloud Run -- Serverless Containers
|
|
14
|
+
|
|
15
|
+
```dockerfile
|
|
16
|
+
FROM node:20-slim AS builder
|
|
17
|
+
WORKDIR /app
|
|
18
|
+
COPY package*.json ./
|
|
19
|
+
RUN npm ci --production
|
|
20
|
+
COPY . .
|
|
21
|
+
RUN npm run build
|
|
22
|
+
|
|
23
|
+
FROM node:20-slim
|
|
24
|
+
WORKDIR /app
|
|
25
|
+
COPY --from=builder /app/dist ./dist
|
|
26
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
27
|
+
ENV PORT=8080
|
|
28
|
+
EXPOSE 8080
|
|
29
|
+
CMD ["node", "dist/server.js"]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
gcloud run deploy my-service \
|
|
34
|
+
--image gcr.io/my-project/my-service:latest \
|
|
35
|
+
--region us-central1 \
|
|
36
|
+
--concurrency 80 \
|
|
37
|
+
--min-instances 1 \
|
|
38
|
+
--max-instances 10 \
|
|
39
|
+
--memory 512Mi \
|
|
40
|
+
--cpu 1 \
|
|
41
|
+
--set-env-vars "NODE_ENV=production" \
|
|
42
|
+
--service-account my-service@my-project.iam.gserviceaccount.com
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Set `--min-instances 1` to avoid cold starts for critical services. Use `--concurrency` to match your app's threading model. Keep container startup under 10 seconds.
|
|
46
|
+
|
|
47
|
+
### Firestore -- Document Database
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Firestore, FieldValue } from "@google-cloud/firestore";
|
|
51
|
+
|
|
52
|
+
const db = new Firestore();
|
|
53
|
+
|
|
54
|
+
// Create/update with merge
|
|
55
|
+
await db.collection("users").doc(userId).set({
|
|
56
|
+
name: "Alice",
|
|
57
|
+
email: "alice@example.com",
|
|
58
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
59
|
+
}, { merge: true });
|
|
60
|
+
|
|
61
|
+
// Query with composite filter
|
|
62
|
+
const activeOrders = await db.collection("orders")
|
|
63
|
+
.where("userId", "==", userId)
|
|
64
|
+
.where("status", "in", ["pending", "processing"])
|
|
65
|
+
.orderBy("createdAt", "desc")
|
|
66
|
+
.limit(20)
|
|
67
|
+
.get();
|
|
68
|
+
|
|
69
|
+
// Transaction -- atomic read-modify-write
|
|
70
|
+
await db.runTransaction(async (tx) => {
|
|
71
|
+
const ref = db.collection("accounts").doc(accountId);
|
|
72
|
+
const snap = await tx.get(ref);
|
|
73
|
+
const balance = snap.data()?.balance ?? 0;
|
|
74
|
+
if (balance < amount) throw new Error("Insufficient funds");
|
|
75
|
+
tx.update(ref, { balance: balance - amount });
|
|
76
|
+
tx.create(db.collection("transactions").doc(), {
|
|
77
|
+
accountId, amount: -amount, createdAt: FieldValue.serverTimestamp(),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Design documents for the read pattern -- denormalize. Keep documents under 1MB. Use subcollections for 1:many relationships.
|
|
83
|
+
|
|
84
|
+
### Cloud Functions -- Event-Driven
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { onDocumentCreated } from "firebase-functions/v2/firestore";
|
|
88
|
+
import { onMessagePublished } from "firebase-functions/v2/pubsub";
|
|
89
|
+
|
|
90
|
+
export const onOrderCreated = onDocumentCreated("orders/{orderId}", async (event) => {
|
|
91
|
+
const order = event.data?.data();
|
|
92
|
+
if (!order) return;
|
|
93
|
+
await sendConfirmationEmail(order.userId, order);
|
|
94
|
+
await updateInventory(order.items);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const processAnalytics = onMessagePublished("analytics-events", async (event) => {
|
|
98
|
+
const data = event.data.message.json;
|
|
99
|
+
await bigquery.insert("events", data);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Pub/Sub -- Async Messaging
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { PubSub } from "@google-cloud/pubsub";
|
|
107
|
+
|
|
108
|
+
const pubsub = new PubSub();
|
|
109
|
+
|
|
110
|
+
// Publish with ordering key
|
|
111
|
+
const topic = pubsub.topic("order-events", { enableMessageOrdering: true });
|
|
112
|
+
await topic.publishMessage({
|
|
113
|
+
data: Buffer.from(JSON.stringify({ orderId, action: "created" })),
|
|
114
|
+
orderingKey: orderId,
|
|
115
|
+
attributes: { eventType: "order.created" },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Subscribe with flow control
|
|
119
|
+
const sub = pubsub.subscription("order-processor", {
|
|
120
|
+
flowControl: { maxMessages: 10 },
|
|
121
|
+
});
|
|
122
|
+
sub.on("message", async (message) => {
|
|
123
|
+
try {
|
|
124
|
+
await processOrderEvent(JSON.parse(message.data.toString()));
|
|
125
|
+
message.ack();
|
|
126
|
+
} catch {
|
|
127
|
+
message.nack();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Set dead-letter topics for messages that fail repeatedly. Use ordering keys only when needed -- they reduce throughput.
|
|
133
|
+
|
|
134
|
+
### Cloud Storage -- Object Store
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { Storage } from "@google-cloud/storage";
|
|
138
|
+
|
|
139
|
+
const storage = new Storage();
|
|
140
|
+
const bucket = storage.bucket("my-app-assets");
|
|
141
|
+
|
|
142
|
+
// Resumable upload for large files
|
|
143
|
+
await bucket.upload(localPath, {
|
|
144
|
+
destination: `uploads/${userId}/${filename}`,
|
|
145
|
+
resumable: true,
|
|
146
|
+
metadata: { contentType: "application/pdf" },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Signed URL for temporary access
|
|
150
|
+
const [url] = await bucket.file(`uploads/${userId}/${filename}`).getSignedUrl({
|
|
151
|
+
action: "read",
|
|
152
|
+
expires: Date.now() + 15 * 60 * 1000,
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### IAM -- Least Privilege
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# One service account per service
|
|
160
|
+
gcloud iam service-accounts create my-service-sa \
|
|
161
|
+
--display-name "My Service"
|
|
162
|
+
|
|
163
|
+
# Grant only needed roles
|
|
164
|
+
gcloud projects add-iam-policy-binding my-project \
|
|
165
|
+
--member "serviceAccount:my-service-sa@my-project.iam.gserviceaccount.com" \
|
|
166
|
+
--role "roles/datastore.user"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Never use the default compute service account in production. Use Workload Identity Federation for external services instead of key files.
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
| Pattern | Service | Use Case |
|
|
174
|
+
|---------|---------|----------|
|
|
175
|
+
| Container API | Cloud Run | REST/gRPC microservices |
|
|
176
|
+
| Document store | Firestore | User profiles, settings |
|
|
177
|
+
| Event trigger | Cloud Functions | Firestore/Pub/Sub handlers |
|
|
178
|
+
| Message bus | Pub/Sub | Service decoupling |
|
|
179
|
+
| File storage | Cloud Storage | Uploads, backups, static assets |
|
|
180
|
+
|
|
181
|
+
## Checklist
|
|
182
|
+
- [ ] Cloud Run services have `--min-instances 1` for latency-critical paths
|
|
183
|
+
- [ ] Firestore composite indexes created for all multi-field queries
|
|
184
|
+
- [ ] Cloud Functions have memory and timeout configured per function
|
|
185
|
+
- [ ] Pub/Sub subscriptions have dead-letter topics configured
|
|
186
|
+
- [ ] Cloud Storage buckets have lifecycle rules for old objects
|
|
187
|
+
- [ ] Each service uses a dedicated service account with minimal roles
|
|
188
|
+
- [ ] No service account key files -- use Workload Identity Federation
|
|
189
|
+
- [ ] Infrastructure defined in Terraform or Deployment Manager
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-workflow
|
|
3
|
+
description: Git workflow patterns including branching strategies, conventional commits, rebasing, cherry-pick, and bisect.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Workflow Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Apply these patterns when collaborating on any software project using Git. Whether you are managing feature branches, writing commit messages, resolving merge conflicts, or tracking down regressions, consistent Git workflows reduce friction and keep the history readable. These patterns are especially important in teams using CI/CD, code review, and automated release pipelines.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### Branching Strategy (Trunk-Based with Short-Lived Branches)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Create a feature branch from main
|
|
17
|
+
git checkout main
|
|
18
|
+
git pull origin main
|
|
19
|
+
git checkout -b feat/user-avatars
|
|
20
|
+
|
|
21
|
+
# Work on the branch with small, atomic commits
|
|
22
|
+
git add src/components/Avatar.tsx src/components/Avatar.test.tsx
|
|
23
|
+
git commit -m "feat(ui): add Avatar component with fallback initials"
|
|
24
|
+
|
|
25
|
+
git add src/api/upload-avatar.ts
|
|
26
|
+
git commit -m "feat(api): add avatar upload endpoint with S3 presigned URL"
|
|
27
|
+
|
|
28
|
+
# Keep branch up to date with main
|
|
29
|
+
git fetch origin
|
|
30
|
+
git rebase origin/main
|
|
31
|
+
|
|
32
|
+
# Push and create PR
|
|
33
|
+
git push -u origin feat/user-avatars
|
|
34
|
+
gh pr create --title "feat: user avatar upload and display" \
|
|
35
|
+
--body "Adds avatar component and S3 upload endpoint"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Conventional Commits
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Format: <type>(<scope>): <description>
|
|
42
|
+
# Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build
|
|
43
|
+
|
|
44
|
+
# Feature
|
|
45
|
+
git commit -m "feat(auth): add OAuth 2.0 Google login"
|
|
46
|
+
|
|
47
|
+
# Bug fix
|
|
48
|
+
git commit -m "fix(api): handle null response in user endpoint"
|
|
49
|
+
|
|
50
|
+
# Breaking change (note the ! after scope)
|
|
51
|
+
git commit -m "feat(api)!: change /users response format to paginated"
|
|
52
|
+
|
|
53
|
+
# Multi-line with body and footer
|
|
54
|
+
git commit -m "fix(db): resolve connection pool exhaustion under load
|
|
55
|
+
|
|
56
|
+
The pool was not releasing connections after transaction rollback.
|
|
57
|
+
Added explicit release in the finally block of executeTransaction.
|
|
58
|
+
|
|
59
|
+
Closes #342"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Interactive Rebase for Clean History
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Squash the last 4 commits into a clean history before PR merge
|
|
66
|
+
git rebase -i HEAD~4
|
|
67
|
+
|
|
68
|
+
# In the editor:
|
|
69
|
+
# pick abc1234 feat(auth): add login form skeleton
|
|
70
|
+
# squash def5678 wip: hook up validation
|
|
71
|
+
# squash 789abcd fix typo in error message
|
|
72
|
+
# squash bcd0123 add tests for login flow
|
|
73
|
+
|
|
74
|
+
# Result: one clean commit with a comprehensive message
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Cherry-Pick a Specific Commit
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Bring a hotfix from main into a release branch
|
|
81
|
+
git checkout release/2.1
|
|
82
|
+
git cherry-pick abc1234
|
|
83
|
+
|
|
84
|
+
# Cherry-pick without committing (to modify before committing)
|
|
85
|
+
git cherry-pick --no-commit abc1234
|
|
86
|
+
git add -p # stage only the parts you need
|
|
87
|
+
git commit -m "fix(billing): backport invoice rounding fix to 2.1"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Git Bisect to Find Regressions
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Start bisect
|
|
94
|
+
git bisect start
|
|
95
|
+
|
|
96
|
+
# Mark the current commit as bad
|
|
97
|
+
git bisect bad
|
|
98
|
+
|
|
99
|
+
# Mark a known good commit (e.g., last release tag)
|
|
100
|
+
git bisect good v2.0.0
|
|
101
|
+
|
|
102
|
+
# Git checks out a midpoint - test it, then mark good or bad
|
|
103
|
+
npm test -- --run src/billing.test.ts
|
|
104
|
+
git bisect good # or: git bisect bad
|
|
105
|
+
|
|
106
|
+
# Automate with a test script
|
|
107
|
+
git bisect start HEAD v2.0.0
|
|
108
|
+
git bisect run npm test -- --run src/billing.test.ts
|
|
109
|
+
|
|
110
|
+
# Clean up
|
|
111
|
+
git bisect reset
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Stash and Apply
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Save work in progress before switching branches
|
|
118
|
+
git stash push -m "wip: dashboard chart refactor"
|
|
119
|
+
|
|
120
|
+
# List stashes
|
|
121
|
+
git stash list
|
|
122
|
+
|
|
123
|
+
# Apply and remove from stash
|
|
124
|
+
git stash pop
|
|
125
|
+
|
|
126
|
+
# Apply without removing (keep stash for reuse)
|
|
127
|
+
git stash apply stash@{0}
|
|
128
|
+
|
|
129
|
+
# Stash only unstaged changes (keep staged files)
|
|
130
|
+
git stash push --keep-index -m "unstaged changes only"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Handling Merge Conflicts
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# During rebase, when conflicts occur:
|
|
137
|
+
git rebase origin/main
|
|
138
|
+
# CONFLICT in src/config.ts
|
|
139
|
+
|
|
140
|
+
# 1. Open the file, resolve the conflict markers
|
|
141
|
+
# 2. Stage the resolved file
|
|
142
|
+
git add src/config.ts
|
|
143
|
+
|
|
144
|
+
# 3. Continue the rebase
|
|
145
|
+
git rebase --continue
|
|
146
|
+
|
|
147
|
+
# If you want to abort and go back to before the rebase
|
|
148
|
+
git rebase --abort
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Tags and Releases
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Create an annotated tag for release
|
|
155
|
+
git tag -a v2.1.0 -m "Release 2.1.0: avatar upload, improved billing"
|
|
156
|
+
|
|
157
|
+
# Push tags
|
|
158
|
+
git push origin v2.1.0
|
|
159
|
+
|
|
160
|
+
# List tags matching a pattern
|
|
161
|
+
git tag -l "v2.*"
|
|
162
|
+
|
|
163
|
+
# Delete a tag locally and remotely
|
|
164
|
+
git tag -d v2.1.0-beta
|
|
165
|
+
git push origin --delete v2.1.0-beta
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
| Scenario | Command | Purpose |
|
|
171
|
+
|----------|---------|---------|
|
|
172
|
+
| Undo last commit (keep changes) | `git reset --soft HEAD~1` | Recommit with different message |
|
|
173
|
+
| View file at specific commit | `git show abc1234:src/config.ts` | Inspect historical state |
|
|
174
|
+
| Find who changed a line | `git blame -L 10,20 src/auth.ts` | Track authorship |
|
|
175
|
+
| See branches merged to main | `git branch --merged main` | Clean up stale branches |
|
|
176
|
+
| Diff only file names | `git diff --name-only HEAD~3` | Quick overview of recent changes |
|
|
177
|
+
| Restore a deleted file | `git checkout HEAD~1 -- src/removed.ts` | Recover from accidental deletion |
|
|
178
|
+
|
|
179
|
+
## Checklist
|
|
180
|
+
- [ ] Feature branches are short-lived (< 2 days) and branched from main
|
|
181
|
+
- [ ] Commits follow conventional commit format (`feat:`, `fix:`, `chore:`, etc.)
|
|
182
|
+
- [ ] Rebase onto main before creating a PR to avoid merge commits
|
|
183
|
+
- [ ] Each commit is atomic: it compiles, tests pass, and represents one logical change
|
|
184
|
+
- [ ] Tags follow semver and are created for every release
|
|
185
|
+
- [ ] Stale branches are deleted after PR merge
|
|
186
|
+
- [ ] Never force-push to main or shared branches
|
|
187
|
+
- [ ] Use `git bisect` instead of manual searching when tracking regressions
|