重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
b2c-custom-job-steps by salesforcecommercecloud/b2c-developer-tooling
npx skills add https://github.com/salesforcecommercecloud/b2c-developer-tooling --skill b2c-custom-job-steps此技能指导您为 Salesforce B2C Commerce 批处理创建新的自定义作业步骤。
正在运行现有作业? 如果您需要通过 CLI 执行作业或导入站点存档,请改用
b2c-cli:b2c-job技能。
自定义作业步骤允许您将自定义业务逻辑作为 B2C Commerce 作业的一部分来执行。有两种执行模型:
| 模型 | 使用场景 | 进度跟踪 |
|---|---|---|
| 面向任务 | 单一操作(FTP、导入/导出) | 有限 |
| 面向分块 | 批量数据处理 | 细粒度 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
my_cartridge/
├── cartridge/
│ ├── scripts/
│ │ └── steps/
│ │ ├── myTaskStep.js # 面向任务的脚本
│ │ └── myChunkStep.js # 面向分块的脚本
│ └── my_cartridge.properties
└── steptypes.json # 步骤类型定义(位于代码库根目录)
重要提示: steptypes.json 文件必须放在代码库的根文件夹中,而不是 cartridge/ 目录内。每个代码库只能有一个 steptypes.json 文件。
{
"step-types": {
"script-module-step": [
{
"@type-id": "custom.MyTaskStep",
"@supports-parallel-execution": "false",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "我的自定义任务步骤",
"module": "my_cartridge/cartridge/scripts/steps/myTaskStep.js",
"function": "execute",
"timeout-in-seconds": 900,
"parameters": {
"parameter": [
{
"@name": "InputFile",
"@type": "string",
"@required": "true",
"description": "输入文件路径"
},
{
"@name": "Enabled",
"@type": "boolean",
"@required": "false",
"default-value": "true",
"description": "启用处理"
}
]
},
"status-codes": {
"status": [
{
"@code": "OK",
"description": "步骤成功完成"
},
{
"@code": "ERROR",
"description": "步骤失败"
},
{
"@code": "NO_DATA",
"description": "没有要处理的数据"
}
]
}
}
],
"chunk-script-module-step": [
{
"@type-id": "custom.MyChunkStep",
"@supports-parallel-execution": "true",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "批量数据处理步骤",
"module": "my_cartridge/cartridge/scripts/steps/myChunkStep.js",
"before-step-function": "beforeStep",
"read-function": "read",
"process-function": "process",
"write-function": "write",
"after-step-function": "afterStep",
"total-count-function": "getTotalCount",
"chunk-size": 100,
"transactional": "false",
"timeout-in-seconds": 1800,
"parameters": {
"parameter": [
{
"@name": "CategoryId",
"@type": "string",
"@required": "true"
}
]
}
}
]
}
}
用于单一操作,如 FTP 传输、文件生成或导入/导出。
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
/**
* 执行任务步骤
* @param {Object} parameters - 作业步骤参数
* @param {dw.job.JobStepExecution} stepExecution - 步骤执行上下文
* @returns {dw.system.Status} 执行状态
*/
exports.execute = function (parameters, stepExecution) {
var log = Logger.getLogger('job', 'MyTaskStep');
try {
var inputFile = parameters.InputFile;
var enabled = parameters.Enabled;
if (!enabled) {
log.info('步骤已禁用,跳过');
return new Status(Status.OK, 'SKIP', '步骤已禁用');
}
// 在此处编写您的业务逻辑
log.info('正在处理文件:' + inputFile);
// 返回成功
return new Status(Status.OK);
} catch (e) {
log.error('步骤失败:' + e.message);
return new Status(Status.ERROR, 'ERROR', e.message);
}
};
// 成功
return new Status(Status.OK);
return new Status(Status.OK, 'CUSTOM_CODE', '自定义消息');
// 错误
return new Status(Status.ERROR);
return new Status(Status.ERROR, null, '错误消息');
重要提示: 自定义状态码仅适用于 OK 状态。如果您将自定义代码与 ERROR 状态一起使用,它将被替换为 ERROR。自定义状态码不能包含逗号、通配符、前导/尾随空格,也不能超过 100 个字符。
用于可计数数据的批量处理(产品、订单、客户)。
重要提示: 您不能为面向分块的步骤定义自定义退出状态。分块模块总是以 OK 或 ERROR 结束。
| 函数 | 目的 | 返回值 |
|---|---|---|
read() | 获取下一个项目 | 项目或无 |
process(item) | 转换项目 | 处理后的项目或无(用于过滤) |
write(items) | 保存项目块 | 无 |
| 函数 | 目的 | 返回值 |
|---|---|---|
beforeStep() | 初始化(打开文件、查询) | 无 |
afterStep(success) | 清理(关闭文件) | 无 |
getTotalCount() | 返回总项目数以跟踪进度 | 数字 |
beforeChunk() | 每个分块之前 | 无 |
afterChunk() | 每个分块之后 | 无 |
'use strict';
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var File = require('dw/io/File');
var FileWriter = require('dw/io/FileWriter');
var log = Logger.getLogger('job', 'MyChunkStep');
var products;
var fileWriter;
/**
* 在处理前初始化
*/
exports.beforeStep = function (parameters, stepExecution) {
log.info('开始分块处理');
// 打开资源
var outputFile = new File(File.IMPEX + '/export/products.csv');
fileWriter = new FileWriter(outputFile);
fileWriter.writeLine('ID,Name,Price');
// 查询产品
products = ProductMgr.queryAllSiteProducts();
};
/**
* 获取总计数以跟踪进度
*/
exports.getTotalCount = function (parameters, stepExecution) {
return products.count;
};
/**
* 读取下一个项目
* 返回无表示数据结束
*/
exports.read = function (parameters, stepExecution) {
if (products.hasNext()) {
return products.next();
}
// 返回无 = 数据结束
};
/**
* 处理单个项目
* 返回无表示过滤掉该项目
*/
exports.process = function (product, parameters, stepExecution) {
// 过滤:跳过离线产品
if (!product.online) {
return; // 已过滤
}
// 转换
return {
id: product.ID,
name: product.name,
price: product.priceModel.price.value
};
};
/**
* 写入已处理的项目块
*/
exports.write = function (items, parameters, stepExecution) {
for (var i = 0; i < items.size(); i++) {
var item = items.get(i);
fileWriter.writeLine(item.id + ',' + item.name + ',' + item.price);
}
};
/**
* 所有分块处理后的清理工作
*/
exports.afterStep = function (success, parameters, stepExecution) {
// 关闭资源
if (fileWriter) {
fileWriter.close();
}
if (products) {
products.close();
}
if (success) {
log.info('分块处理成功完成');
} else {
log.error('分块处理失败');
}
};
| 类型 | 描述 | 示例值 |
|---|---|---|
string | 文本值 | "my-value" |
boolean | true/false | true |
long | 整数 | 12345 |
double | 小数 | 123.45 |
datetime-string | ISO 日期时间 | "2024-01-15T10:30:00Z" |
date-string | ISO 日期 | "2024-01-15" |
time-string | ISO 时间 | "10:30:00" |
| 属性 | 适用于 | 描述 |
|---|---|---|
@trim | 全部 | 验证前修剪空格(默认:true) |
@required | 全部 | 标记为必需(默认:true) |
@target-type | datetime-string, date-string, time-string | 转换为 long 或 date(默认:date) |
pattern | string | 用于验证的正则表达式模式 |
min-length | string | 最小字符串长度(必须 ≥1) |
max-length | string | 最大字符串长度(总计最多 1000 个字符) |
min-value | long, double, datetime-string, time-string | 最小数值 |
max-value | long, double, datetime-string, time-string | 最大数值 |
enum-values | 全部 | 限制为允许的值(在 BM 中显示为下拉列表) |
| 属性 | 必需 | 描述 |
|---|---|---|
@type-id | 是 | 唯一 ID(必须以 custom. 开头,最多 100 个字符) |
@supports-parallel-execution | 否 | 允许并行执行(默认:true) |
@supports-site-context | 否 | 在站点范围的作业中可用(默认:true) |
@supports-organization-context | 否 | 在组织范围的作业中可用(默认:true) |
module | 是 | 脚本模块的路径 |
function | 是 | 要执行的函数名(面向任务) |
timeout-in-seconds | 否 | 步骤超时时间(建议设置) |
transactional | 否 | 包装在单个事务中(默认:false) |
chunk-size | 是* | 每个分块的项目数(*分块步骤必需) |
上下文约束: @supports-site-context 和 @supports-organization-context 不能同时为 true 或同时为 false - 一个必须为 true,另一个为 false。
afterStep() 中关闭资源 - 查询、文件、连接Transaction.wrap() 以获得控制权b2c-cli:b2c-job - 用于通过 CLI 运行现有作业和导入站点存档b2c:b2c-webservices - 当作业步骤需要调用外部 HTTP 服务或 API 时,使用 webservices 技能进行服务配置和 HTTP 客户端模式每周安装次数
61
代码仓库
GitHub 星标数
34
首次出现
2026年2月21日
安全审计
安装于
github-copilot56
codex53
cursor53
opencode52
amp51
kimi-cli51
This skill guides you through creating new custom job steps for Salesforce B2C Commerce batch processing.
Running an existing job? If you need to execute jobs or import site archives via CLI, use the
b2c-cli:b2c-jobskill instead.
Custom job steps allow you to execute custom business logic as part of B2C Commerce jobs. There are two execution models:
| Model | Use Case | Progress Tracking |
|---|---|---|
| Task-oriented | Single operations (FTP, import/export) | Limited |
| Chunk-oriented | Bulk data processing | Fine-grained |
my_cartridge/
├── cartridge/
│ ├── scripts/
│ │ └── steps/
│ │ ├── myTaskStep.js # Task-oriented script
│ │ └── myChunkStep.js # Chunk-oriented script
│ └── my_cartridge.properties
└── steptypes.json # Step type definitions (at cartridge ROOT)
Important: The steptypes.json file must be placed in the root folder of the cartridge, not inside the cartridge/ directory. Only one steptypes.json file per cartridge.
{
"step-types": {
"script-module-step": [
{
"@type-id": "custom.MyTaskStep",
"@supports-parallel-execution": "false",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "My custom task step",
"module": "my_cartridge/cartridge/scripts/steps/myTaskStep.js",
"function": "execute",
"timeout-in-seconds": 900,
"parameters": {
"parameter": [
{
"@name": "InputFile",
"@type": "string",
"@required": "true",
"description": "Path to input file"
},
{
"@name": "Enabled",
"@type": "boolean",
"@required": "false",
"default-value": "true",
"description": "Enable processing"
}
]
},
"status-codes": {
"status": [
{
"@code": "OK",
"description": "Step completed successfully"
},
{
"@code": "ERROR",
"description": "Step failed"
},
{
"@code": "NO_DATA",
"description": "No data to process"
}
]
}
}
],
"chunk-script-module-step": [
{
"@type-id": "custom.MyChunkStep",
"@supports-parallel-execution": "true",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "Bulk data processing step",
"module": "my_cartridge/cartridge/scripts/steps/myChunkStep.js",
"before-step-function": "beforeStep",
"read-function": "read",
"process-function": "process",
"write-function": "write",
"after-step-function": "afterStep",
"total-count-function": "getTotalCount",
"chunk-size": 100,
"transactional": "false",
"timeout-in-seconds": 1800,
"parameters": {
"parameter": [
{
"@name": "CategoryId",
"@type": "string",
"@required": "true"
}
]
}
}
]
}
}
Use for single operations like FTP transfers, file generation, or import/export.
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
/**
* Execute the task step
* @param {Object} parameters - Job step parameters
* @param {dw.job.JobStepExecution} stepExecution - Step execution context
* @returns {dw.system.Status} Execution status
*/
exports.execute = function (parameters, stepExecution) {
var log = Logger.getLogger('job', 'MyTaskStep');
try {
var inputFile = parameters.InputFile;
var enabled = parameters.Enabled;
if (!enabled) {
log.info('Step disabled, skipping');
return new Status(Status.OK, 'SKIP', 'Step disabled');
}
// Your business logic here
log.info('Processing file: ' + inputFile);
// Return success
return new Status(Status.OK);
} catch (e) {
log.error('Step failed: ' + e.message);
return new Status(Status.ERROR, 'ERROR', e.message);
}
};
// Success
return new Status(Status.OK);
return new Status(Status.OK, 'CUSTOM_CODE', 'Custom message');
// Error
return new Status(Status.ERROR);
return new Status(Status.ERROR, null, 'Error message');
Important: Custom status codes work only with OK status. If you use a custom code with ERROR status, it is replaced with ERROR. Custom status codes cannot contain commas, wildcards, leading/trailing whitespace, or exceed 100 characters.
Use for bulk processing of countable data (products, orders, customers).
Important: You cannot define custom exit status for chunk-oriented steps. Chunk modules always finish with either OK or ERROR.
| Function | Purpose | Returns |
|---|---|---|
read() | Get next item | Item or nothing |
process(item) | Transform item | Processed item or nothing (filters) |
write(items) | Save chunk of items | Nothing |
| Function | Purpose | Returns |
|---|---|---|
beforeStep() | Initialize (open files, queries) | Nothing |
afterStep(success) | Cleanup (close files) | Nothing |
getTotalCount() | Return total items for progress | Number |
beforeChunk() | Before each chunk | Nothing |
afterChunk() | After each chunk |
'use strict';
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var File = require('dw/io/File');
var FileWriter = require('dw/io/FileWriter');
var log = Logger.getLogger('job', 'MyChunkStep');
var products;
var fileWriter;
/**
* Initialize before processing
*/
exports.beforeStep = function (parameters, stepExecution) {
log.info('Starting chunk processing');
// Open resources
var outputFile = new File(File.IMPEX + '/export/products.csv');
fileWriter = new FileWriter(outputFile);
fileWriter.writeLine('ID,Name,Price');
// Query products
products = ProductMgr.queryAllSiteProducts();
};
/**
* Get total count for progress tracking
*/
exports.getTotalCount = function (parameters, stepExecution) {
return products.count;
};
/**
* Read next item
* Return nothing to signal end of data
*/
exports.read = function (parameters, stepExecution) {
if (products.hasNext()) {
return products.next();
}
// Return nothing = end of data
};
/**
* Process single item
* Return nothing to filter out item
*/
exports.process = function (product, parameters, stepExecution) {
// Filter: skip offline products
if (!product.online) {
return; // Filtered out
}
// Transform
return {
id: product.ID,
name: product.name,
price: product.priceModel.price.value
};
};
/**
* Write chunk of processed items
*/
exports.write = function (items, parameters, stepExecution) {
for (var i = 0; i < items.size(); i++) {
var item = items.get(i);
fileWriter.writeLine(item.id + ',' + item.name + ',' + item.price);
}
};
/**
* Cleanup after all chunks
*/
exports.afterStep = function (success, parameters, stepExecution) {
// Close resources
if (fileWriter) {
fileWriter.close();
}
if (products) {
products.close();
}
if (success) {
log.info('Chunk processing completed successfully');
} else {
log.error('Chunk processing failed');
}
};
| Type | Description | Example Value |
|---|---|---|
string | Text value | "my-value" |
boolean | true/false | true |
long | Integer | 12345 |
double |
| Attribute | Applies To | Description |
|---|---|---|
@trim | All | Trim whitespace before validation (default: true) |
@required | All | Mark as required (default: true) |
@target-type | datetime-string, date-string, time-string | Convert to long or date (default: ) |
| Attribute | Required | Description |
|---|---|---|
@type-id | Yes | Unique ID (must start with custom., max 100 chars) |
@supports-parallel-execution | No | Allow parallel execution (default: true) |
@supports-site-context | No | Available in site-scoped jobs (default: true) |
@supports-organization-context |
Context Constraints: @supports-site-context and @supports-organization-context cannot both be true or both be false - one must be true and the other false.
afterStep() - queries, files, connectionsTransaction.wrap() for controlb2c-cli:b2c-job - For running existing jobs and importing site archives via CLIb2c:b2c-webservices - When job steps need to call external HTTP services or APIs, use the webservices skill for service configuration and HTTP client patternsWeekly Installs
61
Repository
GitHub Stars
34
First Seen
Feb 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
github-copilot56
codex53
cursor53
opencode52
amp51
kimi-cli51
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
123,700 周安装
使用 Turborepo 和 pnpm workspaces 配置可扩展的 Monorepo 项目结构
60 周安装
review-diff:AI代码审查工具,专注Git差异分析,提升代码质量与开发效率
60 周安装
WidgetKit代码审查清单:iOS小组件开发最佳实践与性能优化指南
60 周安装
AI产品摄影生成器 - 一键创建专业电商主图、白底图、生活场景照
60 周安装
OneKey 代码质量检查与规范指南:ESLint、TypeScript 预提交检查与最佳实践
60 周安装
Wish SSH 服务器代码审查清单:Go SSH 安全配置、优雅关闭与中间件最佳实践
60 周安装
| Nothing |
| Decimal |
123.45 |
datetime-string | ISO datetime | "2024-01-15T10:30:00Z" |
date-string | ISO date | "2024-01-15" |
time-string | ISO time | "10:30:00" |
datepattern | string | Regex pattern for validation |
min-length | string | Minimum string length (must be ≥1) |
max-length | string | Maximum string length (max 1000 chars total) |
min-value | long, double, datetime-string, time-string | Minimum numeric value |
max-value | long, double, datetime-string, time-string | Maximum numeric value |
enum-values | All | Restrict to allowed values (dropdown in BM) |
| No |
Available in org-scoped jobs (default: true) |
module | Yes | Path to script module |
function | Yes | Function name to execute (task-oriented) |
timeout-in-seconds | No | Step timeout (recommended to set) |
transactional | No | Wrap in single transaction (default: false) |
chunk-size | Yes* | Items per chunk (*required for chunk steps) |