Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/npm-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ jobs:
- name: Install dependencies
run: bun install

- name: Generate node catalog
run: bun run crawl-nodes
- name: Generate node catalog and schemas
run: bun run crawl

- name: Capture trigger schemas
run: bun run scripts/capture-trigger-schemas.ts --from-existing
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern - secrets in script execution: The trigger schema capture script receives N8N credentials and could potentially access/modify workflows.

Risk: If the script is compromised or has a bug, it could leak credentials or modify production workflows.

Recommendations:

  1. Use a dedicated test/staging n8n instance for schema capture, not production
  2. Document that N8N_API_KEY should be a read-only key if possible
  3. Add safeguards in the script to prevent accidental workflow modification
  4. Consider running this in an isolated environment
- name: Capture trigger schemas
  run: bun run scripts/capture-trigger-schemas.ts --from-existing
  env:
    N8N_API_KEY: ${{ secrets.N8N_SCHEMA_CAPTURE_KEY }}  # Separate read-only key
    N8N_HOST: ${{ secrets.N8N_STAGING_HOST }}  # Use staging, not prod

env:
N8N_API_KEY: ${{ secrets.N8N_API_KEY }}
N8N_HOST: ${{ secrets.N8N_HOST }}

- name: Build package
run: bun run build
Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/schema-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Schema Update

on:
schedule:
- cron: '0 3 * * 1' # Monday 3AM UTC
workflow_dispatch:

jobs:
check:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install

- name: Generate schemas
run: bun run crawl

- name: Capture trigger schemas
run: bun run scripts/capture-trigger-schemas.ts --from-existing
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if --from-existing is parsed anywhere in the capture script
rg -n 'from-existing' scripts/capture-trigger-schemas.ts

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Look at the argument parsing logic around lines 306-310
sed -n '300,320p' scripts/capture-trigger-schemas.ts

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 931


🏁 Script executed:

#!/bin/bash
# Search for --from-existing across the entire codebase
rg -r 'from-existing' --type ts --type js

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 475


🏁 Script executed:

#!/bin/bash
# Check the workflow file mentioned in the review
grep -n 'from-existing' .github/workflows/schema-update.yml

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 153


🏁 Script executed:

#!/bin/bash
# Search for this flag in all workflow files
grep -rn 'from-existing' .github/workflows/

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 297


🏁 Script executed:

#!/bin/bash
# Check the full main() function to understand the behavior without this flag
sed -n '300,400p' scripts/capture-trigger-schemas.ts | head -60

Repository: elizaos-plugins/plugin-n8n-workflow

Length of output: 2371


--from-existing flag is not recognized by capture-trigger-schemas.ts.

The script only parses --trigger=, --timeout=, --keep, and --create-only (lines 307-310). The --from-existing flag, which is passed in both .github/workflows/schema-update.yml (line 23) and .github/workflows/npm-deploy.yml (line 77), is silently ignored. The script will create and activate new workflows instead of only reading from existing ones, contrary to the intended behavior in CI.

🤖 Prompt for AI Agents
In @.github/workflows/schema-update.yml around lines 22 - 23, The CI flag
--from-existing is not parsed by capture-trigger-schemas.ts so it is ignored;
update the CLI parsing (where --trigger, --timeout, --keep, and --create-only
are handled) to accept a --from-existing boolean (e.g., set a fromExisting
variable in parseArgs or the existing argument-parsing block) and then propagate
that flag into the main flow (functions like captureTriggerSchemas /
createAndActivateWorkflows or whichever routines perform creation/activation) so
that when fromExisting is true the code only reads existing workflows and
explicitly skips any creation or activation steps.

env:
N8N_API_KEY: ${{ secrets.N8N_API_KEY }}
N8N_HOST: ${{ secrets.N8N_HOST }}

- run: bun run build

- name: Download latest release
run: |
mkdir -p /tmp/latest
bunx npm pack @elizaos/plugin-n8n-workflow@latest --pack-destination /tmp
tar xzf /tmp/elizaos-plugin-n8n-workflow-*.tgz -C /tmp/latest
Comment on lines +27 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

