@tekyzinc/gsd-t 2.45.11 → 2.50.10
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/CHANGELOG.md +23 -0
- package/README.md +26 -5
- package/bin/debug-ledger.js +193 -0
- package/bin/gsd-t.js +259 -1
- package/commands/gsd-t-complete-milestone.md +2 -1
- package/commands/gsd-t-debug.md +48 -2
- package/commands/gsd-t-doc-ripple.md +148 -0
- package/commands/gsd-t-execute.md +102 -5
- package/commands/gsd-t-help.md +25 -2
- package/commands/gsd-t-integrate.md +41 -1
- package/commands/gsd-t-qa.md +26 -5
- package/commands/gsd-t-quick.md +39 -1
- package/commands/gsd-t-test-sync.md +26 -1
- package/commands/gsd-t-verify.md +8 -2
- package/commands/gsd-t-wave.md +57 -0
- package/docs/GSD-T-README.md +84 -1
- package/docs/architecture.md +9 -1
- package/docs/framework-comparison-scorecard.md +160 -0
- package/docs/requirements.md +33 -0
- package/examples/rules/desktop.ini +2 -0
- package/package.json +2 -2
- package/templates/CLAUDE-global.md +82 -4
- package/templates/stacks/_security.md +243 -0
- package/templates/stacks/desktop.ini +2 -0
- package/templates/stacks/docker.md +202 -0
- package/templates/stacks/firebase.md +166 -0
- package/templates/stacks/flutter.md +205 -0
- package/templates/stacks/github-actions.md +201 -0
- package/templates/stacks/graphql.md +216 -0
- package/templates/stacks/neo4j.md +218 -0
- package/templates/stacks/nextjs.md +184 -0
- package/templates/stacks/node-api.md +196 -0
- package/templates/stacks/playwright.md +528 -0
- package/templates/stacks/postgresql.md +225 -0
- package/templates/stacks/python.md +243 -0
- package/templates/stacks/react-native.md +216 -0
- package/templates/stacks/react.md +293 -0
- package/templates/stacks/redux.md +193 -0
- package/templates/stacks/rest-api.md +202 -0
- package/templates/stacks/supabase.md +188 -0
- package/templates/stacks/tailwind.md +169 -0
- package/templates/stacks/typescript.md +176 -0
- package/templates/stacks/vite.md +176 -0
- package/templates/stacks/vue.md +189 -0
- package/templates/stacks/zustand.md +203 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Firebase Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Firestore Security Rules
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Default-deny: rules start with allow read, write: if false;
|
|
12
|
+
├── Write granular rules per collection and operation (read, create, update, delete)
|
|
13
|
+
├── Use request.auth.uid for user-scoped access — NEVER trust client-sent IDs
|
|
14
|
+
├── Validate data shape in rules: request.resource.data.field is string
|
|
15
|
+
├── Test rules with the Firebase Emulator before deploying
|
|
16
|
+
└── NEVER use allow read, write: if true in production
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**GOOD**
|
|
20
|
+
```
|
|
21
|
+
rules_version = '2';
|
|
22
|
+
service cloud.firestore {
|
|
23
|
+
match /databases/{database}/documents {
|
|
24
|
+
match /users/{userId} {
|
|
25
|
+
allow read: if request.auth != null && request.auth.uid == userId;
|
|
26
|
+
allow update: if request.auth != null && request.auth.uid == userId
|
|
27
|
+
&& request.resource.data.keys().hasOnly(['displayName', 'avatar', 'updatedAt'])
|
|
28
|
+
&& request.resource.data.displayName is string
|
|
29
|
+
&& request.resource.data.displayName.size() <= 100;
|
|
30
|
+
allow create, delete: if false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Firestore Data Modeling
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
MANDATORY:
|
|
42
|
+
├── Denormalize for read performance — Firestore charges per read, not per field
|
|
43
|
+
├── Subcollections for large or unbounded lists (messages, orders)
|
|
44
|
+
├── Embedded objects for small, bounded data that's always read together
|
|
45
|
+
├── Store document IDs as fields when needed for queries
|
|
46
|
+
├── Use server timestamps: serverTimestamp() — not client Date.now()
|
|
47
|
+
└── NEVER create deeply nested subcollection hierarchies (max 2-3 levels)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**GOOD**
|
|
51
|
+
```typescript
|
|
52
|
+
// User document — embedded small data
|
|
53
|
+
{
|
|
54
|
+
id: "user-123",
|
|
55
|
+
displayName: "Jane Doe",
|
|
56
|
+
email: "jane@example.com",
|
|
57
|
+
settings: { theme: "dark", notifications: true }, // embedded — always read together
|
|
58
|
+
createdAt: serverTimestamp(),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Orders — subcollection (unbounded, queried separately)
|
|
62
|
+
// /users/user-123/orders/{orderId}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Cloud Functions
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Use v2 functions (onRequest, onCall, onDocumentWritten) — not v1
|
|
72
|
+
├── Validate ALL inputs in onCall functions — they're public endpoints
|
|
73
|
+
├── Set memory and timeout limits per function
|
|
74
|
+
├── Use secrets manager for API keys — not environment config
|
|
75
|
+
├── Idempotent design — functions may retry on failure
|
|
76
|
+
└── Keep functions focused — one responsibility per function
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**GOOD**
|
|
80
|
+
```typescript
|
|
81
|
+
import { onCall, HttpsError } from 'firebase-functions/v2/https';
|
|
82
|
+
import { z } from 'zod';
|
|
83
|
+
|
|
84
|
+
const schema = z.object({ orderId: z.string().uuid() });
|
|
85
|
+
|
|
86
|
+
export const cancelOrder = onCall({ memory: '256MiB', timeoutSeconds: 30 }, async (request) => {
|
|
87
|
+
if (!request.auth) throw new HttpsError('unauthenticated', 'Must be logged in');
|
|
88
|
+
|
|
89
|
+
const parsed = schema.safeParse(request.data);
|
|
90
|
+
if (!parsed.success) throw new HttpsError('invalid-argument', 'Invalid input');
|
|
91
|
+
|
|
92
|
+
await db.collection('orders').doc(parsed.data.orderId).update({ status: 'cancelled' });
|
|
93
|
+
return { success: true };
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 4. Authentication
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
MANDATORY:
|
|
103
|
+
├── Use Firebase Auth — don't build custom auth alongside it
|
|
104
|
+
├── Store additional user data in Firestore — not in Auth custom claims (limited to 1KB)
|
|
105
|
+
├── Custom claims for roles/permissions only (admin, moderator)
|
|
106
|
+
├── Handle auth state with onAuthStateChanged listener
|
|
107
|
+
├── Sign out clears local state — don't leave stale data
|
|
108
|
+
└── NEVER store Firebase API keys as secrets — they're meant to be public (security rules protect data)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 5. Storage Rules
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
MANDATORY:
|
|
117
|
+
├── Restrict uploads by file type and size in rules
|
|
118
|
+
├── User-scoped paths: /users/{userId}/avatar — match in rules
|
|
119
|
+
├── Validate content type: request.resource.contentType.matches('image/.*')
|
|
120
|
+
├── Set max file size in rules: request.resource.size < 5 * 1024 * 1024
|
|
121
|
+
└── NEVER allow public write access to storage
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 6. Emulator and Local Development
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
MANDATORY:
|
|
130
|
+
├── Use Firebase Emulator Suite for local development
|
|
131
|
+
├── Test security rules against the emulator before deploying
|
|
132
|
+
├── Seed data via emulator import — not manual dashboard entry
|
|
133
|
+
├── CI runs tests against emulators — not production
|
|
134
|
+
└── Never connect to production from local development
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 7. Anti-Patterns
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
NEVER:
|
|
143
|
+
├── allow read, write: if true in production rules
|
|
144
|
+
├── Trusting client-sent user IDs — use request.auth.uid
|
|
145
|
+
├── Deep subcollection nesting (4+ levels)
|
|
146
|
+
├── Large documents (> 1MB) — split into subcollections
|
|
147
|
+
├── Reading entire collections without query limits
|
|
148
|
+
├── v1 Cloud Functions for new code — use v2
|
|
149
|
+
├── Storing secrets in Firebase config — use Secret Manager
|
|
150
|
+
└── Testing against production — use emulators
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Firebase Verification Checklist
|
|
156
|
+
|
|
157
|
+
- [ ] Security rules default-deny with granular per-collection policies
|
|
158
|
+
- [ ] request.auth.uid used for access control — no client IDs trusted
|
|
159
|
+
- [ ] Data model optimized for reads (denormalized where appropriate)
|
|
160
|
+
- [ ] Subcollections for unbounded lists
|
|
161
|
+
- [ ] Cloud Functions v2 with input validation
|
|
162
|
+
- [ ] Functions are idempotent
|
|
163
|
+
- [ ] Storage rules restrict file type and size
|
|
164
|
+
- [ ] All rules tested against emulator
|
|
165
|
+
- [ ] serverTimestamp() used — not client timestamps
|
|
166
|
+
- [ ] No allow read, write: if true in production
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Flutter Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. State Management — Riverpod
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Use Riverpod for state management — not setState for anything beyond local UI
|
|
12
|
+
├── Provider for each data source (API, local storage, computed)
|
|
13
|
+
├── AsyncNotifierProvider for server data with loading/error states
|
|
14
|
+
├── StateProvider for simple toggles/filters
|
|
15
|
+
├── NEVER store server data in StatefulWidget setState
|
|
16
|
+
└── ref.watch for reactive reads, ref.read for one-shot actions (onTap)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**BAD**
|
|
20
|
+
```dart
|
|
21
|
+
class _MyState extends State<MyWidget> {
|
|
22
|
+
List<User> users = [];
|
|
23
|
+
void initState() {
|
|
24
|
+
super.initState();
|
|
25
|
+
fetchUsers().then((u) => setState(() => users = u));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**GOOD**
|
|
31
|
+
```dart
|
|
32
|
+
@riverpod
|
|
33
|
+
class UsersNotifier extends _$UsersNotifier {
|
|
34
|
+
@override
|
|
35
|
+
Future<List<User>> build() => userRepository.getAll();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// In widget:
|
|
39
|
+
final users = ref.watch(usersNotifierProvider);
|
|
40
|
+
users.when(
|
|
41
|
+
data: (list) => UserListView(users: list),
|
|
42
|
+
loading: () => const LoadingSkeleton(),
|
|
43
|
+
error: (e, _) => ErrorView(message: e.toString()),
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Widget Design
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
MANDATORY:
|
|
53
|
+
├── Max 150 lines per widget file — extract sub-widgets
|
|
54
|
+
├── Prefer StatelessWidget — use ConsumerWidget with Riverpod
|
|
55
|
+
├── Extract build method sub-trees into separate widgets (not methods)
|
|
56
|
+
├── One widget per file for major widgets
|
|
57
|
+
├── Use const constructors wherever possible
|
|
58
|
+
└── No business logic in build() — move to providers/notifiers
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**BAD** — `Widget _buildHeader()` methods that return widget trees.
|
|
62
|
+
|
|
63
|
+
**GOOD** — Extract `HeaderWidget` as its own `ConsumerWidget`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Navigation
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Use go_router for declarative routing
|
|
72
|
+
├── Define all routes in a single router config file
|
|
73
|
+
├── Use named routes — NEVER hardcode path strings in widgets
|
|
74
|
+
├── Handle deep links in the router config
|
|
75
|
+
└── Redirect unauthenticated users in router guards — not in widgets
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**GOOD**
|
|
79
|
+
```dart
|
|
80
|
+
final router = GoRouter(
|
|
81
|
+
routes: [
|
|
82
|
+
GoRoute(path: '/', builder: (_, __) => const HomePage()),
|
|
83
|
+
GoRoute(path: '/users/:id', builder: (_, state) =>
|
|
84
|
+
UserDetailPage(id: state.pathParameters['id']!)),
|
|
85
|
+
],
|
|
86
|
+
redirect: (context, state) {
|
|
87
|
+
final isLoggedIn = ref.read(authProvider).isLoggedIn;
|
|
88
|
+
if (!isLoggedIn) return '/login';
|
|
89
|
+
return null;
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Data Models
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
MANDATORY:
|
|
100
|
+
├── Use freezed for immutable data classes with copyWith, ==, and serialization
|
|
101
|
+
├── JSON serialization via json_serializable or freezed's fromJson/toJson
|
|
102
|
+
├── NEVER use raw Map<String, dynamic> between layers
|
|
103
|
+
├── Separate models: API DTO → Domain Model → UI ViewModel (if needed)
|
|
104
|
+
└── Use sealed classes (Dart 3) for state unions
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**GOOD**
|
|
108
|
+
```dart
|
|
109
|
+
@freezed
|
|
110
|
+
class User with _$User {
|
|
111
|
+
const factory User({
|
|
112
|
+
required String id,
|
|
113
|
+
required String name,
|
|
114
|
+
required String email,
|
|
115
|
+
required UserRole role,
|
|
116
|
+
}) = _User;
|
|
117
|
+
|
|
118
|
+
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 5. Repository Pattern
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
MANDATORY:
|
|
128
|
+
├── All API/DB calls go through repository classes — widgets never call HTTP directly
|
|
129
|
+
├── Repository returns domain models — not raw JSON or Response objects
|
|
130
|
+
├── Handle errors in repository — throw typed exceptions
|
|
131
|
+
├── Abstract repository interface for testability
|
|
132
|
+
└── Use dio or http package — configure interceptors for auth/logging
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 6. Platform-Specific Code
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
MANDATORY:
|
|
141
|
+
├── Use Platform.isAndroid/isIOS checks sparingly — prefer adaptive widgets
|
|
142
|
+
├── Platform channels: define a clear Dart interface, handle errors on both sides
|
|
143
|
+
├── Test on both platforms before marking task complete
|
|
144
|
+
├── Use device_info_plus for runtime capability detection
|
|
145
|
+
└── Handle permissions gracefully — explain why before requesting
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 7. Performance
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
MANDATORY:
|
|
154
|
+
├── Use const widgets wherever possible — prevents unnecessary rebuilds
|
|
155
|
+
├── Use ListView.builder for long lists — NEVER ListView with children for 20+ items
|
|
156
|
+
├── Cache images with CachedNetworkImage — not Image.network
|
|
157
|
+
├── Avoid rebuilding entire widget trees — use granular providers
|
|
158
|
+
├── Profile with Flutter DevTools before optimizing
|
|
159
|
+
└── Minimize widget depth — deep nesting kills readability and performance
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 8. Testing
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
MANDATORY:
|
|
168
|
+
├── Unit tests for providers, notifiers, and business logic
|
|
169
|
+
├── Widget tests for UI components with pumpWidget
|
|
170
|
+
├── Integration tests for critical user flows
|
|
171
|
+
├── Use mocktail for mocking — not mockito (null-safety friendly)
|
|
172
|
+
├── Test loading, error, and empty states — not just happy path
|
|
173
|
+
└── Golden tests for complex custom widgets (optional but recommended)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 9. Anti-Patterns
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
NEVER:
|
|
182
|
+
├── setState for server data (use Riverpod)
|
|
183
|
+
├── Raw Map<String, dynamic> between layers — use typed models
|
|
184
|
+
├── Build methods over 80 lines — extract widgets
|
|
185
|
+
├── Hardcoded strings — use constants or l10n
|
|
186
|
+
├── Nested callbacks deeper than 2 levels — use async/await
|
|
187
|
+
├── context.read in build() — use ref.watch
|
|
188
|
+
├── Ignoring dispose() — always clean up controllers and streams
|
|
189
|
+
└── print() for logging — use logger package
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Flutter Verification Checklist
|
|
195
|
+
|
|
196
|
+
- [ ] Riverpod for state management — no setState for server data
|
|
197
|
+
- [ ] Widgets under 150 lines with const constructors
|
|
198
|
+
- [ ] go_router with named routes — no hardcoded path strings
|
|
199
|
+
- [ ] freezed models for all data — no raw Maps
|
|
200
|
+
- [ ] Repository pattern — widgets never call HTTP
|
|
201
|
+
- [ ] Loading, error, and empty states handled in every async widget
|
|
202
|
+
- [ ] ListView.builder for dynamic lists
|
|
203
|
+
- [ ] Tests cover providers, widgets, and critical flows
|
|
204
|
+
- [ ] Tested on both iOS and Android
|
|
205
|
+
- [ ] No print() in committed code
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# GitHub Actions Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Workflow Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── One workflow per concern: ci.yml, deploy.yml, release.yml
|
|
12
|
+
├── Name workflows clearly: name: "CI — Lint, Test, Build"
|
|
13
|
+
├── Trigger on specific events — NEVER use on: push without branch filters
|
|
14
|
+
├── Use workflow_dispatch for manual triggers on deploy/release workflows
|
|
15
|
+
└── Keep workflows under 200 lines — extract reusable logic to composite actions
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**GOOD**
|
|
19
|
+
```yaml
|
|
20
|
+
name: CI — Lint, Test, Build
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
branches: [main, develop]
|
|
24
|
+
pull_request:
|
|
25
|
+
branches: [main]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Job Design
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
MANDATORY:
|
|
34
|
+
├── Name jobs descriptively: jobs: lint:, jobs: test-unit:, jobs: build:
|
|
35
|
+
├── Use needs: for job dependencies — parallelize independent jobs
|
|
36
|
+
├── Set timeout-minutes on every job (default 360 is too long)
|
|
37
|
+
├── Use matrix strategy for multi-version/multi-platform testing
|
|
38
|
+
└── Fail fast by default — set fail-fast: false only when you need all results
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**GOOD**
|
|
42
|
+
```yaml
|
|
43
|
+
jobs:
|
|
44
|
+
lint:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 10
|
|
47
|
+
steps: ...
|
|
48
|
+
|
|
49
|
+
test:
|
|
50
|
+
needs: lint
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
strategy:
|
|
54
|
+
matrix:
|
|
55
|
+
node-version: [18, 20]
|
|
56
|
+
steps: ...
|
|
57
|
+
|
|
58
|
+
build:
|
|
59
|
+
needs: test
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
steps: ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Caching
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Cache dependencies (node_modules, pip, gradle) — saves minutes per run
|
|
72
|
+
├── Use actions/cache or setup-node with cache: 'npm'
|
|
73
|
+
├── Cache key must include lock file hash — invalidates on dependency changes
|
|
74
|
+
├── Cache Playwright browsers separately (they're large)
|
|
75
|
+
└── Set restore-keys for partial cache hits
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**GOOD**
|
|
79
|
+
```yaml
|
|
80
|
+
- uses: actions/setup-node@v4
|
|
81
|
+
with:
|
|
82
|
+
node-version: 20
|
|
83
|
+
cache: 'npm'
|
|
84
|
+
|
|
85
|
+
# Or explicit caching:
|
|
86
|
+
- uses: actions/cache@v4
|
|
87
|
+
with:
|
|
88
|
+
path: ~/.npm
|
|
89
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
90
|
+
restore-keys: ${{ runner.os }}-npm-
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Secrets Management
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
MANDATORY:
|
|
99
|
+
├── NEVER hardcode secrets, tokens, or API keys in workflow files
|
|
100
|
+
├── Use GitHub repository or environment secrets
|
|
101
|
+
├── Use environment-scoped secrets for deploy workflows (staging vs production)
|
|
102
|
+
├── Limit secret access: only jobs that need them should reference them
|
|
103
|
+
├── NEVER echo or log secrets — even accidentally via debug output
|
|
104
|
+
└── Rotate secrets periodically — set reminders
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**BAD**
|
|
108
|
+
```yaml
|
|
109
|
+
env:
|
|
110
|
+
API_KEY: sk-abc123 # NEVER DO THIS
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**GOOD**
|
|
114
|
+
```yaml
|
|
115
|
+
env:
|
|
116
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 5. Actions Versioning
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
MANDATORY:
|
|
125
|
+
├── Pin actions to major version: uses: actions/checkout@v4
|
|
126
|
+
├── For security-critical workflows, pin to SHA: uses: actions/checkout@abc123
|
|
127
|
+
├── NEVER use @latest or @main for third-party actions
|
|
128
|
+
├── Review third-party actions before use — check the source
|
|
129
|
+
└── Prefer official actions (actions/*, github/*) over community ones
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 6. Deployment Workflows
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
MANDATORY:
|
|
138
|
+
├── Require manual approval for production deploys (environment protection rules)
|
|
139
|
+
├── Deploy to staging first — production only after staging passes
|
|
140
|
+
├── Include a rollback step or document the rollback procedure
|
|
141
|
+
├── Tag releases with version numbers after successful deploy
|
|
142
|
+
├── Use concurrency groups to prevent parallel deploys
|
|
143
|
+
└── Post-deploy: run smoke tests against the deployed environment
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**GOOD**
|
|
147
|
+
```yaml
|
|
148
|
+
deploy-production:
|
|
149
|
+
needs: deploy-staging
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
environment:
|
|
152
|
+
name: production
|
|
153
|
+
url: https://app.example.com
|
|
154
|
+
concurrency:
|
|
155
|
+
group: deploy-production
|
|
156
|
+
cancel-in-progress: false
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 7. Notifications and Artifacts
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
RECOMMENDED:
|
|
165
|
+
├── Upload test results and coverage as artifacts
|
|
166
|
+
├── Notify on failure (Slack, email) — don't notify on every success
|
|
167
|
+
├── Upload build artifacts for deploy jobs to download
|
|
168
|
+
├── Set artifact retention days (default 90 is often too long)
|
|
169
|
+
└── Use job summaries (echo >> $GITHUB_STEP_SUMMARY) for key metrics
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 8. Anti-Patterns
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
NEVER:
|
|
178
|
+
├── on: push without branch filters (triggers on every branch)
|
|
179
|
+
├── Hardcoded secrets in workflow files
|
|
180
|
+
├── @latest or @main for actions — pin versions
|
|
181
|
+
├── Jobs without timeout-minutes
|
|
182
|
+
├── Skipping cache — wastes minutes on every run
|
|
183
|
+
├── Deploy to production without staging gate
|
|
184
|
+
├── Single monolithic workflow file (500+ lines)
|
|
185
|
+
└── Running expensive jobs on PRs from forks without approval
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## GitHub Actions Verification Checklist
|
|
191
|
+
|
|
192
|
+
- [ ] Workflows named descriptively with specific triggers
|
|
193
|
+
- [ ] Branch filters on push triggers
|
|
194
|
+
- [ ] Jobs have timeout-minutes set
|
|
195
|
+
- [ ] Dependencies cached (npm, pip, etc.)
|
|
196
|
+
- [ ] Secrets in GitHub Secrets — never hardcoded
|
|
197
|
+
- [ ] Actions pinned to major version or SHA
|
|
198
|
+
- [ ] Deploy requires staging → production progression
|
|
199
|
+
- [ ] Production deploy has environment protection
|
|
200
|
+
- [ ] Concurrency groups prevent parallel deploys
|
|
201
|
+
- [ ] Test artifacts uploaded
|