Skip to content

Commit e52ab8c

Browse files
committed
wip pipeline converter; non AST approach
1 parent 90cc338 commit e52ab8c

15 files changed

Lines changed: 21245 additions & 0 deletions

File tree

LegacyDeveloperDocumentation.pdf

6.14 MB
Binary file not shown.

legacydocs.md

Lines changed: 17576 additions & 0 deletions
Large diffs are not rendered by default.

packages/b2c-cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@oclif/plugin-version": "^2",
2222
"@salesforce/b2c-tooling-sdk": "workspace:*",
2323
"cliui": "^9.0.1",
24+
"glob": "^13.0.0",
2425
"marked": "^15.0.0",
2526
"marked-terminal": "^7.3.0",
2627
"open": "^11.0.0"
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
import {existsSync} from 'node:fs';
8+
import {writeFile} from 'node:fs/promises';
9+
import {basename, dirname, join, resolve} from 'node:path';
10+
import {Args, Flags, ux} from '@oclif/core';
11+
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
12+
import {convertPipeline, type ConvertResult} from '@salesforce/b2c-tooling-sdk/operations/pipeline';
13+
import {glob} from 'glob';
14+
import {t} from '../../i18n/index.js';
15+
16+
interface ConvertResponse {
17+
results: ConvertResult[];
18+
totalPipelines: number;
19+
successCount: number;
20+
warningCount: number;
21+
}
22+
23+
export default class PipelineConvert extends BaseCommand<typeof PipelineConvert> {
24+
static args = {
25+
input: Args.string({
26+
description: 'Pipeline XML file or glob pattern (e.g., "*.xml" or "pipelines/**/*.xml")',
27+
required: true,
28+
}),
29+
};
30+
31+
static description = t(
32+
'commands.pipeline.convert.description',
33+
'Convert B2C Commerce pipeline XML files to JavaScript controllers',
34+
);
35+
36+
static enableJsonFlag = true;
37+
38+
static examples = [
39+
'<%= config.bin %> <%= command.id %> Account.xml',
40+
'<%= config.bin %> <%= command.id %> Account.xml --output ./controllers',
41+
'<%= config.bin %> <%= command.id %> "pipelines/*.xml" --output ./controllers',
42+
'<%= config.bin %> <%= command.id %> Cart.xml --dry-run',
43+
];
44+
45+
static flags = {
46+
...BaseCommand.baseFlags,
47+
output: Flags.string({
48+
char: 'o',
49+
description: 'Output directory for generated controllers',
50+
}),
51+
'dry-run': Flags.boolean({
52+
description: 'Preview generated code without writing files',
53+
default: false,
54+
}),
55+
};
56+
57+
protected operations = {
58+
convertPipeline,
59+
};
60+
61+
async run(): Promise<ConvertResponse> {
62+
const {input} = this.args;
63+
const {output, 'dry-run': dryRun} = this.flags;
64+
65+
// Resolve input files
66+
const inputFiles = await this.resolveInputFiles(input);
67+
68+
if (inputFiles.length === 0) {
69+
this.error(
70+
t('commands.pipeline.convert.noFilesFound', 'No pipeline XML files found matching: {{pattern}}', {
71+
pattern: input,
72+
}),
73+
);
74+
}
75+
76+
// Process all files and collect results
77+
const conversionResults = await this.processFiles(inputFiles, output, dryRun);
78+
79+
const response: ConvertResponse = {
80+
results: conversionResults.results,
81+
totalPipelines: inputFiles.length,
82+
successCount: conversionResults.successCount,
83+
warningCount: conversionResults.warningCount,
84+
};
85+
86+
if (this.jsonEnabled()) {
87+
return response;
88+
}
89+
90+
if (!dryRun) {
91+
this.log('');
92+
this.log(
93+
t('commands.pipeline.convert.summary', 'Converted {{count}} pipeline(s) with {{warnings}} warning(s)', {
94+
count: conversionResults.successCount,
95+
warnings: conversionResults.warningCount,
96+
}),
97+
);
98+
}
99+
100+
return response;
101+
}
102+
103+
/**
104+
* Processes a single input file.
105+
*/
106+
private async processFile(
107+
inputFile: string,
108+
output: string | undefined,
109+
dryRun: boolean,
110+
): Promise<{result: ConvertResult; success: boolean}> {
111+
const pipelineName = basename(inputFile, '.xml');
112+
113+
if (!dryRun) {
114+
this.log(
115+
t('commands.pipeline.convert.converting', 'Converting {{name}}...', {
116+
name: pipelineName,
117+
}),
118+
);
119+
}
120+
121+
// Determine output path
122+
const outputPath = dryRun
123+
? undefined
124+
: output
125+
? join(output, `${pipelineName}.js`)
126+
: join(dirname(inputFile), `${pipelineName}.js`);
127+
128+
const result = await this.operations.convertPipeline(inputFile, {
129+
outputPath,
130+
dryRun,
131+
});
132+
133+
if (result.warnings.length > 0) {
134+
for (const warning of result.warnings) {
135+
this.warn(warning);
136+
}
137+
}
138+
139+
let success = false;
140+
if (dryRun) {
141+
// In dry-run mode, output the generated code
142+
if (!this.jsonEnabled()) {
143+
ux.stdout(`\n// === ${pipelineName}.js ===\n`);
144+
ux.stdout(result.code);
145+
ux.stdout('\n');
146+
}
147+
} else if (outputPath) {
148+
// Write the file
149+
await writeFile(outputPath, result.code, 'utf8');
150+
this.log(
151+
t('commands.pipeline.convert.generated', 'Generated: {{path}}', {
152+
path: outputPath,
153+
}),
154+
);
155+
success = true;
156+
}
157+
158+
return {result, success};
159+
}
160+
161+
/**
162+
* Processes all input files and returns results.
163+
*/
164+
private async processFiles(
165+
inputFiles: string[],
166+
output: string | undefined,
167+
dryRun: boolean,
168+
): Promise<{results: ConvertResult[]; successCount: number; warningCount: number}> {
169+
const results: ConvertResult[] = [];
170+
let successCount = 0;
171+
let warningCount = 0;
172+
173+
// Process files sequentially to maintain order and avoid concurrent file operations
174+
for (const inputFile of inputFiles) {
175+
// eslint-disable-next-line no-await-in-loop
176+
const fileResult = await this.processFile(inputFile, output, dryRun);
177+
results.push(fileResult.result);
178+
successCount += fileResult.success ? 1 : 0;
179+
warningCount += fileResult.result.warnings.length;
180+
}
181+
182+
return {results, successCount, warningCount};
183+
}
184+
185+
/**
186+
* Resolves input pattern to list of files.
187+
*/
188+
private async resolveInputFiles(pattern: string): Promise<string[]> {
189+
const resolvedPattern = resolve(pattern);
190+
191+
// Check if it's a direct file path
192+
if (existsSync(resolvedPattern) && resolvedPattern.endsWith('.xml')) {
193+
return [resolvedPattern];
194+
}
195+
196+
// Otherwise treat as glob pattern
197+
const files = await glob(pattern, {
198+
absolute: true,
199+
nodir: true,
200+
});
201+
202+
// Filter to only XML files
203+
return files.filter((f) => f.endsWith('.xml'));
204+
}
205+
}

packages/b2c-tooling-sdk/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@
134134
"default": "./dist/cjs/operations/scapi-schemas/index.js"
135135
}
136136
},
137+
"./operations/pipeline": {
138+
"development": "./src/operations/pipeline/index.ts",
139+
"import": {
140+
"types": "./dist/esm/operations/pipeline/index.d.ts",
141+
"default": "./dist/esm/operations/pipeline/index.js"
142+
},
143+
"require": {
144+
"types": "./dist/cjs/operations/pipeline/index.d.ts",
145+
"default": "./dist/cjs/operations/pipeline/index.js"
146+
}
147+
},
137148
"./cli": {
138149
"development": "./src/cli/index.ts",
139150
"import": {

0 commit comments

Comments
 (0)