First-release scenario will fail silently.

If @elizaos/plugin-n8n-workflow hasn't been published to npm yet, bunx npm pack @elizaos/plugin-n8n-workflow@latest will fail, aborting the entire job. Consider adding continue-on-error: true to this step and treating a missing release as "everything changed":

♻️ Suggested fix
       - name: Download latest release
+        id: download
+        continue-on-error: true
         run: |
           mkdir -p /tmp/latest
           bunx npm pack `@elizaos/plugin-n8n-workflow`@latest --pack-destination /tmp
           tar xzf /tmp/elizaos-plugin-n8n-workflow-*.tgz -C /tmp/latest

Then in the compare step, treat a download failure as "changed":

       - name: Compare with latest release
         id: compare
         run: |
+          if [ "${{ steps.download.outcome }}" = "failure" ]; then
+            echo "No previous release found — treating as changed"
+            echo "changed=true" >> $GITHUB_OUTPUT
+            exit 0
+          fi
           CHANGED=false
🤖 Prompt for AI Agents
In @.github/workflows/schema-update.yml around lines 27 - 31, Add
continue-on-error: true to the "Download latest release" step that runs bunx npm
pack `@elizaos/plugin-n8n-workflow`@latest so the job doesn't abort if the package
isn't published; detect the failure by checking the presence of the extracted
/tmp/latest folder or by setting a step output (e.g., download_succeeded) based
on the pack/tar exit status, then update the subsequent compare step to treat
download_succeeded=false (or missing /tmp/latest) as "changed" so the workflow
proceeds as a first-release case.


- name: Compare with latest release
id: compare
run: |
CHANGED=false

for file in defaultNodes.json schemaIndex.json triggerSchemaIndex.json; do
NEW="dist/data/$file"
OLD="/tmp/latest/package/dist/data/$file"

if [ ! -f "$NEW" ]; then
continue
fi
Comment on lines +42 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing local artifact skipped silently — may mask build failures.

If dist/data/$file doesn't exist (e.g., the crawl or build step failed partially), the loop continues without marking anything as changed. This means a broken build could produce an empty diff and no version bump, silently swallowing the problem. Consider logging a warning or failing if an expected file is absent after a successful build.

🤖 Prompt for AI Agents
In @.github/workflows/schema-update.yml around lines 42 - 44, The loop that
skips missing artifacts currently does `if [ ! -f "$NEW" ]; then continue; fi`
which silently hides build failures; change this to emit a clear failure or
warning by checking the same `$NEW` variable inside the loop and either (a)
`echo` a descriptive warning with the file name and set a non-success flag
(e.g., `MISSING=true`) so the workflow can fail after the loop, or (b)
immediately `echo "ERROR: missing expected artifact $NEW"` and `exit 1`; update
the surrounding logic to use the `MISSING` flag if chosen so the job fails when
any expected `dist/data/$file` is absent.


if [ ! -f "$OLD" ]; then
echo "$file: NEW file (not in latest release)"
CHANGED=true
continue
fi

if ! diff <(jq -S . "$NEW") <(jq -S . "$OLD") > /dev/null 2>&1; then
echo "$file: CHANGED"
CHANGED=true
else
echo "$file: unchanged"
fi
done

echo "changed=$CHANGED" >> $GITHUB_OUTPUT

- name: Bump version
if: steps.compare.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
npm version patch --no-git-tag-version
VERSION=$(jq -r .version package.json)
git add package.json
git commit -m "chore: bump to v${VERSION} (schema update)"
git push
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: This workflow automatically pushes version bumps to main without creating a PR for review.

Risk: Schema changes could introduce breaking changes that bypass code review.

Recommendation: Instead of auto-pushing, create a PR:

