Generated banner for Rp Quick Woocommerce
Generated logo for Rp Quick Woocommerce

Rp Quick Woocommerce

WixOfficialPublished by Wix

  • Skill
  • Workflow
  • Automation and integrations
  • MIT
  • Low risk
  • Has scripts

From the author

Runs the bounded deterministic public WooCommerce quick-import adapter after a qualifying source probe.
— the description by Wix

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The rp-quick-woocommerce skill provides a structured, deterministic procedure for importing entity data from WooCommerce into a target platform. Designed for scenarios where a quick-import mode is pre-selected, this skill relies on a predefined entity graph and avoids external dependencies like browser scraping, plugin endpoints, or LLM-based mapping. It follows a strict sequence of preflight, planning, setup, extraction, and import steps to ensure data integrity and consistency.

What it does

  • Executes a preflight check to validate the WooCommerce source environment.
  • Generates a deterministic import plan based on a predefined entity graph.
  • Idempotently configures a fallback blog member for the target platform.
  • Performs data extraction and import operations using a structured manifest.
  • Supports dry-run execution for safe testing of the import process.

When to use it

  • When the system router has specifically selected quick-mode for a WooCommerce migration.
  • When the source probe has identified a WooCommerce environment with high confidence.
  • When you need a reliable, deterministic import process without using browser scraping or plugin-based extraction.

Advantages

  • Ensures consistent results through a deterministic entity graph.
  • Avoids complex dependencies like browser extraction or LLM mapping.
  • Provides a clear, step-by-step workflow for migration tasks.
  • Includes built-in preflight checks to identify potential blockers early.

Usage examples

“Run the preflight check for the WooCommerce import in the current project directory.”
The skill executes the preflight script and generates the initial quick-mode/preflight.json file.
“Perform the quick import for the project in /home/user/migration.”
The skill runs the extraction and import sequence, creating the necessary entities based on the established plan.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/wix/rp-quick-woocommerce-t7t2/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip rp-quick-woocommerce.zip -d .agents/skills

Documentation by Wix

rp-quick-woocommerce

Use only when the router selected this resource for managementImportMode=quick and the source probe identified woocommerce with high confidence. This adapter imports only the entity graph declared in quick-mode.json; it never uses WordPress/WooCommerce credentials, browser extraction, page scraping, plugin endpoints, or an LLM mapping step.

Run the adapter from the repository root:

node skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js preflight <projectDir>
node skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js plan <projectDir>
node skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js setup-author <projectDir>
node skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js extract <projectDir>
node skills/wix-replatform/resources/rp-quick-woocommerce/scripts/quick-mode.js import <projectDir> [--dry-run]

The first two commands create the authoritative quick-mode/preflight.json, quick-mode/plan.json, and execution/execution-manifest.json. Setup discovery consumes the quick plan; normal setup execution and execution approval are still mandatory before the extract/import run. Do not replace the deterministic entity list with data discovered from pages. If an endpoint preflight fails, persist the reported blocker and offer standard mode.

The setup-author command is a setup step: it idempotently creates the dedicated fallback Blog member and records only its ID as WIX_BLOG_FALLBACK_MEMBER_ID. It uses the shared rp-target-wix writer runtime and sends no email. The import entrypoint requires normal setup verification and that setup-provisioned value before creating posts.

Files

scripts/quick-mode.js

#!/usr/bin/env node
'use strict';

// Public-only, read-only WooCommerce quick-mode capture. This deliberately contains no
// credentials, browser automation, or fallback endpoint guessing.
const fs = require('node:fs/promises');
const path = require('node:path');
const crypto = require('node:crypto');

const ADAPTER_DIR = path.resolve(__dirname, '..');
const CONTRACT = require(path.join(ADAPTER_DIR, 'quick-mode.json'));
const recovery = require(path.join(ADAPTER_DIR, '..', 'rp-quick-runtime', 'lib', 'limit-recovery.js'));

