Skip to content

Commit 85410e6

Browse files
vtalasCopilot
andauthored
Update instructions (#923)
* Update instructions * Update .github/copilot-instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/copilot-instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/copilot-instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/copilot-instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .claude/commands/validate-and-fix-component.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d0beee9 commit 85410e6

File tree

3 files changed

+433
-256
lines changed

3 files changed

+433
-256
lines changed

.claude/CLAUDE.md

Lines changed: 79 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -2,208 +2,85 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5-
## Project Overview
5+
**Full development guide:** `.github/copilot-instructions.md` - Contains detailed patterns, examples, JSON schemas, and best practices.
66

7-
Appmixer Connectors is a collection of integrations for the Appmixer workflow engine. Each connector enables integration with an external service (Slack, GitHub, Salesforce, etc.) and contains components that perform specific actions.
7+
## Quick Reference
88

9-
## Commands
9+
### Commands
1010

1111
```bash
12-
# Install dependencies (required before running tests)
12+
# Install dependencies
1313
node scripts/npm_install.js
1414

15-
# Run all unit tests
15+
# Run tests
1616
npm run test-unit
17-
18-
# Run tests for a specific connector
1917
npm run test-unit -- test/<connector_name>
2018

21-
# Run linting
19+
# Linting
2220
npm run lint
23-
```
2421

25-
## Architecture
22+
# Validate outputType components
23+
npm run validate-outputtype
24+
```
2625

2726
### Connector Structure
2827

29-
Each connector lives in `src/appmixer/<connector_name>/` with this structure:
30-
3128
```
32-
<connector_name>/
33-
├── service.json # Service metadata (name, label, icon, version)
34-
├── auth.js # Authentication configuration (OAuth2 or API key)
29+
src/appmixer/<connector_name>/
30+
├── service.json # Service metadata
31+
├── auth.js # Authentication (OAuth2 or API key)
3532
├── bundle.json # Version and changelog
36-
├── package.json # Dependencies (optional)
37-
├── quota.js # Rate limiting rules (optional)
38-
├── lib.js # Shared utilities (optional)
39-
├── routes.js # HTTP routes for webhooks (optional)
40-
├── jobs.js # Background jobs (optional)
41-
└── <module>/ # Component module (usually "core")
33+
├── lib.js # Shared utilities (REQUIRED for outputType components)
34+
└── core/
4235
└── <ComponentName>/
43-
├── component.json # Component configuration and UI schema
44-
└── <ComponentName>.js # Component behavior logic
36+
├── component.json
37+
└── <ComponentName>.js
4538
```
4639

47-
### Component Types
48-
49-
**Action Components** (have `inPorts`):
50-
- **Get**: Retrieve single item by ID (`GetTask`, `GetUser`)
51-
- **List**: Retrieve all items, no filtering (`ListTasks`, `ListUsers`)
52-
- **Find**: Search with filters, includes `outputType` and `notFound` port (`FindTasks`)
53-
- **Create**: Create new item (`CreateTask`)
54-
- **Update**: Modify existing item by ID (`UpdateTask`)
55-
- **Delete**: Remove item by ID, returns empty object (`DeleteTask`)
56-
57-
**Trigger Components** (NO `inPorts`, use `properties` instead):
58-
- **Polling** (`tick: true`): Use `tick(context)` method, called periodically
59-
- **Webhook** (`webhook: true`): Use `start()`, `receive()`, `stop()` lifecycle methods
60-
- **Hybrid**: Both `tick` and `webhook` for real-time events with maintenance
61-
62-
### Key Files
63-
64-
- `component.json`: Defines inputs (`inPorts`), outputs (`outPorts`), authentication (`auth`), and UI inspector config
65-
- `<Component>.js`: Implements `receive(context)` for actions, `tick(context)` for polling triggers
66-
- `auth.js`: Either `type: 'apiKey'` or `type: 'oauth2'` with validation and profile fetching
67-
- `lib.js`: Shared helpers like `sendArrayOutput()` for Find/List components with `outputType` support
68-
69-
## Testing
70-
71-
### Unit Tests
72-
73-
Tests use Mocha with the stub in `test/utils.js`. The `createMockContext()` function provides a mock Appmixer context with stubbed methods for `httpRequest`, `sendJson`, `stateGet/Set`, etc.
40+
See: `.github/copilot-instructions.md` sections "Connector Structure", "Core Configuration Files"
7441

75-
Test files can be in `test/<connector>/` or `src/appmixer/<connector>/artifacts/test/`.
42+
### Authentication
7643

77-
### E2E Test Flows
44+
- **OAuth 2.0**: For services with OAuth flow (Google, GitHub, etc.)
45+
- **API Key**: For services using API keys or tokens
7846

79-
End-to-end test flows are JSON files (`test-flow*.json`) stored in the connector root directory that test complete workflows.
47+
See: `.github/copilot-instructions.md` section "Authentication Types"
8048

81-
**Naming Convention**:
82-
- Flow name format: **`"E2E Connector Name - feature"`** (e.g., `"E2E Dropbox - crud"`, `"E2E Slack - messages"`)
83-
- File name format: `test-flow-<feature>.json` (e.g., `test-flow-crud.json`)
84-
- The `testCase` field in ProcessE2EResults must match the flow name
85-
86-
**Best practices**:
87-
- Create **multiple smaller test flows** per connector (e.g., `test-flow-crud.json`, `test-flow-search.json`)
88-
- Prefer focused flows testing specific features over one large flow
89-
- **Ensure full coverage**: Every component in the connector MUST be tested in at least one flow
90-
91-
**Required components in order**:
92-
1. `OnStart` - Triggers the flow
93-
2. Your connector components - Under test
94-
3. `Assert` - Validate outputs (supported types: `equal`, `notEmpty`, `regex`)
95-
4. `AfterAll` - Cleanup coordination
96-
5. Cleanup components - Delete test data
97-
6. `ProcessE2EResults` - **REQUIRED** - Final component with `recipients`, `testCase`, `result` fields
98-
99-
**Critical Rules**:
100-
- **Assertion types**: Only `equal`, `notEmpty`, and `regex` are supported
101-
- **Multiple Asserts**: MUST be in separate branches with different y-coordinates
102-
- Use EXACT field names from component.json required fields
103-
- Pass data via `$.component-id.out.fieldName` variables
104-
- Always cleanup test data
105-
- Include ProcessE2EResults with standard store IDs (success: `64f6f1f9193228000754082f`, failed: `64f6f1f0193228000754082e`)
106-
- Verify all components are covered across all test flows
107-
108-
**See detailed guide**: `.github/copilot-instructions.md` section "End-to-End (E2E) Test Flows"
109-
110-
**Reference examples**:
111-
- `src/appmixer/googleDocs/test-flow.json`
112-
- `src/appmixer/monday/test-flow.json`
113-
114-
## Key Patterns
115-
116-
### Component Naming
49+
### Component Types
11750

118-
Components must follow the pattern: `vendor.connectorName.module.componentName`
119-
- Use `appmixer` as vendor
120-
- Use `core` as default module name
121-
- Example: `appmixer.slack.core.SendMessage`
51+
| Type | Purpose | Key Rule |
52+
|------|---------|----------|
53+
| **Get** | Single item by ID | Returns item data |
54+
| **List** | All items, no filtering | Uses `outputType`, no limit/offset |
55+
| **Find** | Search with filters | Uses `outputType`, has `notFound` port |
56+
| **Create** | Create new item | Returns created item |
57+
| **Update** | Modify by ID | Returns `{}` |
58+
| **Delete** | Remove by ID | Returns `{}` |
12259

123-
### Component Principles
60+
See: `.github/copilot-instructions.md` sections "Find (Items) Components", "List (Items) Components", "Get (Item) Components", "Create (Item) Components", "Delete (Item) Components", "Update (Item) Components"
12461

125-
**ID Requirements**: For components that require an ID as input, there MUST be another component that returns the entity containing that ID.
126-
- Example: If `GetEmail` requires `emailId`, then `FindEmails` must exist and return entities with `emailId`
62+
**Triggers**: Use `properties` (not `inPorts`), implement `tick()` or `start()/receive()/stop()`
12763

128-
### Component Behavior
64+
See: `.github/copilot-instructions.md` sections "Trigger Components", "Trigger Behavior Requirements"
12965

130-
```javascript
131-
module.exports = {
132-
async receive(context) {
133-
const { inputField } = context.messages.in.content;
134-
135-
const response = await context.httpRequest({
136-
method: 'GET',
137-
url: 'https://api.example.com/resource',
138-
headers: { 'Authorization': `Bearer ${context.auth.accessToken}` }
139-
});
140-
141-
return context.sendJson(response.data, 'out');
142-
}
143-
};
144-
```
66+
## Critical Rules
14567

146-
**Delete Components**:
147-
- MUST return empty object: `return context.sendJson({}, 'out');`
148-
- MUST have `outPorts: ['out']` in component.json
149-
- MUST have at least one required input (the ID of entity being deleted)
68+
### outputType Components (REQUIRED)
15069

151-
**Update Components**:
152-
- MUST return empty object: `return context.sendJson({}, 'out');`
153-
- MUST have at least one required input (the ID of entity being updated)
70+
Components with `outputType` **MUST** use lib.js helpers:
71+
- `lib.sendArrayOutput({ context, outputType, records })`
72+
- `lib.getOutputPortOptions(context, outputType, schema, { label })`
73+
- Array output field: always `result` (not `records`)
15474

155-
### Find/List Components with outputType
75+
**Canonical implementation:** `appmixer-cli/src/ai/src/templates/libs/lib.js`
15676

157-
Find and List components support `outputType` (first/array/object/file) via `lib.js` helpers:
158-
- Use `lib.getOutputPortOptions()` for dynamic output port schema
159-
- Use `lib.sendArrayOutput()` to handle different output types
160-
- Do NOT include `limit` or `offset` fields - use API's max page size internally
77+
See: `.github/copilot-instructions.md` section "outputType Helper Functions"
16178

162-
### Trigger Components
79+
### Delete/Update Components
16380

164-
**Polling trigger** (`tick: true`):
16581
```javascript
166-
module.exports = {
167-
async tick(context) {
168-
const { projectId } = context.properties; // NOT context.messages.in.content
169-
const state = await context.loadState();
170-
const known = state.known ? new Set(state.known) : null;
171-
172-
const { data } = await context.httpRequest({ ... });
173-
174-
// Compare and send only new items
175-
for (const item of data.items) {
176-
if (!known || !known.has(item.id)) {
177-
await context.sendJson(item, 'out');
178-
}
179-
}
180-
await context.saveState({ known: data.items.map(i => i.id) });
181-
}
182-
};
183-
```
184-
185-
**Webhook trigger** (`webhook: true`):
186-
```javascript
187-
module.exports = {
188-
async start(context) {
189-
const webhookUrl = context.getWebhookUrl();
190-
const { data } = await context.httpRequest({
191-
method: 'POST', url: 'https://api.service.com/webhooks',
192-
data: { url: webhookUrl, events: ['item.created'] }
193-
});
194-
return context.saveState({ webhookId: data.id });
195-
},
196-
async receive(context) {
197-
if (context.messages.webhook) {
198-
await context.sendJson(context.messages.webhook.content.data, 'out');
199-
return context.response(); // MUST acknowledge webhook
200-
}
201-
},
202-
async stop(context) {
203-
const { webhookId } = await context.loadState();
204-
if (webhookId) await context.httpRequest({ method: 'DELETE', url: `.../${webhookId}` });
205-
}
206-
};
82+
// MUST return empty object
83+
return context.sendJson({}, 'out');
20784
```
20885

20986
### Required Input Validation
@@ -214,102 +91,56 @@ if (!taskId) {
21491
}
21592
```
21693

217-
## Context API
218-
219-
**Logging**: `context.log` must use this signature:
220-
```javascript
221-
context.log(level, message, [data]);
222-
```
223-
224-
## Authentication
225-
226-
**requestProfileInfo** in `auth.js` (type: `apiKey`) MUST return:
227-
- Object with obfuscated apiKey (if profile API not available), OR
228-
- Object with profile info
229-
230-
## Code Style
231-
232-
- 4 spaces indentation
233-
- camelCase for JavaScript variables (destructure with aliases if API uses snake_case)
234-
- Property names in component.json must match `context.messages.in.content` keys
235-
- Use underscore or camelCase separators in property names (not pipe `|`)
236-
- **Remove unused variables/imports** - every declared variable must be used
237-
- **No single-option selects** - hardcode constants instead of making them user inputs
238-
- **Date/Time fields**: Use inspector type `date-time`, NOT `text`
239-
- Schema: `"type": "string", "format": "date-time"`
240-
- Inspector: `"type": "date-time"`
241-
- For date-only: Add `{ "enableTime": false }` config
242-
243-
## Documentation Reference
244-
245-
Full connector development guide: https://docs.appmixer.com/getting-started/custom-connectors
94+
See: `.github/copilot-instructions.md` section "Component Behavior (JavaScript) Requirements"
24695

247-
### Testing compoenents
96+
### Output Port Schema
24897

249-
it's possible to test components locally using the Appmixer CLI.
98+
Use **either** `schema` or `options` in outPorts, **NOT both**.
25099

251-
#### setup
100+
See: `.github/copilot-instructions.md` section "Output Port Schema Definition"
252101

253-
before running a test, make sure the connector is authenticated. to check the authentication status, run `appmixer test auth validate <path to auth.js file>`
102+
## Testing Components
254103

255-
for example:
256-
`appmixer test auth validate ./src/appmixer/slack/auth.js`
257-
258-
if the authentication is not valid, stop and ask user to authenticate.
259-
260-
#### testng component
261-
262-
use command `appmixer test component <path to component>`
263-
264-
sage: appmixer test component [options] [componentDir]
265-
266-
Options:
267-
-c, --config [config] service configuration
268-
-f, --transform [transform] specify transformer
269-
-i, --input [input] input test message object of file path to the JSON file (default: [])
270-
-m, --mime [mime] mime type, application/json by default
271-
-p, --properties [properties] component properties (JSON format)
272-
-s, --no-state do not show component's state
273-
-t, --tickPeriod [tickPeriod] tick period (in ms), default is 10000 ms
274-
-u, --appmixerApiUrl [appmixerApiUrl] public url, used to test webhooks
275-
-h, --help output usage information
104+
```bash
105+
# Validate auth first
106+
appmixer test auth validate ./src/appmixer/<connector>/auth.js
276107

277-
Examples:
278-
Following example will send input message { "to": "your@email.com" } to component's input port 'in'.
279-
You always have to specify to which input port you want to send message.
280-
$ appmixer test component [path-to-your-component-directory] -i '{ "in": { "to": "your@email.com" } }'
108+
# Test component
109+
appmixer test component ./src/appmixer/<connector>/core/<Component> -i '{"in":{...}}'
110+
```
281111

282-
This is how to specify transformer function from transformer file.
283-
$ appmixer test component [path-to-component] -i '{}' -f './transformers#channelsToSelectArray'
112+
See: `.github/copilot-instructions.md` section "Testing Guidelines"
284113

285-
How to set properties and tick period:
286-
$ appmixer t c [path-to-component] -p '{ "channelId: "123XYZ" }' -t 2000
114+
## E2E Test Flows
287115

288-
You can send more than one message:
289-
$ appmixer t c [path-to-component] -i '{ "in": { "to": "first@email.com" }}' -i '{ "in": { "to": "second@email.com" }}'
116+
Required components in order:
117+
1. `OnStart` → Components under test → `Assert``AfterAll` → Cleanup → `ProcessE2EResults`
290118

291-
You can also specify the input in a separate file and use the file as a input:
292-
$ appmixer t c [path-to-component] -i [path-to-input-json-file]
119+
**Critical:**
120+
- Assertion types: `equal`, `notEmpty`, `regex` only
121+
- Every component MUST be tested in at least one flow
293122

294-
sample input json file:
295-
{
296-
"in": {
297-
"type": "page",
298-
"outputType": "object"
299-
}
300-
}
123+
See: `.github/copilot-instructions.md` section "End-to-End (E2E) Test Flows"
301124

302-
You can specify service configuration (that is normally set through the Backoffice):
303-
$ appmixer t c [path-to-component] -c '{ "key": [service-api-key] }'
125+
## Code Style
304126

305-
You can test webhook components as well. You have to create a tunnel to the localhost server,
306-
which is running by default on http://localhost:2300. You can use ngrok to create the tunnel, for example.
307-
$ appmixer t c [path-to-webhook-component] --appmixerApiUrl "https://541a-86-49-182-253.ngrok.io"
127+
- 4 spaces indentation
128+
- camelCase for JS variables
129+
- Date fields: use `date-time` inspector type, not `text`
130+
- Remove unused variables/imports
308131

309-
You can run appmixer command in your component's directory:
310-
$ appmixer test c
311-
Directory has to contain component.json file and component's source code file.
132+
See: `.github/copilot-instructions.md` sections "Code Style Guidelines (For All)", "Development Guidelines (For All)"
312133

134+
## Reference
313135

136+
- **Full guide:** `.github/copilot-instructions.md`
137+
- **External docs:** https://docs.appmixer.com/getting-started/custom-connectors
314138

139+
### Key Sections in Full Guide
315140

141+
| Topic | Section |
142+
|-------|---------|
143+
| Input/output types | "JSON Schema Reference", "Type Mapping for Input Ports" |
144+
| Component.json structure | "Desired Attribute Order in component.json", "component.json Requirements" |
145+
| Context methods | "Context API" |
146+
| AI-specific rules | "Best Practices (AI Assistance)", "Critical Restrictions for AI Code Generation" |

0 commit comments

Comments
 (0)