Skip to content

Feat/submissions workflow#55

Draft
alex-strapi wants to merge 14 commits intostrapi:developfrom
alex-strapi:feat/submissions-workflow
Draft

Feat/submissions workflow#55
alex-strapi wants to merge 14 commits intostrapi:developfrom
alex-strapi:feat/submissions-workflow

Conversation

@alex-strapi
Copy link
Copy Markdown
Collaborator

@alex-strapi alex-strapi commented Apr 17, 2026

Added submission workflow for both plugins and templates, plus the n8n automation + security-scan pipeline that hangs off Alex's moderation plugin lifecycle hooks.

Closes #8 #9 #10 #12 #13 #14 #15 #16 #17 #31 #32 #33 #34 #35

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

@alex-strapi is attempting to deploy a commit to the Strapi-Website Team on Vercel.

A member of the Team first needs to authorize it.

derrickmehaffy and others added 7 commits April 17, 2026 11:18
…aude skills

Extends apps/automation with an engine-agnostic Docker/Podman compose setup
that runs a local n8n plus an opt-in n8n-mcp service (--profile mcp). A shell
wrapper auto-detects the installed compose engine so pnpm scripts work for
both Docker and Podman users.

Adds Node 20+ scripts for exporting workflows from the local n8n into
apps/automation/workflows/<slug>/workflow.json and importing them back,
with instance-specific fields stripped for clean diffs and optional README
files preserved across re-exports.

Introduces seven project-scoped Claude Code skills covering lifecycle
(start/stop/restart/status), workflow sync (export/import), and domain
expertise for building workflows via n8n-mcp tools. Adds a root CLAUDE.md
that indexes them and gitignores personal .claude/settings*.json plus the
tmp/ scratch directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a README section covering both project-scoped (.mcp.json with
${MCP_AUTH_TOKEN} substitution) and user-scoped (claude mcp add without
--scope project) registration paths, plus a curl verification step. The
n8n-start skill now reminds the user about the registration step when the
mcp profile is brought up, and CLAUDE.md points at the new README section
from the skill index.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…own lint

Expands the "Connecting Claude Code to the local n8n-mcp" section to cover
all three scopes (local / user / project) with a decision table, not just
two. The prior write-up conflated local (default) with user scope. Also
notes that an already-running Claude Code session must be relaunched after
`claude mcp add` for the new server's tools to appear.