async function readJson(filePath) {
  return JSON.parse(await fs.readFile(filePath, 'utf8'));
}
async function writeJson(filePath, value) {
  await fs.mkdir(path.dirname(filePath), { recursive: true });
  await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
async function appendAudit(projectDir, entry) {
  const file = path.join(projectDir, 'logs', 'quick-mode-audit.ndjson');
  await fs.mkdir(path.dirname(file), { recursive: true });
  await fs.appendFile(file, `${JSON.stringify({ timestamp: new Date().toISOString(), ...entry })}\n`, 'utf8');
}
function origin(value) {
  const url = new URL(value);
  if (!['http:', 'https:'].includes(url.protocol)) throw new Error('sourceUrl must use http or https');
  return url.origin;
}
function fingerprint(value) {
  return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
}
async function projectContext(projectDir) {
  const decisions = await readJson(path.join(projectDir, 'orchestration', 'decisions.json'));
  const sourceUrl = decisions.sourceUrl && decisions.sourceUrl.value;
  const sourcePlatform = decisions.sourcePlatform && decisions.sourcePlatform.value;
  if (!sourceUrl) throw new Error('quick mode requires orchestration.decisions.sourceUrl');
  if (sourcePlatform !== 'woocommerce') throw new Error(`quick WooCommerce adapter requires sourcePlatform=woocommerce (got ${sourcePlatform || 'missing'})`);
  return { sourceUrl: origin(sourceUrl), decisions };
}
async function fetchCollection(baseUrl, route) {
  const records = [];
  const pages = [];
  for (let page = 1; ; page += 1) {
    const url = new URL(route, `${baseUrl}/`);
    url.searchParams.set('page', String(page));
    url.searchParams.set('per_page', '100');
    const response = await fetch(url, { headers: { accept: 'application/json' } });
    const text = await response.text();
    let body;
    try { body = text ? JSON.parse(text) : null; } catch { body = null; }
    if (!response.ok || !Array.isArray(body)) {
      return { ok: false, status: response.status, url: url.toString(), bodyType: Array.isArray(body) ? 'array' : typeof body, records, pages };
    }
    records.push(...body);
    pages.push({ page, count: body.length, url: url.toString() });
    const totalPages = Number(response.headers.get('x-wp-totalpages'));
    if (Number.isFinite(totalPages) ? page >= totalPages : body.length < 100) break;
  }
  return { ok: true, records, pages };
}
async function preflight(projectDir) {
  const { sourceUrl } = await projectContext(projectDir);
  const checks = [];
  for (const entity of CONTRACT.entities) {
    const result = await fetchCollection(sourceUrl, entity.route);
    checks.push({ entity: entity.id, route: entity.route, ok: result.ok, status: result.status || 200, url: result.url || result.pages[0]?.url || null, responseShape: result.ok ? 'array' : result.bodyType });
  }
  const out = { schemaVersion: 1, generatedAt: new Date().toISOString(), adapter: { id: CONTRACT.id, version: CONTRACT.version, fingerprint: fingerprint(CONTRACT) }, sourceUrl, status: checks.every((check) => check.ok) ? 'passed' : 'blocked', checks };
  await writeJson(path.join(projectDir, 'quick-mode', 'preflight.json'), out);
  return out;
}
async function plan(projectDir) {
  const { sourceUrl } = await projectContext(projectDir);
  const preflightResult = await readJson(path.join(projectDir, 'quick-mode', 'preflight.json'));
  if (preflightResult.status !== 'passed') throw new Error('quick mode preflight must pass before planning');
  const adapter = { id: CONTRACT.id, version: CONTRACT.version, fingerprint: fingerprint(CONTRACT) };
  const planResult = { schemaVersion: 1, generatedAt: new Date().toISOString(), managementImportMode: 'quick', adapter, sourceUrl, entities: CONTRACT.entities, requiredWixCapabilities: CONTRACT.requiredWixCapabilities, excluded: CONTRACT.excluded, sourceAuth: 'none' };
  await writeJson(path.join(projectDir, 'quick-mode', 'plan.json'), planResult);
  await writeJson(path.join(projectDir, 'execution', 'execution-manifest.json'), { schemaVersion: 1, generatedAt: planResult.generatedAt, managementImportMode: 'quick', adapter, extractCommand: 'rp-quick-woocommerce extract', importCommand: 'rp-quick-woocommerce import', entities: CONTRACT.entities.map((entity) => entity.id) });
  return planResult;
}
async function extract(projectDir) {
  const { sourceUrl } = await projectContext(projectDir);
  const planResult = await readJson(path.join(projectDir, 'quick-mode', 'plan.json'));
  if (planResult.adapter.fingerprint !== fingerprint(CONTRACT)) throw new Error('quick adapter contract changed; re-run preflight and plan');
  const entities = {};
  for (const entity of CONTRACT.entities) {
    const result = await fetchCollection(sourceUrl, entity.route);
    if (!result.ok) throw new Error(`${entity.id} extraction failed: ${result.status} ${result.url}`);
    const file = path.join(projectDir, 'data', 'source-extract', `${entity.id}.ndjson`);
    await fs.mkdir(path.dirname(file), { recursive: true });
    await fs.writeFile(file, result.records.map((record) => JSON.stringify(record)).join('\n') + (result.records.length ? '\n' : ''), 'utf8');
    entities[entity.id] = { file: path.relative(projectDir, file), route: entity.route, recordCount: result.records.length, pages: result.pages };
  }
  const manifest = { schemaVersion: 1, generatedAt: new Date().toISOString(), managementImportMode: 'quick', adapter: planResult.adapter, sourceUrl, entities };
  await writeJson(path.join(projectDir, 'data', 'source-extract', 'manifest.json'), manifest);
  return manifest;
}
function nonEmpty(value) { const text = value == null ? '' : String(value).trim(); return text || undefined; }
function text(value) { return String(value || '').replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').trim(); }
function slug(value) { return nonEmpty(value)?.toLowerCase().replace(/[_\s]+/g, '-').replace(/[^a-z0-9-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || undefined; }
function price(value, minor = 2) { const n = Number(value); return Number.isFinite(n) ? (n / (10 ** Number(minor || 2))).toFixed(Number(minor || 2)) : '0.00'; }
async function readLines(filePath) { const raw = await fs.readFile(filePath, 'utf8'); return raw.split(/\r?\n/).filter(Boolean).map(JSON.parse); }
async function loadWixEnv(projectDir) {
  const values = {};
  try {
    for (const line of (await fs.readFile(path.join(projectDir, 'config', 'wix.env'), 'utf8')).split(/\r?\n/)) {
      const m = line.trim().match(/^([A-Z0-9_]+)=(.*)$/); if (m) values[m[1]] = m[2].replace(/^['"]|['"]$/g, '').trim();
    }
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
  return { ...values, ...process.env };
}
async function loadCrosswalk(projectDir) {
  try { return await readJson(path.join(projectDir, 'state', 'crosswalk', 'quick-mode.json')); }
  catch (error) { if (error.code === 'ENOENT') return {}; throw error; }
}
async function ensureFallbackAuthor(projectDir) {
  const env = await loadWixEnv(projectDir);
  if (env.WIX_BLOG_FALLBACK_MEMBER_ID) return { status: 'already_configured', memberId: env.WIX_BLOG_FALLBACK_MEMBER_ID };
  if (!env.WIX_AUTH_TOKEN || !env.WIX_SITE_ID) throw new Error('WIX_AUTH_TOKEN and WIX_SITE_ID are required to provision the Blog fallback author');
  const writers = require(path.join(ADAPTER_DIR, '..', 'rp-target-wix', 'lib', 'wix-writers.js'));
  const dryRun = writers.createDryRunConfig(env, process.argv.slice(2)).dryRun;
  if (dryRun) return { status: 'planned_dry_run', memberId: null };
  const wix = writers.createWixClient({ dryRun: false, authToken: env.WIX_AUTH_TOKEN, siteId: env.WIX_SITE_ID, projectDir });
  const projectId = path.basename(projectDir).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'migration';
  const email = `replatform+${projectId}@wix.com`;
  const listed = await writers.listMembers(wix, { limit: 100 });
  let member = (listed.members || []).find((item) => String(item.loginEmail || '').toLowerCase() === email);
  if (!member) member = await writers.createMember(wix, { email, name: 'Imported content', slug: `imported-content-${projectId}` });
  if (!member || !member.id) throw new Error('fallback Blog author was not created');
  const configPath = path.join(projectDir, 'config', 'wix.env');
  let raw = ''; try { raw = await fs.readFile(configPath, 'utf8'); } catch (error) { if (error.code !== 'ENOENT') throw error; }
  await fs.mkdir(path.dirname(configPath), { recursive: true });
  await fs.writeFile(configPath, `${raw.replace(/\n?WIX_BLOG_FALLBACK_MEMBER_ID=.*(?:\n|$)/g, '\n').replace(/\s*$/, '\n')}WIX_BLOG_FALLBACK_MEMBER_ID=${member.id}\n`, 'utf8');
  return { status: 'provisioned', memberId: member.id };
}
async function importData(projectDir) {
  const planResult = await readJson(path.join(projectDir, 'quick-mode', 'plan.json'));
  const manifest = await readJson(path.join(projectDir, 'data', 'source-extract', 'manifest.json'));
  if (manifest.adapter.fingerprint !== planResult.adapter.fingerprint) throw new Error('extraction does not match quick plan; re-extract');
  const env = await loadWixEnv(projectDir);
  if (!env.WIX_AUTH_TOKEN || !env.WIX_SITE_ID) throw new Error('WIX_AUTH_TOKEN and WIX_SITE_ID are required for quick import');
  const writers = require(path.join(ADAPTER_DIR, '..', 'rp-target-wix', 'lib', 'wix-writers.js'));
  const dryRun = writers.createDryRunConfig(env, process.argv.slice(2)).dryRun;
  const wix = writers.createWixClient({ dryRun, authToken: env.WIX_AUTH_TOKEN, siteId: env.WIX_SITE_ID, projectDir });
  const sourceDir = path.join(projectDir, 'data', 'source-extract');
  const crosswalk = await loadCrosswalk(projectDir);
  const seen = (entity, sourceId) => crosswalk[entity] && crosswalk[entity][String(sourceId)];
  const remember = (entity, sourceId, targetId) => { if (!dryRun) { crosswalk[entity] ||= {}; crosswalk[entity][String(sourceId)] = targetId; } };
  const checkpointCrosswalk = async () => { if (!dryRun) await writeJson(path.join(projectDir, 'state', 'crosswalk', 'quick-mode.json'), crosswalk); };
  const summary = { schemaVersion: 1, generatedAt: new Date().toISOString(), managementImportMode: 'quick', adapter: planResult.adapter, dryRun, imported: {}, recovered: 0, recoveryCounts: {}, excluded: CONTRACT.excluded };
  const categoryIds = new Map(); const productTagIds = new Map(); const blogCategoryIds = new Map(); const blogTagIds = new Map();
  const existingCategories = new Map((await writers.queryAllStoresCategories(wix)).map((item) => [slug(item.slug), item]));
  for (const row of await readLines(path.join(sourceDir, 'product_category.ndjson'))) {
    if (seen('product_category', row.id)) { categoryIds.set(String(row.id), seen('product_category', row.id)); continue; }
    const sourceSlug = slug(row.slug); const existing = existingCategories.get(sourceSlug);
    const target = existing || await writers.createStoresCategory(wix, { name: text(row.name), slug: sourceSlug, description: nonEmpty(text(row.description).slice(0, 600)), treeReference: writers.STORES_TREE_REFERENCE }); categoryIds.set(String(row.id), target.id);
    remember('product_category', row.id, target.id); await checkpointCrosswalk();
  }
  summary.imported.product_category = categoryIds.size;
  const existingProductTags = new Map(((await writers.sendDirectRest(wix, { method: 'GET', path: '/tags/v1/tags?fqdn=wix.stores.catalog.v3.product' })).tags || []).map((item) => [slug(item.name), item]));
  for (const row of await readLines(path.join(sourceDir, 'product_tag.ndjson'))) {
    if (seen('product_tag', row.id)) { productTagIds.set(String(row.id), seen('product_tag', row.id)); continue; }
    const key = slug(row.name); const existing = existingProductTags.get(key);
    const response = existing || (await writers.sendDirectRest(wix, { method: 'POST', path: '/tags/v1/tags', body: { tag: { name: text(row.name), fqdn: 'wix.stores.catalog.v3.product' } } })).tag;
    productTagIds.set(String(row.id), response.id); remember('product_tag', row.id, response.id); await checkpointCrosswalk();
  }
  summary.imported.product_tag = productTagIds.size;
  const existingBlogCategories = new Map(((await writers.sendDirectRest(wix, { method: 'POST', path: '/blog/v3/categories/query', body: {} })).categories || []).map((item) => [slug(item.slug), item]));
  for (const row of await readLines(path.join(sourceDir, 'blog_category.ndjson'))) { if (seen('blog_category', row.id)) { blogCategoryIds.set(String(row.id), seen('blog_category', row.id)); continue; } const existing = existingBlogCategories.get(slug(row.slug)); const created = existing || await writers.createBlogCategory(wix, { label: text(row.name), slug: slug(row.slug), description: nonEmpty(text(row.description)) }); blogCategoryIds.set(String(row.id), created.id); remember('blog_category', row.id, created.id); await checkpointCrosswalk(); }
  summary.imported.blog_category = blogCategoryIds.size;
  for (const row of await readLines(path.join(sourceDir, 'blog_tag.ndjson'))) { if (seen('blog_tag', row.id)) { blogTagIds.set(String(row.id), seen('blog_tag', row.id)); continue; } const created = await writers.createBlogTag(wix, { label: text(row.name) }); blogTagIds.set(String(row.id), created.id); remember('blog_tag', row.id, created.id); }
  summary.imported.blog_tag = blogTagIds.size;
  const fallbackMemberId = env.WIX_BLOG_FALLBACK_MEMBER_ID;
  if (!fallbackMemberId) throw new Error('setup must provision WIX_BLOG_FALLBACK_MEMBER_ID before importing blog posts');
  let posts = 0;
  for (const row of await readLines(path.join(sourceDir, 'post.ndjson'))) {
    if (seen('post', row.id)) { posts += 1; continue; }
    await appendAudit(projectDir, { phase: 'post', sourceId: String(row.id), state: 'creating_draft' });
    const draft = await writers.createDraftPost(wix, { title: text(row.title?.rendered), memberId: fallbackMemberId, richContent: await writers.convertHtmlToRichContent(wix, row.content?.rendered || '<p></p>'), excerpt: text(row.excerpt?.rendered), slug: slug(row.slug), categoryIds: (row.categories || []).map((id) => blogCategoryIds.get(String(id))).filter(Boolean), tagIds: (row.tags || []).map((id) => blogTagIds.get(String(id))).filter(Boolean), firstPublishedDate: Number.isNaN(new Date(row.date).valueOf()) ? undefined : new Date(row.date).toISOString() });
    await appendAudit(projectDir, { phase: 'post', sourceId: String(row.id), state: 'publishing_draft', targetId: draft.id });
    if (!dryRun) await writers.publishDraftPost(wix, draft.id); remember('post', row.id, draft.id); posts += 1;
    await checkpointCrosswalk(); await appendAudit(projectDir, { phase: 'post', sourceId: String(row.id), state: 'completed', targetId: draft.id });
  }
  summary.imported.post = posts;
  let products = 0;
  // A process can stop after a product create succeeds but before the crosswalk is
  // checkpointed. Build a complete destination slug index up front so a resumed run
  // recovers that write instead of treating the source record as a new product.
  const existingProducts = new Map(
    (await writers.queryAllStoresProducts(wix)).map((item) => [slug(item.slug), item]),
  );
  const productRows = await readLines(path.join(sourceDir, 'product.ndjson'));
  const productSlugCounts = new Map();
  for (const row of productRows) {
    const key = slug(row.slug);
    productSlugCounts.set(key, (productSlugCounts.get(key) || 0) + 1);
  }
  for (const row of productRows) {
    if (seen('product', row.id)) { products += 1; continue; }
    const baseSlug = slug(row.slug);
    const sourceSlug = productSlugCounts.get(baseSlug) > 1 ? `${baseSlug}-${row.id}` : baseSlug;
    const actualPrice = price(row.prices?.price, row.prices?.currency_minor_unit); const regular = price(row.prices?.regular_price, row.prices?.currency_minor_unit);
    const normalized = recovery.normalizeProduct({ sourceId: row.id, sku: nonEmpty(row.sku) || '', tagIds: (row.tags || []).map((tag) => productTagIds.get(String(tag.id))).filter(Boolean) });
    const created = existingProducts.get(sourceSlug) || (productSlugCounts.get(baseSlug) > 1 && existingProducts.get(baseSlug)) || await writers.createStoresProduct(wix, { name: text(row.name), slug: sourceSlug, description: row.description || row.short_description || undefined, productType: 'PHYSICAL', visible: true, tags: { publicTags: { tagIds: normalized.tagIds } }, media: { itemsInfo: { items: (row.images || []).map((image) => image.src).filter(Boolean).map((url) => ({ url })) } }, variantsInfo: { variants: [{ visible: true, sku: normalized.sku || undefined, inStock: row.is_in_stock !== false, price: { actualPrice: { amount: actualPrice }, ...(Number(regular) > Number(actualPrice) ? { compareAtPrice: { amount: regular } } : {}) } }] } });
    if (normalized.recoveries.length) { await recovery.appendRecoveries(projectDir, planResult.adapter, row.id, normalized.recoveries); recovery.recordRecoveredRecord(summary, normalized.recoveries); }
    const targets = (row.categories || []).map((category) => categoryIds.get(String(category.id))).filter(Boolean); if (targets.length) await writers.bulkAddItemToCategories(wix, { productId: created.id, categoryIds: targets }); remember('product', row.id, created.id); products += 1;
  }
  summary.imported.product = products;
  if (!dryRun) await writeJson(path.join(projectDir, 'state', 'crosswalk', 'quick-mode.json'), crosswalk);
  await recovery.writeFinalReport(projectDir, summary);
  await writeJson(path.join(projectDir, 'execution', 'completion-report.json'), summary);
  return summary;
}
async function main() {
  const [command, projectDirArg] = process.argv.slice(2);
  if (!command || !projectDirArg || !['preflight', 'plan', 'setup-author', 'extract', 'import'].includes(command)) throw new Error('Usage: quick-mode.js <preflight|plan|setup-author|extract|import> <projectDir>');
  const result = await ({ preflight, plan, 'setup-author': ensureFallbackAuthor, extract, import: importData })[command](path.resolve(projectDirArg));
  process.stdout.write(`${JSON.stringify({ ok: true, command, status: result.status || 'completed' })}\n`);
}
main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exit(1); });

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Wix
Publisher
Wix (Official)
Source
wix/skills · wix-replatform/resources/rp-quick-woocommerce · commit d168bc2
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Wix.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download rp-quick-woocommerce.zip