Skip to content

Commit 37269fc

Browse files
authored
feat: Add MRT reference app and welcome app to monorepo (#445)
* Move MRT reference app and welcome app to mono repo
1 parent aed14cc commit 37269fc

69 files changed

Lines changed: 7407 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,21 @@ jobs:
8282
working-directory: packages/mrt-utilities
8383
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
8484

85+
- name: Run MRT Reference App tests
86+
id: mrt-reference-app-test
87+
if: always() && steps.mrt-test.conclusion != 'cancelled'
88+
working-directory: packages/mrt-reference-app
89+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
90+
91+
- name: Run MRT Welcome App tests
92+
id: mrt-welcome-app-test
93+
if: always() && steps.mrt-reference-app-test.conclusion != 'cancelled'
94+
working-directory: packages/mrt-welcome-app
95+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
96+
8597
- name: Run VS Extension checks
8698
id: vs-extension-test
87-
if: always() && steps.mrt-test.conclusion != 'cancelled'
99+
if: always() && steps.mrt-welcome-app-test.conclusion != 'cancelled'
88100
working-directory: packages/b2c-vs-extension
89101
run: pnpm run typecheck:agent && pnpm run lint
90102

@@ -182,8 +194,18 @@ jobs:
182194
if: always() && steps.cli-test.conclusion != 'cancelled'
183195
working-directory: packages/mrt-utilities
184196
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
185-
- name: Run VS Extension checks
197+
- name: Run MRT Reference App tests
198+
id: mrt-reference-app-test
186199
if: always() && steps.mrt-test.conclusion != 'cancelled'
200+
working-directory: packages/mrt-reference-app
201+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
202+
- name: Run MRT Welcome App tests
203+
id: mrt-welcome-app-test
204+
if: always() && steps.mrt-reference-app-test.conclusion != 'cancelled'
205+
working-directory: packages/mrt-welcome-app
206+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
207+
- name: Run VS Extension checks
208+
if: always() && steps.mrt-welcome-app-test.conclusion != 'cancelled'
187209
working-directory: packages/b2c-vs-extension
188210
run: pnpm run typecheck:agent && pnpm run lint
189211
- name: Print Windows test failures
@@ -200,6 +222,9 @@ jobs:
200222
'packages/b2c-tooling-sdk/test-results.json',
201223
'packages/b2c-dx-mcp/test-results.json',
202224
'packages/b2c-cli/test-results.json',
225+
'packages/mrt-utilities/test-results.json',
226+
'packages/mrt-reference-app/test-results.json',
227+
'packages/mrt-welcome-app/test-results.json',
203228
];
204229
let totalFailures = 0;
205230
for (const report of reports) {

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@
3030
"packageManager": "pnpm@10.17.1",
3131
"devDependencies": {
3232
"@changesets/changelog-github": "^0.5.2",
33-
"@changesets/cli": "^2.29.8",
33+
"@changesets/cli": "^2.31.0",
3434
"eslint-plugin-prettier": "catalog:",
3535
"prettier": "catalog:"
36+
},
37+
"pnpm": {
38+
"overrides": {
39+
"@smithy/types": "4.14.3"
40+
}
3641
}
3742
}