Incidentally fixes pre-existing markdown-lint warnings in the README
(bare URLs in the Ports table, missing blank lines around fenced code
blocks inside numbered-list steps, missing language hint on the workflow
tree diagram).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ensures the workflows directory survives in version control when no
workflows are checked in yet, so the n8n-export / n8n-import skills
have a target path immediately after cloning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scaffolds six draft workflows covering the automation work tracked in
issues strapi#12-strapi#17, strapi#21, strapi#53, and strapi#54. Everything is inactive by default —
they reference a Strapi email-template content-type (strapi#53) and lifecycle
webhook endpoints that don't exist yet, so activation waits on Boaz's
CMS work landing.

- render-email (shared sub-workflow): fetches the template from Strapi
  by key, interpolates {{ variables }}, wraps in a branded HTML shell,
  and sends via SendGrid (community@strapi.io, separate key from strapi#54).
  Other workflows call this rather than each handling SendGrid themselves.
- package-submission-received: strapi#12 developer email + strapi#16 Slack post
- package-approved: strapi#14 developer email + strapi#17 Slack post
- package-declined: strapi#15 developer email with decline reason
- package-changes-requested: strapi#13 developer email with reviewer feedback
- cleanup-new-label-after-60d: strapi#21 daily cron that strips the 'is_new'
  label from packages older than 60 days

Webhook paths are namespaced under /strapi/* and expect header auth.
Env vars referenced: STRAPI_BASE_URL, STRAPI_API_TOKEN. Credentials for
SendGrid, Slack, and the webhook header need to be attached in the n8n
UI once the real deployment targets are picked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses strapi#53 and strapi#54 — closing the Strapi-side dependencies for the
n8n package review automation workflows on feat/n8n-mcp-automation-setup.

- email-template content-type (collectionType, draftAndPublish: false)
  with key (uid), subject, body (richtext), description, from_name,
  reply_to. n8n workflows look up templates by key.
- Seeds four default templates on bootstrap if missing:
  plugin-submission-received, plugin-approved, plugin-declined,
  plugin-changes-requested. Uses `strapi.documents(...)` v5 API, skips
  existing rows so edits in the admin UI aren't clobbered.
- @strapi/provider-email-sendgrid added at the same experimental
  version as the rest of the Strapi catalog, wired through
  config/plugins.ts with SENDGRID_API_KEY / EMAIL_DEFAULT_FROM /
  EMAIL_DEFAULT_REPLY_TO env vars. Defaults the sender to
  community@strapi.io per the decided automation architecture.
- Automation emails continue to use a separate SendGrid key configured
  in n8n — native Strapi emails (password resets, admin invites) use
  the key wired here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next.js dev server defaults to :3000, which collides with the n8n-mcp
host mapping. Remap n8n-mcp to 3100 on the host; container-internal
port stays at 3000 so the image defaults are untouched.

Updates README, n8n-start skill, and n8n-status skill accordingly. Any
existing Claude Code MCP registration needs its URL updated from
http://localhost:3000/mcp → http://localhost:3100/mcp (remove the old
server, then re-register with the new URL).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was linked to issues Apr 17, 2026
derrickmehaffy and others added 4 commits April 17, 2026 14:30
- Rename plugin-submission.npm_package_name -> package_location (URL; supports
  npm/packagist/pypi/rubygems/nuget), propagated through admin UI, service,
  promoteToPackage, automated checks, and generated types
- Add security_scan_* fields (status/started_at/run_at/dependencies/ai_analysis
  /summary) on both plugin-submission and template-submission; drop orphaned
  npm_advisories field (consolidated into OSV)
- New helper get-package-security-info.js reuses package-info registry extract
  functions to fetch deps/scripts/install-hooks/readme/declared-repo before
  firing the scan webhook (n8n does not duplicate registry lookups)
- New helper n8n-webhook.js centralises webhook URL construction from
  CLOUD_APP_URL (Strapi Cloud default) + N8N_WEBHOOK_MODE (production | test)
- Wire Alex's 7 lifecycle TODO markers: plugin-submission-received/approved/
  declined/changes-requested + template-submission-received/approved/declined
- New content-api routes per submission type: security-scan-result (write-back
  from n8n) + stale-scans (polled by the sweeper workflow)
- Delete security-checks.js; registry-based vuln scanning and AI analysis now
  run in n8n workflows
- Extend email-templates seed with 3 template-* entries; drop dashboard_link
  from developer-facing email bodies (admin URL is internal-only)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename 4 package-* workflows to plugin-* (folders + workflow name + webhook
  path + Extract Payload reading flat CMS payload instead of Strapi content-type
  lifecycle body.entry.*)
- Add 3 template-* workflows mirroring the plugin-* ones, adapted for the
  template-submission content type
- New security-scan workflow: fires on manual admin trigger, consumes the
  CMS-prefetched package_info, runs OSV vuln scan + repo package.json cross-
  check + install-script flagging + Claude Haiku AI analysis on up to 20 source
  files (npm via jsDelivr / GitHub for repo fallback). Writes back per-stage
  results to Strapi via content-api
- New error-handler workflow (settings.errorWorkflow on all 11 community-hub
  workflows) — Error Trigger -> Scope Check allowlist -> Slack alert to
  #integration-marketplace. Opt-in via wiring + defensive name allowlist
- New scan-timeout-sweeper workflow — runs every 15 min, queries the CMS for
  plugin/template submissions stuck in security_scan_status=running past a 30-
  minute threshold, PATCHes them to failed, Slack-notifies the count
- Code nodes in security-scan wrapped in try/catch with defined fallback
  shapes so a parse error mid-flow produces a typed error item instead of
  aborting the whole workflow
- Drop dashboard_link from plugin-changes-requested email variables (admin URL
  is internal Slack-only — dropped from developer email body + payload
  defence-in-depth)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace npm_package_name field with package_location (URL) on the plugin
  submission form + Next.js proxy route — matches the renamed CMS field
- Label "NPM Package Name" -> "Registry URL", hint generalised to cover npm,
  Packagist, PyPI, RubyGems, NuGet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New ./scripts/setup.sh (run via `pnpm bootstrap` — pnpm setup is shadowed
  by pnpm's own built-in). Installs deps, seeds .env from .env.example, warns
  on 'tobemodified' placeholders, reports compose engine, flags low
  fs.inotify.max_user_instances (Linux) that would crash Turbopack's watcher
- Add idempotency to apps/automation/scripts/compose.sh — 'up' detects already-
  running services and exits 0 instead of reconciling, so turbo dev doesn't
  conflict when the stack is already up
- Give apps/search its own copy of the engine-agnostic compose wrapper (was
  hardcoded to docker-compose which fails on podman-only boxes); add
  stop/logs/ps scripts for parity with automation
- Switch automation + search dev scripts to `up -d` so turbo doesn't hang
  attaching to container logs while trying to run cms + web in parallel
- Root package.json shortcuts: n8n:start/stop + search:start/stop

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@derrickmehaffy
Copy link
Copy Markdown
Member

derrickmehaffy commented Apr 17, 2026

My additions on top of Alex's moderation plugin

Infra (apps/automation/): Engine-agnostic compose stack for local n8n + n8n-mcp (Docker or Podman), workflow version-control scripts (workflows:export / :import), and 7 Claude skills for start/stop/restart/status/export/import/workflow-builder.

n8n workflows (11 total): plugin-submission-received / plugin-approved / plugin-declined / plugin-changes-requested + the three template-* equivalents, all wired to Alex's service lifecycle TODO markers through a shared render-email sub-workflow with Mustache-style interpolation. Plus security-scan (OSV vuln scan + repo cross-check + Claude Haiku AI analysis on up to 20 shipped source files via jsDelivr / GitHub), error-handler (Error Trigger → Slack #integration-marketplace, allowlisted to community-hub workflows), scan-timeout-sweeper (scheduled reaper for scans stuck in running), and cleanup-new-label-after-60d.

CMS (apps/cms/):

  • email-template content type (closes Automation: Email template #33) + SendGrid provider config
  • New n8n-webhook.js helper — centralises URL construction from CLOUD_APP_URL + N8N_WEBHOOK_MODE (one env flip switches all webhooks between prod /webhook/… and test /webhook-test/…)
  • New get-package-security-info.js — reuses package-info's registry extract functions to pre-fetch deps / scripts / install-hooks / README before firing the scan webhook, so n8n never duplicates registry lookups
  • Schema additions on both submission types: security_scan_* fields (status / started_at / run_at / dependencies / ai_analysis / summary)
  • npm_package_namepackage_location rename across schema, admin UI, promoted-package mapping, and automated checks — multi-registry URL (npm / Packagist / PyPI / RubyGems / NuGet, inferred from hostname)
  • Two new content-api routes per submission type: security-scan-result (write-back from n8n) + stale-scans (polled by the sweeper) — API-token auth

Web (apps/web/): Submit form + Next.js proxy route migrated to multi-registry URL input; label + hint + placeholder generalised.

Dev experience:

  • pnpm bootstrap — installs, seeds .env files from examples, warns on tobemodified placeholders, flags low fs.inotify.max_user_instances on Linux (crashes Turbopack otherwise)
  • Compose wrapper idempotent (up skips when the stack is already running)
  • apps/search got the engine-agnostic compose wrapper too (was hardcoded to docker-compose, broke on podman-only boxes)
  • Detached-mode dev scripts so turbo doesn't hang attaching to container logs

Still open (none block merge)

  • CMS: admin-UI "Run Security Scan" button (endpoint exists, UI pending), minimal content seed for home/categories (CMS is empty by default → web 404s), fill in real .env secrets
  • n8n: activate the 12 imported workflows + wire credentials (Strapi API token, Anthropic x-api-key, Slack OAuth, SendGrid, webhook auth header), phase-2 impl of non-npm registry deep-scan
  • Deploy: set N8N_WEBHOOK_BASE_URL + N8N_WEBHOOK_MODE on CMS env (CLOUD_APP_URL auto-injects on Strapi Cloud)

checks?: Record<string, AutomatedCheckResult>;
}

interface PluginSubmissionDetail {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we infer this type from Data.ContentType<'plugin::moderation.plugin-submission'>?

@@ -0,0 +1,138 @@
{
"kind": "collectionType",
"collectionName": "plugin_submissions",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering if we should change the naming of this content type. It seems that package_review is more suitable.

"type": "string",
"required": true
},
"package_location": {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you help me better understand why we need this intermediary content type? It seems to me that we're violating the 'single source of truth' ideology. What if a package updates their package location later on, do we then need to retroactively update the package location in the submission content type?

config: {
// Public endpoint — auth is handled at the Next.js proxy layer.
// The proxy must include a valid API token via Authorization header.
auth: false,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we're using an API token in Next.js then auth should be true here.

}

// Create the package entry in draft state. Publish step is manual.
const pkg = await strapi.documents("api::package.package").create({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is where we'll also need to look up the better auth user by email, and if it's not there create it.

"Sent by n8n when a package is submitted to the community marketplace. Variables: package_name, author_name, git_repository.",
body: `Hi {{ author_name }},

Thanks for submitting **{{ package_name }}** to the Strapi community marketplace.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we style this email template? @derrickmehaffy?

*/
export async function GET() {
try {
const res = await fetch(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For GET requests we don't need to proxy. We can just do that using the cmsClient from server side Next.js

return {
find: <const TParams extends GetQueryParams<TContentTypeUID>>(
queryParams?: TParams,
) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This indentation doesn't seem right?

Comment thread package.json
"n8n:start": "pnpm --filter automation start",
"n8n:stop": "pnpm --filter automation n8n:down",
"search:start": "pnpm --filter search start",
"search:stop": "pnpm --filter search stop",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need these scripts? The goal is that we just run a single pnpm run dev command from the root and that starts all the apps/packages

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Strapi: Template submission endpoint Strapi: Package submission endpoint Security review Business review Review workflow

3 participants