Skip to content

Commit b6f4572

Browse files
Merge pull request #571 from elbwalker/565-ai-readiness
565 ai readiness
2 parents ccfb388 + e9bd11b commit b6f4572

File tree

7 files changed

+330
-125
lines changed

7 files changed

+330
-125
lines changed

packages/collector/src/__tests__/destination-code.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,72 @@ describe('destinationCode', () => {
4444
});
4545

4646
describe('init', () => {
47+
it('accepts scripts array in settings', () => {
48+
const settings: Settings = {
49+
scripts: [
50+
'https://example.com/analytics.js',
51+
'https://example.com/pixel.js',
52+
],
53+
init: "console.log('ready')",
54+
};
55+
expect(settings.scripts).toHaveLength(2);
56+
});
57+
58+
it('injects script tags for each URL in scripts array', () => {
59+
const initialScriptCount =
60+
document.head.querySelectorAll('script').length;
61+
62+
const context: InitContext = {
63+
collector: createMockCollector(),
64+
config: {
65+
settings: {
66+
scripts: ['https://example.com/a.js', 'https://example.com/b.js'],
67+
},
68+
},
69+
env: {},
70+
logger: createMockLogger(),
71+
};
72+
73+
destinationCode.init!(context);
74+
75+
const scripts = document.head.querySelectorAll('script');
76+
expect(scripts.length).toBe(initialScriptCount + 2);
77+
78+
const addedScripts = Array.from(scripts).slice(-2);
79+
expect(addedScripts[0].src).toBe('https://example.com/a.js');
80+
expect(addedScripts[0].async).toBe(true);
81+
expect(addedScripts[1].src).toBe('https://example.com/b.js');
82+
expect(addedScripts[1].async).toBe(true);
83+
});
84+
85+
it('injects scripts before running init code', () => {
86+
const initialScriptCount =
87+
document.head.querySelectorAll('script').length;
88+
const mockLogger = createMockLogger();
89+
90+
const context: InitContext = {
91+
collector: createMockCollector(),
92+
config: {
93+
settings: {
94+
scripts: ['https://example.com/lib.js'],
95+
init: "context.logger.info('init ran')",
96+
},
97+
},
98+
env: {},
99+
logger: mockLogger,
100+
};
101+
102+
destinationCode.init!(context);
103+
104+
// Scripts should be injected
105+
const scripts = document.head.querySelectorAll('script');
106+
expect(scripts.length).toBe(initialScriptCount + 1);
107+
expect(Array.from(scripts).pop()?.src).toBe('https://example.com/lib.js');
108+
109+
// Init code should also run
110+
expect(mockLogger.info).toHaveBeenCalledWith('init ran');
111+
});
112+
47113
it('executes init code string', () => {
48114
const mockLogger = createMockLogger();
49115
const context: InitContext = {
@@ -62,6 +128,33 @@ describe('destinationCode', () => {
62128
expect(mockLogger.info).toHaveBeenCalledWith('initialized');
63129
});
64130

131+
it('handles empty scripts array gracefully', () => {
132+
const initialScriptCount =
133+
document.head.querySelectorAll('script').length;
134+
const mockLogger = createMockLogger();
135+
const context: InitContext = {
136+
collector: createMockCollector(),
137+
config: {
138+
settings: {
139+
scripts: [],
140+
init: "context.logger.info('init ran')",
141+
},
142+
},
143+
env: {},
144+
logger: mockLogger,
145+
};
146+
147+
expect(() => destinationCode.init!(context)).not.toThrow();
148+
149+
// No scripts should be added
150+
expect(document.head.querySelectorAll('script').length).toBe(
151+
initialScriptCount,
152+
);
153+
154+
// Init code should still run
155+
expect(mockLogger.info).toHaveBeenCalledWith('init ran');
156+
});
157+
65158
it('handles missing init code gracefully', () => {
66159
const context: InitContext = {
67160
collector: createMockCollector(),

packages/collector/src/destination-code.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,21 @@ export const destinationCode: Destination.Instance = {
77

88
init(context) {
99
const { config, logger } = context;
10-
const initCode = (config.settings as Settings | undefined)?.init;
10+
const settings = config.settings as Settings | undefined;
11+
12+
// Inject scripts (fire and forget)
13+
const scripts = settings?.scripts;
14+
if (scripts && typeof document !== 'undefined') {
15+
for (const src of scripts) {
16+
const script = document.createElement('script');
17+
script.src = src;
18+
script.async = true;
19+
document.head.appendChild(script);
20+
}
21+
}
22+
23+
// Execute init code
24+
const initCode = settings?.init;
1125
if (!initCode) return;
1226
try {
1327
const fn = new Function('context', initCode);

packages/collector/src/types/code.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Destination, Mapping, On, WalkerOS } from '@walkeros/core';
22

33
export interface Settings {
4+
scripts?: string[];
45
init?: string;
56
on?: string;
67
push?: string;

skills/create-destination/SKILL.md

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ Before starting, read these skills:
2222
- [writing-documentation](../writing-documentation/SKILL.md) - Documentation
2323
standards (for Phase 7)
2424

25+
## Choose Your Template
26+
27+
| Complexity | Template | When to Use |
28+
| ---------- | ------------ | ----------------------------------- |
29+
| Simple | `plausible/` | Single SDK call, minimal config |
30+
| Complex | `gtag/` | Multiple services, sub-destinations |
31+
| Server | `gcp/` | Server-side, batching, SDK init |
32+
2533
## Process Overview
2634

2735
```
@@ -80,6 +88,24 @@ ls packages/web/destinations/
8088
# - meta: Pixel with custom events
8189
```
8290

91+
### Gate: Research Complete
92+
93+
Before proceeding, confirm:
94+
95+
- [ ] API pattern identified (SDK function / HTTP / pixel)
96+
- [ ] Auth method documented (API key, token, none)
97+
- [ ] Event types mapped to walkerOS equivalents
98+
99+
### Checkpoint: Research Review (Optional)
100+
101+
If working with human oversight, pause here to confirm:
102+
103+
- API pattern and auth method correct?
104+
- Event mapping makes sense for the use case?
105+
- Any vendor quirks or rate limits to handle?
106+
107+
Continue only after approval.
108+
83109
---
84110

85111
## Phase 2: Create Examples (BEFORE Implementation)
@@ -211,6 +237,11 @@ export * as schemas from './schemas';
211237
export * as examples from './examples';
212238
```
213239

240+
### Gate: Examples Valid
241+
242+
- [ ] All example files compile (`npm run build`)
243+
- [ ] Can trace: input event → expected output for each example
244+
214245
---
215246

216247
## Phase 3: Define Mapping
@@ -278,6 +309,11 @@ Input: events.pageView
278309
Output: Should match outputs.pageViewCall
279310
```
280311

312+
### Gate: Mapping Verified
313+
314+
- [ ] Mapping covers: page view + at least one conversion event
315+
- [ ] Each mapping rule traces correctly to expected output
316+
281317
---
282318

283319
## Phase 4: Scaffold
@@ -390,6 +426,11 @@ function addScript(settings: Settings, env?: DestinationWeb.Env) {
390426
export default destinationVendor;
391427
```
392428

429+
### Gate: Implementation Compiles
430+
431+
- [ ] `npm run build` passes
432+
- [ ] `npm run lint` passes
433+
393434
---
394435

395436
## Phase 6: Test Against Examples
@@ -437,6 +478,11 @@ describe('destinationVendor', () => {
437478
});
438479
```
439480

481+
### Gate: Tests Pass
482+
483+
- [ ] `npm run test` passes
484+
- [ ] Tests verify against example outputs (not hardcoded values)
485+
440486
---
441487

442488
## Phase 7: Document
@@ -456,14 +502,15 @@ Key requirements for destination documentation:
456502

457503
---
458504

459-
## Validation Checklist
505+
## Destination-Specific Validation
460506

461-
- [ ] `npm run build` passes
462-
- [ ] `npm run test` passes
463-
- [ ] `npm run lint` passes
464-
- [ ] Examples cover main use cases
465-
- [ ] Tests verify against example outputs
466-
- [ ] README documents configuration
507+
Beyond [understanding-development](../understanding-development/SKILL.md)
508+
requirements (build, test, lint, no `any`):
509+
510+
- [ ] Uses `getEnv(env)` pattern (never direct `window`/`document` access)
511+
- [ ] `dev.ts` exports `schemas` and `examples`
512+
- [ ] Examples match type signatures
513+
- [ ] Tests use examples for assertions (not hardcoded values)
467514

468515
---
469516

skills/create-source/SKILL.md

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ for their respective cloud platforms.
4848

4949
Many sources are both - they handle platform transport AND transform data.
5050

51+
## Choose Your Template
52+
53+
| Complexity | Template | When to Use |
54+
| --------------------- | -------------- | ------------------------------------- |
55+
| Simple transformation | `fetch/` | Generic HTTP handler, data conversion |
56+
| Platform transport | `gcp/`, `aws/` | Cloud platform integration |
57+
| Browser interception | `dataLayer/` | DOM events, array interception |
58+
5159
## Process Overview
5260

5361
```
@@ -119,6 +127,24 @@ ls packages/server/sources/
119127
# - gcp: Cloud Functions specific
120128
```
121129

130+
### Gate: Research Complete
131+
132+
Before proceeding, confirm:
133+
134+
- [ ] Input trigger identified (HTTP, webhook, DOM, dataLayer)
135+
- [ ] Input schema documented (required/optional fields)
136+
- [ ] Fields mapped to walkerOS event structure
137+
138+
### Checkpoint: Research Review (Optional)
139+
140+
If working with human oversight, pause here to confirm:
141+
142+
- Input format and trigger mechanism correct?
143+
- Event name mapping makes sense?
144+
- Any platform quirks or auth requirements?
145+
146+
Continue only after approval.
147+
122148
---
123149

124150
## Phase 2: Create Input Examples (BEFORE Implementation)
@@ -276,6 +302,12 @@ export * as schemas from './schemas';
276302
export * as examples from './examples';
277303
```
278304

305+
### Gate: Examples Valid
306+
307+
- [ ] All example files compile (`npm run build`)
308+
- [ ] Can trace: input → expected output for each example
309+
- [ ] Edge cases included (minimal input, invalid input)
310+
279311
---
280312

281313
## Phase 3: Define Mapping
@@ -342,6 +374,11 @@ Input: inputs.pageViewInput
342374
Output: Should match outputs.pageViewEvent
343375
```
344376

377+
### Gate: Mapping Verified
378+
379+
- [ ] Event name map covers main input types
380+
- [ ] Each mapping rule traces correctly to expected output
381+
345382
---
346383

347384
## Phase 4: Scaffold
@@ -485,6 +522,11 @@ export async function push(
485522
export default { transformInput, transformBatch, push };
486523
```
487524

525+
### Gate: Implementation Compiles
526+
527+
- [ ] `npm run build` passes
528+
- [ ] `npm run lint` passes
529+
488530
---
489531

490532
## Phase 6: Test Against Examples
@@ -575,6 +617,12 @@ describe('HTTP handler', () => {
575617
});
576618
```
577619

620+
### Gate: Tests Pass
621+
622+
- [ ] `npm run test` passes
623+
- [ ] Tests verify against example outputs (not hardcoded values)
624+
- [ ] Invalid input handled gracefully (no crashes)
625+
578626
---
579627

580628
## Phase 7: Document
@@ -595,15 +643,15 @@ Key requirements for source documentation:
595643

596644
---
597645

598-
## Validation Checklist
646+
## Source-Specific Validation
599647

600-
- [ ] `npm run build` passes
601-
- [ ] `npm run test` passes
602-
- [ ] `npm run lint` passes
603-
- [ ] Examples cover main input patterns
604-
- [ ] Tests verify against example outputs
605-
- [ ] Invalid input handled gracefully
606-
- [ ] README documents input format
648+
Beyond [understanding-development](../understanding-development/SKILL.md)
649+
requirements (build, test, lint, no `any`):
650+
651+
- [ ] `dev.ts` exports `schemas` and `examples`
652+
- [ ] Examples include edge cases (minimal, invalid input)
653+
- [ ] Invalid input returns gracefully (no crashes, clear error)
654+
- [ ] Tests use examples for assertions (not hardcoded values)
607655

608656
---
609657

0 commit comments

Comments
 (0)