packages/b2c-cli/src/commands/mrt/bundle/deploy.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,9 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
104104
}),
105105
'ssr-only': Flags.string({
106106
description: 'Glob patterns for server-only files (comma-separated or JSON array, only for local builds)',
107-
default: 'ssr.js,ssr.mjs,server/**/*',
108107
}),
109108
'ssr-shared': Flags.string({
110109
description: 'Glob patterns for shared files (comma-separated or JSON array, only for local builds)',
111-
default: 'static/**/*,client/**/*',
112110
}),
113111
'node-version': Flags.string({
114112
char: 'n',
@@ -237,8 +235,8 @@ export default class MrtBundleDeploy extends MrtCommand<typeof MrtBundleDeploy>
237235
}
238236

239237
const buildDir = this.flags['build-dir'];
240-
const ssrOnly = parseGlobPatterns(this.flags['ssr-only']);
241-
const ssrShared = parseGlobPatterns(this.flags['ssr-shared']);
238+
const ssrOnly = this.flags['ssr-only'] ? parseGlobPatterns(this.flags['ssr-only']) : undefined;
239+
const ssrShared = this.flags['ssr-shared'] ? parseGlobPatterns(this.flags['ssr-shared']) : undefined;
242240

243241
// Build SSR parameters from flags
244242
const ssrParameters: Record<string, unknown> = parseSsrParams(this.flags['ssr-param']);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
import {writeFileSync} from 'node:fs';
7+
import {gzipSync} from 'node:zlib';
8+
import path from 'node:path';
9+
import {Flags} from '@oclif/core';
10+
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
11+
import {createBundle, getDefaultMessage, DEFAULT_SSR_PARAMETERS} from '@salesforce/b2c-tooling-sdk/operations/mrt';
12+
import {t, withDocs} from '../../../i18n/index.js';
13+
14+
export interface SaveBundleResult {
15+
filePath: string;
16+
projectSlug: string;
17+
message: string;
18+
ssrOnlyCount: number;
19+
ssrSharedCount: number;
20+
}
21+
22+
/**
23+
* Save a bundle to a local directory without uploading it to Managed Runtime.
24+
*/
25+
export default class MrtBundleSave extends BaseCommand<typeof MrtBundleSave> {
26+
static description = withDocs(
27+
t('commands.mrt.bundle.save.description', 'Save a Managed Runtime bundle to a local directory'),
28+
'/cli/mrt.html#b2c-mrt-bundle-save',
29+
);
30+
31+
static enableJsonFlag = true;
32+
33+
static examples = [
34+
'<%= config.bin %> <%= command.id %> --project my-storefront --save-dir ./artifacts',
35+
'<%= config.bin %> <%= command.id %> --project my-storefront --save-dir ./artifacts --gzip',
36+
'<%= config.bin %> <%= command.id %> --project my-storefront --save-dir ./artifacts --build-dir ./dist',
37+
'<%= config.bin %> <%= command.id %> --project my-storefront --save-dir ./artifacts --json',
38+
];
39+
40+
static flags = {
41+
...BaseCommand.baseFlags,
42+
project: Flags.string({
43+
char: 'p',
44+
description: 'MRT project slug (or set MRT_PROJECT env var)',
45+
env: 'MRT_PROJECT',
46+
default: async () => process.env.SFCC_MRT_PROJECT || undefined,
47+
}),
48+
'save-dir': Flags.string({
49+
char: 's',
50+
description: 'Directory to save the bundle to',
51+
required: true,
52+
}),
53+
'build-dir': Flags.string({
54+
char: 'b',
55+
description: 'Path to the build directory',
56+
default: 'build',
57+
}),
58+
'ssr-only': Flags.string({
59+
description: 'Glob patterns for server-only files (comma-separated or JSON array)',
60+
}),
61+
'ssr-shared': Flags.string({
62+
description: 'Glob patterns for shared files (comma-separated or JSON array)',
63+
}),
64+
'node-version': Flags.string({
65+
char: 'n',
66+
description: `Node.js version for SSR runtime (default: ${DEFAULT_SSR_PARAMETERS.SSRFunctionNodeVersion})`,
67+
}),
68+
gzip: Flags.boolean({
69+
char: 'g',
70+
description: 'Gzip the bundle (saves as bundle.tgz instead of bundle.tar)',
71+
default: false,
72+
}),
73+
};
74+
75+
async run(): Promise<SaveBundleResult> {
76+
const project = this.flags.project;
77+
78+
if (!project) {
79+
this.error('MRT project is required. Provide --project flag or set MRT_PROJECT.');
80+
}
81+
82+
const saveDir = this.flags['save-dir'];
83+
const buildDir = this.flags['build-dir'];
84+
const ssrOnly = this.flags['ssr-only'] ? parseGlobPatterns(this.flags['ssr-only']) : undefined;
85+
const ssrShared = this.flags['ssr-shared'] ? parseGlobPatterns(this.flags['ssr-shared']) : undefined;
86+
const ssrParameters: Record<string, unknown> = {};
87+
88+
if (this.flags['node-version']) {
89+
ssrParameters.SSRFunctionNodeVersion = this.flags['node-version'];
90+
}
91+
92+
const message = getDefaultMessage();
93+
94+
this.log(t('commands.mrt.bundle.save.creating', 'Creating bundle for {{project}}...', {project}));
95+
96+
const bundle = await createBundle({
97+
projectSlug: project,
98+
buildDirectory: buildDir,
99+
message,
100+
ssrOnly,
101+
ssrShared,
102+
ssrParameters,
103+
});
104+
105+
const gzip = this.flags.gzip;
106+
const fileName = gzip ? 'bundle.tgz' : 'bundle.tar';
107+
const filePath = path.resolve(saveDir, fileName);
108+
109+
let data = Buffer.from(bundle.data, 'base64');
110+
if (gzip) {
111+
data = gzipSync(data);
112+
}
113+
114+
writeFileSync(filePath, data);
115+
116+
if (!this.jsonEnabled()) {
117+
this.log(t('commands.mrt.bundle.save.success', 'Bundle saved to {{filePath}}', {filePath}));
118+
}
119+
120+
return {
121+
filePath,
122+
projectSlug: project,
123+
message: bundle.message,
124+
ssrOnlyCount: bundle.ssr_only.length,
125+
ssrSharedCount: bundle.ssr_shared.length,
126+
};
127+
}
128+
}
129+
130+
function parseGlobPatterns(value: string): string[] {
131+
const trimmed = value.trim();
132+
if (trimmed.startsWith('[')) {
133+
const parsed: unknown = JSON.parse(trimmed);
134+
if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === 'string')) {
135+
throw new Error(`Invalid glob pattern array: expected an array of strings`);
136+
}
137+
return parsed.map((s: string) => s.trim()).filter(Boolean);
138+
}
139+
return trimmed
140+
.split(',')
141+
.map((s) => s.trim())
142+
.filter(Boolean);
143+
}

