Skip to content

Commit 6d0199b

Browse files
committed
project skill refinement
1 parent 0c0dc6b commit 6d0199b

File tree

7 files changed

+129
-89
lines changed

7 files changed

+129
-89
lines changed

.claude/skills/api-client-development/SKILL.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: api-client-development
3-
description: Creating API clients with OpenAPI specs, authentication, and OAuth scopes for SCAPI and similar APIs
3+
description: Creating typed API clients with OpenAPI specs, authentication, and OAuth scopes for SCAPI and similar APIs. Use when adding a new SCAPI client, generating types from an OpenAPI spec, setting up OAuth middleware, or integrating a new Commerce API endpoint.
44
metadata:
55
internal: true
66
---
@@ -438,6 +438,16 @@ const {data, error} = await client.GET('/endpoint', {...});
438438

439439
---
440440

441+
## Troubleshooting
442+
443+
**OAuth scope errors (401/403 from SCAPI)**: Ensure the client factory calls `auth.withAdditionalScopes()` with both the domain scope (e.g., `sfcc.custom-apis`) and the tenant-specific scope (`SALESFORCE_COMMERCE_API:<tenantId>`). Use `buildTenantScope()` to strip the `f_ecom_` prefix from tenant IDs before building scopes.
444+
445+
**Type generation failures**: Check that the OpenAPI spec in `specs/` is valid YAML/JSON. Run `pnpm --filter @salesforce/b2c-tooling-sdk run generate:types` and inspect the output. Common issues: spec references external files that aren't present, or uses OpenAPI features not supported by openapi-typescript.
446+
447+
**Middleware ordering issues**: Auth middleware should be added first (`client.use(createAuthMiddleware(...))`), then logging. In openapi-fetch, middleware runs in reverse registration order for requests, so auth registered first means it runs last — ensuring the logging middleware sees the final request with auth headers.
448+
449+
**`organizationId` mismatch**: SCAPI path parameters need the `f_ecom_` prefix (use `toOrganizationId()`), while OAuth scopes need the raw tenant ID (use `toTenantId()`). Mixing these up causes 404s or scope errors.
450+
441451
## Checklist: New SCAPI Client
442452

443453
1. Add OpenAPI spec to `specs/`