- name: Create PR for schema update
  if: steps.compare.outputs.changed == 'true'
  run: |
    git checkout -b schema-update-$(date +%Y%m%d)
    git add package.json dist/data/
    git commit -m "chore: update schemas (automated)"
    git push origin schema-update-$(date +%Y%m%d)
    gh pr create --title "chore: update schemas" --body "Automated schema update from scheduled job"

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Trigger publish
if: steps.compare.outputs.changed == 'true'
run: gh workflow run npm-deploy.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ coverage/
.temp/
tmp/
temp/
# Generated node catalog (run `bun run crawl-nodes` to regenerate)
# Generated data files (run `bun run crawl` to regenerate)
src/data/defaultNodes.json
src/data/schemaIndex.json
src/data/triggerSchemaIndex.json

# Script cache
.cache/

22 changes: 22 additions & 0 deletions __tests__/fixtures/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ export function createSlackNode(overrides?: Partial<N8nNode>): N8nNode {
};
}

export function createGmailTriggerNode(overrides?: Partial<N8nNode>): N8nNode {
return {
name: 'Gmail Trigger',
type: 'n8n-nodes-base.gmailTrigger',
typeVersion: 1,
position: [250, 300],
parameters: {},
...overrides,
};
}

export function createGithubTriggerNode(overrides?: Partial<N8nNode>): N8nNode {
return {
name: 'GitHub Trigger',
type: 'n8n-nodes-base.githubTrigger',
typeVersion: 1,
position: [250, 300],
parameters: {},
...overrides,
};
}

// ============================================================================
// WORKFLOWS
// ============================================================================
Expand Down
73 changes: 73 additions & 0 deletions __tests__/integration/actions/createWorkflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,79 @@ describe('CREATE_N8N_WORKFLOW action', () => {
});
});

// ==========================================================================
// MODIFY INCLUDES CHANGES IN PREVIEW
// ==========================================================================

describe('handler - modify includes changes in preview', () => {
test('preview data includes changed parameters after modify', async () => {
const draft: WorkflowDraft = {
workflow: {
name: 'Gmail Forward',
nodes: [
{
name: 'Gmail Trigger',
type: 'n8n-nodes-base.gmailTrigger',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { pollTimes: { item: [{ mode: 'everyMinute' }] } },
},
{
name: 'Forward Email',
type: 'n8n-nodes-base.gmail',
typeVersion: 2,
position: [200, 0] as [number, number],
parameters: { operation: 'send', sendTo: '[email protected]' },
credentials: { gmailOAuth2Api: { id: 'cred-1', name: 'Gmail' } },
},
],
connections: {
'Gmail Trigger': { main: [[{ node: 'Forward Email', type: 'main', index: 0 }]] },
},
},
prompt: 'Forward emails',
userId: 'user-001',
createdAt: Date.now(),
};

const modifiedWorkflow = {
...draft.workflow,
nodes: [
draft.workflow.nodes[0],
{
...draft.workflow.nodes[1],
parameters: { operation: 'send', sendTo: '[email protected]' },
},
],
};

const mockService = createMockService({
modifyWorkflowDraft: mock(() => Promise.resolve(modifiedWorkflow)),
});

const runtime = createMockRuntime({
services: { [N8N_WORKFLOW_SERVICE_TYPE]: mockService },
useModel: createUseModelMock({ intent: 'modify', reason: 'User wants to modify' }),
cache: { 'workflow_draft:user-001': draft },
});

const callback = createMockCallback();

await createWorkflowAction.handler(
runtime,
createMockMessage({ content: { text: 'change email to [email protected]' } }),
createMockState(),
{ intent: 'modify', modification: 'change email to [email protected]' },
callback
);

// The callback text should contain the new email (changes are passed to formatActionResponse)
const calls = (callback as any).mock.calls;
const lastText = calls[calls.length - 1][0].text;
expect(lastText).toContain('[email protected]');
});
});

// ==========================================================================
// CALLBACK SUCCESS STATUS TESTS
// ==========================================================================
Expand Down
Loading
Loading