packages/b2c-dx-mcp/src/tools/diagnostics/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ import {createLogsWatchStopTool} from './logs-watch-stop.js';
2626
import {createLogsWatchListTool} from './logs-watch-list.js';
2727

2828
export interface DiagnosticsToolInjections
29-
extends LogsGetRecentInjections,
30-
LogsListFilesInjections,
31-
LogsWatchStartInjections {}
29+
extends LogsGetRecentInjections, LogsListFilesInjections, LogsWatchStartInjections {}
3230

3331
export function createDiagnosticsTools(
3432
loadServices: () => Promise<Services> | Services,

packages/b2c-tooling-sdk/src/cli/mrt-command.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ export abstract class MrtCommand<T extends typeof Command> extends BaseCommand<T
5555
default: async () => process.env.SFCC_MRT_ENVIRONMENT || process.env.MRT_TARGET || undefined,
5656
}),
5757
'cloud-origin': Flags.string({
58+
char: 'o',
5859
description: `MRT cloud origin URL (or set mrtOrigin in dw.json; default: ${DEFAULT_MRT_ORIGIN})`,
5960
env: 'MRT_CLOUD_ORIGIN',
6061
default: async () => process.env.SFCC_MRT_CLOUD_ORIGIN || undefined,
6162
}),
6263
'credentials-file': Flags.string({
64+
char: 'c',
6365
description: 'Path to MRT credentials file (overrides default ~/.mobify)',
6466
env: 'MRT_CREDENTIALS_FILE',
6567
}),

packages/b2c-tooling-sdk/src/operations/mrt/bundle.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ import tar from 'tar-fs';
1919
import {Minimatch} from 'minimatch';
2020
import {getLogger} from '../../logging/logger.js';
2121

22+
/**
23+
* Shape of config.server.ts exported from an MRT app.
24+
* Used to define ssrOnly, ssrShared, and ssrParameters for bundle creation.
25+
*/
26+
export interface MrtServerConfig {
27+
ssrOnly: string[];
28+
ssrShared: string[];
29+
ssrParameters?: Record<string, unknown>;
30+
}
31+
32+
export const DEFAULT_SSR_ONLY = ['ssr.js', 'ssr.mjs', 'server/**/*'];
33+
export const DEFAULT_SSR_SHARED = ['static/**/*', 'client/**/*'];
34+
2235
/**
2336
* Default SSR parameters applied to all bundles.
2437
* These can be overridden by providing ssrParameters in CreateBundleOptions.
@@ -49,15 +62,17 @@ export interface CreateBundleOptions {
4962

5063
/**
5164
* Glob patterns for files that should only run on the server.
65+
* If omitted, loaded from build/config.server.js if present.
5266
* @example ['ssr.js', 'ssr/*.js']
5367
*/
54-
ssrOnly: string[];
68+
ssrOnly?: string[];
5569

