Skip to content

Commit 59829f8

Browse files
W-21109909: Storefront-next project discovery (#137)
* First draft for sf-next detection * Update readme * Remove sfcc-odyssey hardcoding, update tests and readme * Small lint and test changes
1 parent c11199d commit 59829f8

4 files changed

Lines changed: 97 additions & 10 deletions

File tree

packages/b2c-dx-mcp/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The server analyzes your working directory and enables toolsets based on what it
5757
| Project Type | Detection | Toolsets Enabled |
5858
|--------------|-----------|------------------|
5959
| **PWA Kit v3** | `@salesforce/pwa-kit-*`, `@salesforce/retail-react-app`, or `ccExtensibility` in package.json | PWAV3, MRT, SCAPI |
60-
| **Storefront Next** | `@salesforce/storefront-next-*` in package.json | STOREFRONTNEXT, MRT, SCAPI |
60+
| **Storefront Next** | Root or a workspace package has `@salesforce/storefront-next*` dependency, or package name starting with `storefront-next`. | STOREFRONTNEXT, MRT, CARTRIDGES, SCAPI |
6161
| **Cartridges** | `.project` file in cartridge directory | CARTRIDGES, SCAPI |
6262
| **No project detected** | No B2C markers found | SCAPI (base toolset only) |
6363

packages/b2c-tooling-sdk/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ The SDK provides subpath exports for tree-shaking and organization:
306306
| `@salesforce/b2c-tooling-sdk/operations/users` | Account Manager user management |
307307
| `@salesforce/b2c-tooling-sdk/operations/roles` | Account Manager role management |
308308
| `@salesforce/b2c-tooling-sdk/operations/orgs` | Account Manager organization management |
309-
| `@salesforce/b2c-tooling-sdk/discovery` | Workspace type detection (PWA Kit, SFRA, etc.) |
309+
| `@salesforce/b2c-tooling-sdk/discovery` | Workspace type detection (PWA Kit, Storefront Next, cartridges, etc.) |
310310
| `@salesforce/b2c-tooling-sdk/cli` | CLI utilities (BaseCommand, table rendering) |
311311
| `@salesforce/b2c-tooling-sdk/logging` | Structured logging utilities |
312312

packages/b2c-tooling-sdk/src/discovery/patterns/storefront-next.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,57 @@
88
*
99
* @module discovery/patterns/storefront-next
1010
*/
11+
import path from 'node:path';
1112
import type {DetectionPattern} from '../types.js';
12-
import {readPackageJson} from '../utils.js';
13+
import type {PackageJson} from '../utils.js';
14+
import {readPackageJson, globDirs} from '../utils.js';
15+
16+
/**
17+
* Returns true if this package.json (deps or name) indicates a Storefront Next project.
18+
* Relies on @salesforce/storefront-next* dependency or package name starting with "storefront-next" only.
19+
*/
20+
function packageIndicatesStorefrontNext(pkg: PackageJson): boolean {
21+
const deps = Object.keys({...pkg.dependencies, ...pkg.devDependencies});
22+
if (deps.some((dep) => dep.startsWith('@salesforce/storefront-next'))) {
23+
return true;
24+
}
25+
const name = pkg.name;
26+
if (typeof name !== 'string' || !name.trim()) return false;
27+
const nameWithoutScope = name.includes('/') ? name.split('/').pop()?.trim() : name.trim();
28+
if (!nameWithoutScope) return false;
29+
return nameWithoutScope.startsWith('storefront-next');
30+
}
1331

1432
/**
1533
* Detection pattern for Storefront Next (Odyssey) projects.
1634
*
17-
* Detects projects that have Storefront Next SDK dependencies.
18-
* Matches packages starting with @salesforce/storefront-next
19-
* (e.g., @salesforce/storefront-next-dev, @salesforce/storefront-next-runtime).
35+
* Detects (1) storefront-next-template and similar: root package.json has
36+
* @salesforce/storefront-next* dependency or name starting with "storefront-next".
37+
* (2) storefront-next monorepo: root has no signal but a workspace package has
38+
* the dependency or name (e.g. https://github.com/SalesforceCommerceCloud/storefront-next).
2039
*/
2140
export const storefrontNextPattern: DetectionPattern = {
2241
name: 'storefront-next',
2342
projectType: 'storefront-next',
2443
detect: async (workspacePath) => {
25-
const pkg = await readPackageJson(workspacePath);
26-
if (!pkg) return false;
44+
const rootPkg = await readPackageJson(workspacePath);
45+
if (!rootPkg) return false;
46+
47+
if (packageIndicatesStorefrontNext(rootPkg)) return true;
48+
49+
const workspaces = rootPkg.workspaces;
50+
if (!workspaces) return false;
2751

28-
const deps = Object.keys({...pkg.dependencies, ...pkg.devDependencies});
29-
return deps.some((dep) => dep.startsWith('@salesforce/storefront-next'));
52+
const patterns = Array.isArray(workspaces) ? workspaces : [workspaces];
53+
for (const pattern of patterns) {
54+
if (typeof pattern !== 'string' || !pattern.trim()) continue;
55+
const dirs = await globDirs(pattern, {cwd: workspacePath});
56+
for (const dir of dirs) {
57+
const pkgPath = path.join(workspacePath, dir);
58+
const pkg = await readPackageJson(pkgPath);
59+
if (pkg && packageIndicatesStorefrontNext(pkg)) return true;
60+
}
61+
}
62+
return false;
3063
},
3164
};

packages/b2c-tooling-sdk/test/discovery/patterns/storefront-next.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,59 @@ describe('discovery/patterns/storefront-next', () => {
7676

7777
expect(result).to.be.false;
7878
});
79+
80+
it('detects by root package name starting with storefront-next', async () => {
81+
const pkg = {name: 'storefront-next-app'};
82+
await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
83+
84+
const result = await storefrontNextPattern.detect(tempDir);
85+
86+
expect(result).to.be.true;
87+
});
88+
89+
it('detects monorepo when a workspace package has storefront-next dependency', async () => {
90+
const rootPkg = {name: 'my-monorepo', workspaces: ['packages/*']};
91+
await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify(rootPkg));
92+
const packagesDir = path.join(tempDir, 'packages');
93+
await fs.mkdir(packagesDir, {recursive: true});
94+
const pkgDir = path.join(packagesDir, 'storefront-app');
95+
await fs.mkdir(pkgDir, {recursive: true});
96+
const pkgPkg = {name: 'storefront-app', dependencies: {'@salesforce/storefront-next-runtime': '^1.0.0'}};
97+
await fs.writeFile(path.join(pkgDir, 'package.json'), JSON.stringify(pkgPkg));
98+
99+
const result = await storefrontNextPattern.detect(tempDir);
100+
101+
expect(result).to.be.true;
102+
});
103+
104+
it('detects monorepo when a workspace package name starts with storefront-next', async () => {
105+
const rootPkg = {name: 'some-repo', workspaces: ['packages/*']};
106+
await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify(rootPkg));
107+
const packagesDir = path.join(tempDir, 'packages');
108+
await fs.mkdir(packagesDir, {recursive: true});
109+
const pkgDir = path.join(packagesDir, 'storefront-next-runtime');
110+
await fs.mkdir(pkgDir, {recursive: true});
111+
const pkgPkg = {name: 'storefront-next-runtime'};
112+
await fs.writeFile(path.join(pkgDir, 'package.json'), JSON.stringify(pkgPkg));
113+
114+
const result = await storefrontNextPattern.detect(tempDir);
115+
116+
expect(result).to.be.true;
117+
});
118+
119+
it('returns false for monorepo when no workspace package indicates storefront-next', async () => {
120+
const rootPkg = {name: 'my-monorepo', workspaces: ['packages/*']};
121+
await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify(rootPkg));
122+
const packagesDir = path.join(tempDir, 'packages');
123+
await fs.mkdir(packagesDir, {recursive: true});
124+
const pkgDir = path.join(packagesDir, 'other-app');
125+
await fs.mkdir(pkgDir, {recursive: true});
126+
const pkgPkg = {name: 'other-app', dependencies: {react: '^18.0.0'}};
127+
await fs.writeFile(path.join(pkgDir, 'package.json'), JSON.stringify(pkgPkg));
128+
129+
const result = await storefrontNextPattern.detect(tempDir);
130+
131+
expect(result).to.be.false;
132+
});
79133
});
80134
});

0 commit comments

Comments
 (0)