sfcc-metadata-cli 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/check.yml +80 -0
- package/AGENTS.md +82 -0
- package/LICENSE +661 -0
- package/README.md +249 -0
- package/biome.json +32 -0
- package/commands/create-migration.js +157 -0
- package/commands/custom-object.js +426 -0
- package/commands/site-preference.js +503 -0
- package/commands/system-object.js +572 -0
- package/index.js +34 -0
- package/lib/merge.js +271 -0
- package/lib/templates.js +315 -0
- package/lib/utils.js +188 -0
- package/package.json +24 -15
- package/test/merge.test.js +84 -0
- package/test/templates.test.js +133 -0
- package/test/utils.test.js +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# sfcc-metadata-cli
|
|
2
|
+
|
|
3
|
+
A companion CLI tool to [b2c-tools](https://github.com/SalesforceCommerceCloud/b2c-tools) for creating SFCC B2C migrations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Create migrations** with timestamp-based naming for unique, sortable names
|
|
8
|
+
- **Generate custom object definitions** with interactive prompts
|
|
9
|
+
- **Add site preferences** with automatic group detection
|
|
10
|
+
- **Extend system objects** with custom attributes
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install sfcc-metadata-cli --save-dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Using npx
|
|
22
|
+
npx sfcc-metadata <command>
|
|
23
|
+
|
|
24
|
+
# Or add scripts to your package.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### package.json Scripts (Recommended)
|
|
28
|
+
|
|
29
|
+
Add these scripts to your project's `package.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"scripts": {
|
|
34
|
+
"migration:create": "sfcc-metadata create",
|
|
35
|
+
"migration:custom-object": "sfcc-metadata custom-object",
|
|
36
|
+
"migration:site-preference": "sfcc-metadata site-preference",
|
|
37
|
+
"migration:system-object": "sfcc-metadata system-object"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run migration:create
|
|
46
|
+
npm run migration:custom-object
|
|
47
|
+
npm run migration:site-preference
|
|
48
|
+
npm run migration:system-object
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Commands
|
|
52
|
+
|
|
53
|
+
#### Create Migration
|
|
54
|
+
|
|
55
|
+
Creates a new migration folder with timestamp-based naming: `YYYYMMDD_HHMMSS_description`
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Interactive mode
|
|
59
|
+
npx sfcc-metadata create
|
|
60
|
+
|
|
61
|
+
# With description
|
|
62
|
+
npx sfcc-metadata create "add order status field"
|
|
63
|
+
# Creates: 20251231_143052_add_order_status_field
|
|
64
|
+
|
|
65
|
+
# Short format (date + sequence)
|
|
66
|
+
npx sfcc-metadata create --short "fix shipping"
|
|
67
|
+
# Creates: 20251231_01_fix_shipping
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Custom Object Definition
|
|
71
|
+
|
|
72
|
+
Creates a custom object type definition in the `meta/custom-objecttype-definitions.xml` file.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Interactive mode
|
|
76
|
+
npx sfcc-metadata custom-object
|
|
77
|
+
|
|
78
|
+
# Example: Create a custom object for caching
|
|
79
|
+
npx sfcc-metadata custom-object \
|
|
80
|
+
--type-id CacheConfig \
|
|
81
|
+
--display-name "Cache Configuration" \
|
|
82
|
+
--key-id cacheKey \
|
|
83
|
+
--storage-scope site
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Site Preference
|
|
87
|
+
|
|
88
|
+
Creates a site preference attribute in the `meta/system-objecttype-extensions.xml` file.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Interactive mode
|
|
92
|
+
npx sfcc-metadata site-preference
|
|
93
|
+
|
|
94
|
+
# Example: Add a boolean preference
|
|
95
|
+
npx sfcc-metadata site-preference \
|
|
96
|
+
--attribute-id enableFeatureX \
|
|
97
|
+
--type boolean \
|
|
98
|
+
--default-value false \
|
|
99
|
+
--group "Custom Configs"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### System Object Extension
|
|
103
|
+
|
|
104
|
+
Extends system objects (Order, Product, Customer, etc.) with custom attributes.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Interactive mode
|
|
108
|
+
npx sfcc-metadata system-object
|
|
109
|
+
|
|
110
|
+
# Example: Add a custom attribute to Order
|
|
111
|
+
npx sfcc-metadata system-object \
|
|
112
|
+
--object-type Order \
|
|
113
|
+
--attribute-id customField \
|
|
114
|
+
--type string \
|
|
115
|
+
--group Order_Custom
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Common Options
|
|
119
|
+
|
|
120
|
+
| Option | Alias | Description |
|
|
121
|
+
| ------------------ | ----- | ------------------------------------------------------ |
|
|
122
|
+
| `--migrations-dir` | `-m` | Path to migrations directory (default: `./migrations`) |
|
|
123
|
+
| `--interactive` | `-i` | Enable/disable interactive mode |
|
|
124
|
+
| `--help` | `-h` | Show help |
|
|
125
|
+
|
|
126
|
+
## Migration Naming Convention
|
|
127
|
+
|
|
128
|
+
### New Format (Recommended)
|
|
129
|
+
|
|
130
|
+
Migrations use **timestamp-based naming** for guaranteed uniqueness and natural sorting:
|
|
131
|
+
|
|
132
|
+
| Format | Example | Description |
|
|
133
|
+
| ---------------------- | ---------------------------------- | --------------------------- |
|
|
134
|
+
| `YYYYMMDD_HHMMSS` | `20251231_143052` | Full timestamp |
|
|
135
|
+
| `YYYYMMDD_HHMMSS_desc` | `20251231_143052_add_order_fields` | With description |
|
|
136
|
+
| `YYYYMMDD_NN_desc` | `20251231_01_fix_shipping` | Short format (--short flag) |
|
|
137
|
+
|
|
138
|
+
**Benefits:**
|
|
139
|
+
- ✅ No conflicts - timestamp ensures uniqueness
|
|
140
|
+
- ✅ Self-documenting - description in the name
|
|
141
|
+
- ✅ Natural sorting - alphabetical order = chronological order
|
|
142
|
+
- ✅ Unlimited migrations per day
|
|
143
|
+
|
|
144
|
+
### Legacy Format
|
|
145
|
+
|
|
146
|
+
Old migrations using `YY.MM.N` format are still supported and will sort before new migrations.
|
|
147
|
+
|
|
148
|
+
## Supported Attribute Types
|
|
149
|
+
|
|
150
|
+
| Type | Description |
|
|
151
|
+
| ---------------- | ---------------------------- |
|
|
152
|
+
| `string` | Single-line text |
|
|
153
|
+
| `text` | Multi-line text |
|
|
154
|
+
| `html` | HTML content |
|
|
155
|
+
| `int` | Integer number |
|
|
156
|
+
| `double` | Decimal number |
|
|
157
|
+
| `boolean` | True/False |
|
|
158
|
+
| `date` | Date only |
|
|
159
|
+
| `datetime` | Date and time |
|
|
160
|
+
| `enum-of-string` | Single/multi-select dropdown |
|
|
161
|
+
| `enum-of-int` | Integer enumeration |
|
|
162
|
+
| `set-of-string` | Set of strings |
|
|
163
|
+
| `image` | Image reference |
|
|
164
|
+
| `password` | Encrypted password |
|
|
165
|
+
|
|
166
|
+
## Examples
|
|
167
|
+
|
|
168
|
+
### Create a Complete Migration
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 1. Create the migration folder
|
|
172
|
+
node tools/migration-helper/index.js create
|
|
173
|
+
|
|
174
|
+
# 2. Add a custom object
|
|
175
|
+
node tools/migration-helper/index.js custom-object
|
|
176
|
+
|
|
177
|
+
# 3. Add a site preference
|
|
178
|
+
node tools/migration-helper/index.js site-preference
|
|
179
|
+
|
|
180
|
+
# 4. Preview the migration
|
|
181
|
+
npm run migrate
|
|
182
|
+
|
|
183
|
+
# 5. Apply the migration
|
|
184
|
+
npm run migrate:apply
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Generated XML Examples
|
|
188
|
+
|
|
189
|
+
#### Custom Object
|
|
190
|
+
```xml
|
|
191
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
192
|
+
<metadata xmlns="http://www.demandware.com/xml/impex/metadata/2006-10-31">
|
|
193
|
+
<custom-type type-id="MyCustomObject">
|
|
194
|
+
<display-name xml:lang="x-default">My Custom Object</display-name>
|
|
195
|
+
<staging-mode>source-to-target</staging-mode>
|
|
196
|
+
<storage-scope>site</storage-scope>
|
|
197
|
+
<key-definition attribute-id="key">
|
|
198
|
+
<type>string</type>
|
|
199
|
+
<min-length>0</min-length>
|
|
200
|
+
</key-definition>
|
|
201
|
+
<attribute-definitions>
|
|
202
|
+
<!-- attributes -->
|
|
203
|
+
</attribute-definitions>
|
|
204
|
+
<group-definitions>
|
|
205
|
+
<!-- groups -->
|
|
206
|
+
</group-definitions>
|
|
207
|
+
</custom-type>
|
|
208
|
+
</metadata>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Site Preference
|
|
212
|
+
```xml
|
|
213
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
214
|
+
<metadata xmlns="http://www.demandware.com/xml/impex/metadata/2006-10-31">
|
|
215
|
+
<type-extension type-id="SitePreferences">
|
|
216
|
+
<custom-attribute-definitions>
|
|
217
|
+
<attribute-definition attribute-id="myPreference">
|
|
218
|
+
<display-name xml:lang="x-default">My Preference</display-name>
|
|
219
|
+
<type>boolean</type>
|
|
220
|
+
<mandatory-flag>false</mandatory-flag>
|
|
221
|
+
<externally-managed-flag>false</externally-managed-flag>
|
|
222
|
+
<default-value>false</default-value>
|
|
223
|
+
</attribute-definition>
|
|
224
|
+
</custom-attribute-definitions>
|
|
225
|
+
<group-definitions>
|
|
226
|
+
<attribute-group group-id="Custom Configs">
|
|
227
|
+
<!-- existing attributes -->
|
|
228
|
+
<attribute attribute-id="myPreference"/>
|
|
229
|
+
</attribute-group>
|
|
230
|
+
</group-definitions>
|
|
231
|
+
</type-extension>
|
|
232
|
+
</metadata>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Integration with b2c-tools
|
|
236
|
+
|
|
237
|
+
After creating migrations with this tool, use b2c-tools to apply them:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Preview migrations
|
|
241
|
+
node build/b2c-tools.js import migrate --dry-run
|
|
242
|
+
|
|
243
|
+
# Apply migrations
|
|
244
|
+
node build/b2c-tools.js import migrate
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
AGPL-3.0-or-later
|
package/biome.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"organizeImports": {
|
|
4
|
+
"enabled": true
|
|
5
|
+
},
|
|
6
|
+
"linter": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"rules": {
|
|
9
|
+
"recommended": true,
|
|
10
|
+
"complexity": {
|
|
11
|
+
"noForEach": "off"
|
|
12
|
+
},
|
|
13
|
+
"style": {
|
|
14
|
+
"noNonNullAssertion": "off"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"formatter": {
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"indentStyle": "space",
|
|
21
|
+
"indentWidth": 4
|
|
22
|
+
},
|
|
23
|
+
"javascript": {
|
|
24
|
+
"formatter": {
|
|
25
|
+
"quoteStyle": "single",
|
|
26
|
+
"semicolons": "always"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": {
|
|
30
|
+
"ignore": ["node_modules", "package-lock.json"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Migration Command
|
|
3
|
+
* Creates a new migration folder with timestamp-based naming convention
|
|
4
|
+
* Format: YYYYMMDD_HHMMSS or YYYYMMDD_HHMMSS_description
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const {
|
|
11
|
+
generateMigrationName,
|
|
12
|
+
generateShortMigrationName,
|
|
13
|
+
listMigrations,
|
|
14
|
+
ensureDir,
|
|
15
|
+
getLatestMigration,
|
|
16
|
+
} = require('../lib/utils');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
command: 'create [description]',
|
|
20
|
+
aliases: ['new', 'init'],
|
|
21
|
+
desc: 'Create a new migration folder (format: YYYYMMDD_HHMMSS_description)',
|
|
22
|
+
builder: (yargs) => {
|
|
23
|
+
return yargs
|
|
24
|
+
.positional('description', {
|
|
25
|
+
describe:
|
|
26
|
+
'Short description for the migration (e.g., "add_order_fields")',
|
|
27
|
+
type: 'string',
|
|
28
|
+
})
|
|
29
|
+
.option('short', {
|
|
30
|
+
alias: 's',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
default: false,
|
|
33
|
+
description:
|
|
34
|
+
'Use short format: YYYYMMDD_NN instead of timestamp',
|
|
35
|
+
})
|
|
36
|
+
.option('with-meta', {
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
default: false,
|
|
39
|
+
description: 'Create meta folder structure',
|
|
40
|
+
})
|
|
41
|
+
.option('interactive', {
|
|
42
|
+
alias: 'i',
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
default: false,
|
|
45
|
+
description: 'Interactive mode with prompts',
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
handler: async (argv) => {
|
|
49
|
+
const migrationsDir = path.resolve(argv.migrationsDir);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
let description = argv.description;
|
|
53
|
+
const withMeta = argv.withMeta;
|
|
54
|
+
const useShortFormat = argv.short;
|
|
55
|
+
|
|
56
|
+
// List existing migrations
|
|
57
|
+
const existingMigrations = listMigrations(migrationsDir);
|
|
58
|
+
const latestMigration = getLatestMigration(migrationsDir);
|
|
59
|
+
|
|
60
|
+
if (existingMigrations.length > 0) {
|
|
61
|
+
console.log(chalk.cyan('\nRecent migrations:'));
|
|
62
|
+
existingMigrations.slice(-5).forEach((m) => {
|
|
63
|
+
console.log(chalk.gray(` - ${m}`));
|
|
64
|
+
});
|
|
65
|
+
if (existingMigrations.length > 5) {
|
|
66
|
+
console.log(
|
|
67
|
+
chalk.gray(
|
|
68
|
+
` ... and ${existingMigrations.length - 5} more`,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
console.log();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (argv.interactive || (!description && process.stdin.isTTY)) {
|
|
76
|
+
const answers = await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'input',
|
|
79
|
+
name: 'description',
|
|
80
|
+
message:
|
|
81
|
+
'Migration description (e.g., "add_order_status_field"):',
|
|
82
|
+
filter: (input) => input.trim(),
|
|
83
|
+
transformer: (input) => {
|
|
84
|
+
// Show preview of the sanitized name
|
|
85
|
+
const sanitized = input
|
|
86
|
+
.toLowerCase()
|
|
87
|
+
.replace(/\s+/g, '_')
|
|
88
|
+
.replace(/[^a-z0-9_]/g, '');
|
|
89
|
+
return (
|
|
90
|
+
input +
|
|
91
|
+
(sanitized !== input
|
|
92
|
+
? chalk.gray(` → ${sanitized}`)
|
|
93
|
+
: '')
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
description = answers.description || description;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Generate migration name
|
|
103
|
+
const migrationName = useShortFormat
|
|
104
|
+
? generateShortMigrationName(migrationsDir, description)
|
|
105
|
+
: generateMigrationName(migrationsDir, description);
|
|
106
|
+
|
|
107
|
+
// Check if migration already exists
|
|
108
|
+
const migrationPath = path.join(migrationsDir, migrationName);
|
|
109
|
+
if (existingMigrations.includes(migrationName)) {
|
|
110
|
+
console.error(
|
|
111
|
+
chalk.red(
|
|
112
|
+
`\nError: Migration "${migrationName}" already exists`,
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create migration folder
|
|
119
|
+
ensureDir(migrationPath);
|
|
120
|
+
console.log(chalk.green(`\n✓ Created migration: ${migrationName}`));
|
|
121
|
+
|
|
122
|
+
// Create meta folder if requested
|
|
123
|
+
if (withMeta) {
|
|
124
|
+
const metaPath = path.join(migrationPath, 'meta');
|
|
125
|
+
ensureDir(metaPath);
|
|
126
|
+
console.log(chalk.green('✓ Created meta folder'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(chalk.cyan(`\nMigration path: ${migrationPath}`));
|
|
130
|
+
console.log(chalk.gray('\nNext steps:'));
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.gray(' - Add XML files to the migration folder'),
|
|
133
|
+
);
|
|
134
|
+
console.log(
|
|
135
|
+
chalk.gray(
|
|
136
|
+
' - Use "migration-helper custom-object" to add custom object definitions',
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
console.log(
|
|
140
|
+
chalk.gray(
|
|
141
|
+
' - Use "migration-helper site-preference" to add site preferences',
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
console.log(
|
|
145
|
+
chalk.gray(' - Run "npm run migrate" to preview migrations'),
|
|
146
|
+
);
|
|
147
|
+
console.log(
|
|
148
|
+
chalk.gray(
|
|
149
|
+
' - Run "npm run migrate:apply" to apply migrations',
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|