5670
/**
5771
* Glob patterns for files shared between client and server.
72+
* If omitted, loaded from build/config.server.js if present.
5873
* @example ['static/**\/*', '**\/*.js']
5974
*/
60-
ssrShared: string[];
75+
ssrShared?: string[];
6176

6277
/**
6378
* Path to the build directory containing the application build output.
@@ -170,15 +185,39 @@ export function getDefaultMessage(): string {
170185
* });
171186
* ```
172187
*/
188+
async function loadServerConfig(buildPath: string): Promise<MrtServerConfig | null> {
189+
const configPath = path.join(buildPath, 'config.server.js');
190+
try {
191+
await stat(configPath);
192+
} catch {
193+
return null;
194+
}
195+
try {
196+
const mod = await import(configPath);
197+
const config: MrtServerConfig = mod.config ?? mod.default?.config ?? mod.default;
198+
return config ?? null;
199+
} catch {
200+
return null;
201+
}
202+
}
203+
173204
export async function createBundle(options: CreateBundleOptions): Promise<Bundle> {
174205
const logger = getLogger();
175-
const {ssrOnly, ssrShared, projectSlug} = options;
206+
const {projectSlug} = options;
176207
const buildDirectory = options.buildDirectory || 'build';
177208
const message = options.message || getDefaultMessage();
209+
const buildPath = path.isAbsolute(buildDirectory) ? buildDirectory : path.join(process.cwd(), buildDirectory);
210+
211+
const serverConfig = await loadServerConfig(buildPath);
212+
213+
const ssrOnly = options.ssrOnly ?? serverConfig?.ssrOnly ?? DEFAULT_SSR_ONLY;
214+
const ssrShared = options.ssrShared ?? serverConfig?.ssrShared ?? DEFAULT_SSR_SHARED;
215+
const ssrParamsFromConfig = serverConfig?.ssrParameters ?? {};
178216

179-
// Merge default SSR parameters with provided ones (provided values take precedence)
217+
// Merge: defaults < config.server.js < explicit options (explicit values win)
180218
const ssrParameters = {
181219
...DEFAULT_SSR_PARAMETERS,
220+
...ssrParamsFromConfig,
182221
...options.ssrParameters,
183222
};
184223

@@ -189,9 +228,6 @@ export async function createBundle(options: CreateBundleOptions): Promise<Bundle
189228
throw new Error('ssrOnly and ssrShared patterns are required and cannot be empty');
190229
}
191230

192-
// Verify build directory exists
193-
const buildPath = path.isAbsolute(buildDirectory) ? buildDirectory : path.join(process.cwd(), buildDirectory);
194-
195231
try {
196232
await stat(buildPath);
197233
} catch {

packages/b2c-tooling-sdk/src/operations/mrt/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@
4646
*/
4747

4848
// Bundle creation
49-
export {createBundle, createGlobFilter, getDefaultMessage, DEFAULT_SSR_PARAMETERS} from './bundle.js';
50-
export type {CreateBundleOptions, Bundle} from './bundle.js';
49+
export {
50+
createBundle,
51+
createGlobFilter,
52+
getDefaultMessage,
53+
DEFAULT_SSR_PARAMETERS,
54+
DEFAULT_SSR_ONLY,
55+
DEFAULT_SSR_SHARED,
56+
} from './bundle.js';
57+
export type {CreateBundleOptions, Bundle, MrtServerConfig} from './bundle.js';
5158

5259
// Push and bundle operations
5360
export {pushBundle, uploadBundle, listBundles, downloadBundle, deleteBundle, bulkDeleteBundles} from './push.js';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"all": true,
3+
"src": ["src"],
4+
"exclude": [
5+
"src/**/*.test.ts",
6+
"src/dev/**",
7+
"**/*.d.ts"
8+
],
9+
"reporter": ["text", "text-summary", "html", "lcov"],
10+
"report-dir": "coverage"
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
dist/
3+
coverage/
4+
*.tsbuildinfo
5+
build/
6+
build/**.*

0 commit comments

Comments
 (0)