.claude/skills/cli-command-development/SKILL.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: cli-command-development
3-
description: Creating new CLI commands and topics for the B2C CLI using oclif
3+
description: Creating new CLI commands and topics for the B2C CLI using oclif. Use when adding a new command, creating a topic, adding flags or arguments, implementing table output, or extending BaseCommand/OAuthCommand/InstanceCommand.
44
metadata:
55
internal: true
66
---
@@ -323,6 +323,16 @@ if (error) {
323323

324324
See [API Client Development](../api-client-development/SKILL.md#error-handling) for supported error patterns.
325325

326+
## Troubleshooting
327+
328+
**Command not found after creating file**: Ensure the file is in the correct `packages/b2c-cli/src/commands/` subdirectory matching the intended command path. Run `pnpm --filter @salesforce/b2c-cli run build` to regenerate the oclif manifest. For new topics, add the topic to `package.json` under `oclif.topics`.
329+
330+
**Flag parsing errors**: Check that flag names use kebab-case in the `static flags` definition. The `char` shorthand must be a single character. If using `dependsOn`, the referenced flag must exist in the same command's flags.
331+
332+
**Missing i18n keys**: The `t()` function falls back to the default string (second argument), so missing keys won't crash at runtime. However, keep key paths consistent with the `commands.<topic>.<command>.<key>` pattern for future localization.
333+
334+
**"requireX" methods not available**: Verify the command extends the correct base class. `requireServer()` is on `InstanceCommand`, `requireOAuthCredentials()` is on `OAuthCommand`, `requireMrtCredentials()` is on `MrtCommand`. Check the class hierarchy if a method is missing.
335+
326336
## Creating a Command Checklist
327337

328338
1. Create file at `packages/b2c-cli/src/commands/<topic>/<command>.ts`

.claude/skills/documentation/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: documentation
3-
description: Updating user guides, CLI reference, and API documentation for the B2C CLI project
3+
description: Updating user guides, CLI reference, and API documentation for the B2C CLI project. Use when adding or changing CLI command docs, writing JSDoc for TypeDoc generation, updating Vitepress sidebar config, or creating new guide pages.
44
metadata:
55
internal: true
66
---

.claude/skills/sdk-module-development/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: sdk-module-development
3-
description: Adding new modules and exports to the @salesforce/b2c-tooling-sdk package
3+
description: Adding new modules and exports to the @salesforce/b2c-tooling-sdk package. Use when creating a new SDK module, adding barrel file exports, configuring package.json exports, or building client factory functions.
44
metadata:
55
internal: true
66
---

.claude/skills/testing/SKILL.md

Lines changed: 10 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: testing
3-
description: Writing tests for the B2C CLI project using Mocha, Chai, and MSW
3+
description: Writing tests for the B2C CLI project using Mocha, Chai, and MSW. Use when writing unit or integration tests, mocking HTTP requests with MSW, testing CLI commands with oclif, isolating config in tests, or setting up test coverage.
44
metadata:
55
internal: true
66
---
@@ -438,71 +438,7 @@ it('handles server flag', async () => {
438438

439439
## Testing CLI Commands with oclif
440440

441-
### Integration Tests with runCommand
442-
443-
Use `@oclif/test`'s `runCommand()` for integration-style tests:
444-
445-
```typescript
446-
import { runCommand } from '@oclif/test';
447-
import { expect } from 'chai';
448-
449-
describe('ods list', () => {
450-
it('runs without errors', async () => {
451-
const { error } = await runCommand('ods list --help');
452-
expect(error).to.be.undefined;
453-
});
454-
});
455-
```
456-
457-
### SDK Base Command Integration Tests
458-
459-
The SDK includes a test fixture at `test/fixtures/test-cli/` for integration testing base command behavior. See `test/cli/base-command.integration.test.ts` for examples.
460-
461-
### When to Use Each Approach
462-
463-
| Approach | Use For |
464-
|----------|---------|
465-
| Unit tests with `stubParse` | Testing protected method logic in isolation |
466-
| Integration tests with fixture | Testing full command lifecycle, flag parsing |
467-
| `runCommand()` in b2c-cli | Testing actual CLI commands |
468-
469-
## E2E Tests
470-
471-
E2E tests run against real infrastructure and are skipped without credentials:
472-
473-
```typescript
474-
describe('ODS Lifecycle E2E', function () {
475-
this.timeout(360_000); // 6 minutes
476-
477-
const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js');
478-
479-
before(function () {
480-
if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET) {
481-
this.skip();
482-
}
483-
});
484-
485-
async function runCLI(args: string[]) {
486-
return execa('node', [CLI_BIN, ...args], {
487-
env: { ...process.env, SFCC_LOG_LEVEL: 'silent' },
488-
reject: false,
489-
});
490-
}
491-
492-
it('creates a sandbox', async function () {
493-
this.timeout(300_000);
494-
495-
const result = await runCLI([
496-
'ods', 'create',
497-
'--realm', process.env.TEST_REALM!,
498-
'--ttl', '24',
499-
'--json',
500-
]);
501-
502-
expect(result.exitCode).to.equal(0);
503-
});
504-
});
505-
```
441+
See [CLI Command Testing Patterns](./references/CLI-COMMAND-TESTING.md) for integration tests with `runCommand`, SDK base command fixture tests, E2E test patterns, and when to use each approach.
506442

507443
## Coverage
508444

@@ -515,30 +451,19 @@ open coverage/index.html
515451

516452
## Test Helpers Reference
517453

518-
### CLI Package (`packages/b2c-cli/test/helpers/`)
454+
See [Test Helpers Reference](./references/HELPERS.md) for a full list of helpers available in both the CLI and SDK packages.
519455

520-
| Helper | Purpose |
521-
|--------|---------|
522-
| `runSilent(fn)` | Capture and suppress stdout/stderr from command execution |
523-
| `stubParse(command, flags, args)` | Stub oclif's parse method with flags (includes silent log level) |
524-
| `createTestCommand(CommandClass, config, flags, args)` | Create command instance with stubbed parse |
525-
| `createIsolatedConfigHooks()` | Mocha hooks for config isolation |
526-
| `createIsolatedEnvHooks()` | Mocha hooks for env var isolation |
456+
## Troubleshooting
527457

528-
### SDK Package (`packages/b2c-tooling-sdk/test/helpers/`)
458+
**MSW handler not matching requests**: Verify the URL pattern in `http.get()`/`http.post()` matches the full URL including base path. Use `onUnhandledRequest: 'error'` in `server.listen()` to surface unmatched requests. Check that the HTTP method matches (e.g., `http.all()` for WebDAV methods like MKCOL/PROPFIND).
529459

530-
| Helper | Purpose |
531-
|--------|---------|
532-
| `MockAuthStrategy` | Mock authentication for API clients |
533-
| `stubParse(command, flags, args)` | Stub oclif's parse method (includes silent log level) |
534-
| `createNullStream()` | Create a writable stream that discards output |
535-
| `CapturingStream` | Writable stream that captures output for assertions |
460+
**Config or env vars leaking between tests**: Always pair `isolateConfig()` with `restoreConfig()` in `beforeEach`/`afterEach`. Missing `restoreConfig()` causes subsequent tests to run with cleared env vars. Use `sinon.restore()` in `afterEach` to clean up all stubs.
536461

537-
### SDK Test Utils (exported from package)
462+
**Import path errors ("module not found")**: Use package exports (`@salesforce/b2c-tooling-sdk/clients`) not relative paths. If a new export was added, ensure it's in `package.json` `exports` with the `development` condition pointing to the `.ts` source file.
538463

539-
```typescript
540-
import { isolateConfig, restoreConfig } from '@salesforce/b2c-tooling-sdk/test-utils';
541-
```
464+
**Fake timers break MSW**: MSW v2 uses microtasks internally. Never use `@sinonjs/fake-timers` or `sinon.useFakeTimers()` in tests that use MSW. Use `pollInterval: 10` for fast polling tests instead.
465+
466+
**Test output is noisy**: Use `runSilent()` to suppress stdout/stderr from commands. The `stubParse` helper automatically sets `'log-level': 'silent'` to quiet pino logger output.
542467

543468
## Writing Tests Checklist
544469

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# CLI Command Testing Patterns
2+
3+
## Testing CLI Commands with oclif
4+
5+
### Integration Tests with runCommand
6+
7+
Use `@oclif/test`'s `runCommand()` for integration-style tests:
8+
9+
```typescript
10+
import { runCommand } from '@oclif/test';
11+
import { expect } from 'chai';
12+
13+
describe('ods list', () => {
14+
it('runs without errors', async () => {
15+
const { error } = await runCommand('ods list --help');
16+
expect(error).to.be.undefined;
17+
});
18+
});
19+
```
20+
21+
### SDK Base Command Integration Tests
22+
23+
The SDK includes a test fixture at `test/fixtures/test-cli/` for integration testing base command behavior. See `test/cli/base-command.integration.test.ts` for examples.
24+
25+
### When to Use Each Approach
26+
27+
| Approach | Use For |
28+
|----------|---------|
29+
| Unit tests with `stubParse` | Testing protected method logic in isolation |
30+
| Integration tests with fixture | Testing full command lifecycle, flag parsing |
31+
| `runCommand()` in b2c-cli | Testing actual CLI commands |
32+
33+
## E2E Tests
34+
35+
E2E tests run against real infrastructure and are skipped without credentials:
36+
37+
```typescript
38+
describe('ODS Lifecycle E2E', function () {
39+
this.timeout(360_000); // 6 minutes
40+
41+
const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js');
42+
43+
before(function () {
44+
if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET) {
45+
this.skip();
46+
}
47+
});
48+
49+
async function runCLI(args: string[]) {
50+
return execa('node', [CLI_BIN, ...args], {
51+
env: { ...process.env, SFCC_LOG_LEVEL: 'silent' },
52+
reject: false,
53+
});
54+
}
55+
56+
it('creates a sandbox', async function () {
57+
this.timeout(300_000);
58+
59+
const result = await runCLI([
60+
'ods', 'create',
61+
'--realm', process.env.TEST_REALM!,
62+
'--ttl', '24',
63+
'--json',
64+
]);
65+
66+
expect(result.exitCode).to.equal(0);
67+
});
68+
});
69+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Test Helpers Reference
2+
3+
## CLI Package (`packages/b2c-cli/test/helpers/`)
4+
5+
| Helper | Purpose |
6+
|--------|---------|
7+
| `runSilent(fn)` | Capture and suppress stdout/stderr from command execution |
8+
| `stubParse(command, flags, args)` | Stub oclif's parse method with flags (includes silent log level) |
9+
| `createTestCommand(CommandClass, config, flags, args)` | Create command instance with stubbed parse |
10+
| `createIsolatedConfigHooks()` | Mocha hooks for config isolation |
11+
| `createIsolatedEnvHooks()` | Mocha hooks for env var isolation |
12+
13+
## SDK Package (`packages/b2c-tooling-sdk/test/helpers/`)
14+
15+
| Helper | Purpose |
16+
|--------|---------|
17+
| `MockAuthStrategy` | Mock authentication for API clients |
18+
| `stubParse(command, flags, args)` | Stub oclif's parse method (includes silent log level) |
19+
| `createNullStream()` | Create a writable stream that discards output |
20+
| `CapturingStream` | Writable stream that captures output for assertions |
21+
22+
## SDK Test Utils (exported from package)
23+
24+
```typescript
25+
import { isolateConfig, restoreConfig } from '@salesforce/b2c-tooling-sdk/test-utils';
26+
```

0 commit comments

Comments
 (0)