Rp Quick Woocommerce
WixOfficialPublished by Wix
- Skill
- Workflow
- Automation and integrations
- MIT
- Low risk
- Has scripts
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
Download the skill as a ZIP:
https://autogineer.com/agent-skills/wix/rp-quick-woocommerce-t7t2/downloadUnzip 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
- ATTRIBUTION.mdAdded by AutoGineer845 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md1.8 KiB
- quick-mode.json1.2 KiB
- scripts/quick-mode.jsScript18.4 KiB
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(/&/g, '&').replace(/"/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 or Wix.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download rp-quick-woocommerce.zip