Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@
"build:graphql": "yarn build:admin-api && yarn build:merchandising-api",
"lint": "docker run --rm -e RUN_LOCAL=true --env-file .github/super-linter.env -v \"$PWD\":/tmp/lint github/super-linter:slim-v5"
},
"packageManager": "yarn@3.2.4"
"packageManager": "yarn@4.13.0"
}
3 changes: 2 additions & 1 deletion scripts/build-with-enhanced-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ async function buildWithEnhancedSchema() {
// Step 2: Generate SpectaQL config pointing to the enhanced schema
console.log('🚀 Step 2: Generating SpectaQL configuration...');
const fs = require('fs');
const { tempConfigPath } = require('./generate-spectaql-config');
const { tempConfigPath, writeTempMerchandisingConfig } = require('./generate-spectaql-config');
writeTempMerchandisingConfig();
let tempConfig = fs.readFileSync(tempConfigPath, 'utf8');
tempConfig = tempConfig.replace(
/^(\s*)#(introspectionFile:\s*spectaql\/enhanced-schema\.json)/m,
Expand Down
91 changes: 50 additions & 41 deletions scripts/generate-spectaql-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,43 @@
const fs = require('fs');
const path = require('path');

// Load environment variables from .env file
// Load environment variables from .env file (needed when this module is required or run as CLI)
require('dotenv').config();

// Read the base configuration
const configPath = path.join(__dirname, '../spectaql/config-merchandising.yml');
const configContent = fs.readFileSync(configPath, 'utf8');
const tempConfigPath = path.join(__dirname, '../spectaql/config-merchandising-temp.yml');

// Get TENANT_ID from environment variable (from .env file or system)
const tenantId = process.env.TENANT_ID;
if (!tenantId) {
console.error('Error: TENANT_ID environment variable is required');
console.error('');
console.error('You can set it in several ways:');
console.error('1. Create a .env file in the project root with: TENANT_ID=your_tenant_id');
console.error('2. Set it as a system environment variable: export TENANT_ID=your_tenant_id');
console.error('3. Set it inline: TENANT_ID=your_tenant_id node scripts/generate-spectaql-config.js');
console.error('');
console.error('Example .env file:');
console.error(' TENANT_ID=abc123');
console.error(' # API_KEY=your_api_key_here');
process.exit(1);
}
/**
* Writes `config-merchandising-temp.yml` with `${TENANT_ID}` substituted.
* Call this before SpectaQL when using the merchandising temp config.
*/
function writeTempMerchandisingConfig() {
const tenantId = process.env.TENANT_ID;
if (!tenantId) {
console.error('Error: TENANT_ID environment variable is required');
console.error('');
console.error('You can set it in several ways:');
console.error('1. Create a .env file in the project root with: TENANT_ID=your_tenant_id');
console.error('2. Set it as a system environment variable: export TENANT_ID=your_tenant_id');
console.error('3. Set it inline: TENANT_ID=your_tenant_id node scripts/generate-spectaql-config.js');
console.error('');
console.error('Example .env file:');
console.error(' TENANT_ID=abc123');
console.error(' # API_KEY=your_api_key_here');
process.exit(1);
}

// Replace the placeholders with the actual values
let updatedConfig = configContent.replace(/\${TENANT_ID}/g, tenantId);
const configContent = fs.readFileSync(
path.join(__dirname, '../spectaql/config-merchandising.yml'),
'utf8'
);
const updatedConfig = configContent.replace(/\${TENANT_ID}/g, tenantId);
fs.writeFileSync(tempConfigPath, updatedConfig);

// Write the updated configuration to a temporary file
const tempConfigPath = path.join(__dirname, '../spectaql/config-merchandising-temp.yml');
fs.writeFileSync(tempConfigPath, updatedConfig);

console.log(`Generated SpectaQL config with:`);
console.log(` Tenant ID: ${tenantId}`);
console.log(`Temporary config file: ${tempConfigPath}`);
console.log('Use this file with: spectaql --config spectaql/config-merchandising-temp.yml');
console.log(`Generated SpectaQL config with:`);
console.log(` Tenant ID: ${tenantId}`);
console.log(`Temporary config file: ${tempConfigPath}`);
console.log('Use this file with: spectaql --config spectaql/config-merchandising-temp.yml');
}

// Function to clean up temporary file
function cleanupTempFile() {
Expand All @@ -56,16 +59,22 @@ function cleanupTempFile() {
}
}

// Clean up on script exit
process.on('exit', cleanupTempFile);
process.on('SIGINT', () => {
cleanupTempFile();
process.exit(0);
});
process.on('SIGTERM', () => {
cleanupTempFile();
process.exit(0);
});
if (require.main === module) {
writeTempMerchandisingConfig();

process.on('exit', cleanupTempFile);
process.on('SIGINT', () => {
cleanupTempFile();
process.exit(0);
});
process.on('SIGTERM', () => {
cleanupTempFile();
process.exit(0);
});
}

// Export the cleanup function for use by other scripts
module.exports = { cleanupTempFile, tempConfigPath };
module.exports = {
cleanupTempFile,
tempConfigPath,
writeTempMerchandisingConfig
};
25 changes: 7 additions & 18 deletions scripts/run-spectaql-with-cleanup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
* Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from Adobe.
*/

const fs = require('fs');
const { spawn } = require('child_process');
const { cleanupTempFile, tempConfigPath } = require('./generate-spectaql-config');
const {
cleanupTempFile,
writeTempMerchandisingConfig
} = require('./generate-spectaql-config');

// Get command line arguments (everything after the script name)
const args = process.argv.slice(2);
Expand All @@ -19,24 +21,11 @@ if (args.length === 0) {
process.exit(1);
}

// First, generate the config file
console.log('Generating SpectaQL configuration...');

// Switch the temp config to use the enhanced schema file instead of the live URL.
// The enhanced-schema.json contains descriptions injected from the metadata file
// that the live introspection endpoint does not return.
// The original config is never modified — the temp file is cleaned up after the build.
let tempConfig = fs.readFileSync(tempConfigPath, 'utf8');
tempConfig = tempConfig.replace(
/^(\s*)#\s*(introspectionFile:\s*spectaql\/enhanced-schema\.json)/m,
'$1$2'
);
tempConfig = tempConfig.replace(
/^(\s*)(url:\s*https:\/\/.*\/graphql.*)/gm,
'$1# $2'
writeTempMerchandisingConfig();
console.log(
'SpectaQL will introspect the live GraphQL URL from the temp config (not enhanced-schema.json).'
);
fs.writeFileSync(tempConfigPath, tempConfig);
console.log('Configured SpectaQL to use enhanced schema file');

// Now run SpectaQL with the provided arguments
console.log('Running SpectaQL...');
Expand Down
14 changes: 7 additions & 7 deletions spectaql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This directory contains [SpectaQL](https://github.com/anvilco/spectaql) configur
|---|---|
| `config-merchandising.yml` | Main SpectaQL config. Defines where to get the schema, what to document/hide, metadata paths, server info, intro text, theme, and the output directory (`static/graphql-api/merchandising-api`). |
| `metadata-merchandising.json` | Filtering and description metadata. Marks unwanted queries, types, enums, unions, input objects, and interfaces as `undocumented: true` so SpectaQL excludes them. Also contains custom descriptions for query arguments under `FIELD_ARGUMENT`. |
| `enhanced-schema.json` | Cached introspection result with custom descriptions already injected. Used for dev builds instead of hitting the live endpoint on every rebuild. Generated by `scripts/fetch-and-enhance-schema.js`. |
| `enhanced-schema.json` | Cached introspection result with descriptions from the live endpoint plus overlays from `metadata-merchandising.json`. Used for **production** builds (`yarn build:merchandising-api`) after a fresh fetch. Generated by `scripts/fetch-and-enhance-schema.js`. |
| `config-merchandising-temp.yml` | Auto-generated temp config. A copy of the main config with `${TENANT_ID}` placeholders replaced by the actual environment variable value. Deleted automatically after the build. |
| `config-admin.yml` | Configuration for the Admin API (reference not currently implemented). |
| `comdox-theme/` | Custom SpectaQL theme. Contains Handlebars partial overrides (`type.hbs` and `_query-or-mutation-or-subscription.hbs`) that customize how types and queries render in the HTML output. |
Expand All @@ -25,7 +25,7 @@ This directory contains [SpectaQL](https://github.com/anvilco/spectaql) configur
| Script | Role |
|---|---|
| `generate-spectaql-config.js` | Reads `config-merchandising.yml`, substitutes `${TENANT_ID}` from your `.env` file, writes the temp config, and cleans up on exit. |
| `run-spectaql-with-cleanup.js` | Orchestrates the **dev build**: generates the temp config, switches it to use `enhanced-schema.json`, spawns SpectaQL as a child process, then cleans up the temp file. This is what `yarn dev:merchandising-api` runs. |
| `run-spectaql-with-cleanup.js` | Orchestrates the **dev build**: writes the temp config, runs SpectaQL against the **live GraphQL URL** from that config (same as `config-merchandising.yml`), then cleans up the temp file. This is what `yarn dev:merchandising-api` runs. |
| `fetch-and-enhance-schema.js` | Fetches a live introspection result from the GraphQL endpoint, then injects custom descriptions from `metadata-merchandising.json` into the raw schema. Saves the result to `enhanced-schema.json`. |
| `build-with-enhanced-schema.js` | Orchestrates the **production build**: (1) fetches a fresh schema and injects descriptions from `metadata-merchandising.json`, (2) generates the temp config and rewrites it to point at `enhanced-schema.json` instead of the live URL, (3) runs SpectaQL. This is what `yarn build:merchandising-api` runs. |

Expand Down Expand Up @@ -128,24 +128,24 @@ What happens during a production build:

1. `fetch-and-enhance-schema.js` fetches the live introspection result and injects descriptions from `metadata-merchandising.json` directly into the schema JSON.
1. The result is saved to `enhanced-schema.json`.
1. `generate-spectaql-config.js` reads the YAML config, replaces `${TENANT_ID}`, and writes the temp config.
1. The production build script writes the temp config (same substitution as `node scripts/generate-spectaql-config.js`).
1. The temp config is rewritten to use `introspectionFile: spectaql/enhanced-schema.json` instead of the live URL.
1. SpectaQL runs against the local enhanced schema file.
1. The custom theme partials override the default rendering.
1. Output lands in `static/graphql-api/merchandising-api/index.html`.
1. The temp config file is cleaned up.

### Dev build (fast iteration)
### Dev build (live schema)

Uses the existing `enhanced-schema.json` on disk — no live API call. Useful when iterating on metadata descriptions, theme templates, or config without waiting for a schema fetch:
Introspects the **live** GraphQL endpoint on every run (and on each SpectaQL watch rebuild). Requires `.env` with `TENANT_ID` and `CATALOG_VIEW_ID`, and network access to the API host.

```bash
yarn dev:merchandising-api
```

This starts SpectaQL in watch mode with a live preview at `http://localhost:4400`. It rebuilds automatically when source files change.
This starts SpectaQL in development mode with a preview at `http://localhost:4400`. Metadata from `metadata-merchandising.json` is still applied by SpectaQL via `metadataFile` in the config.

> **Note:** The dev build does not refresh `enhanced-schema.json`. If the live schema has changed or you have updated `metadata-merchandising.json`, run `yarn build:merchandising-api` to get a fresh result.
> **Note:** To refresh the checked-in `enhanced-schema.json` and production HTML without watch mode, run `yarn build:merchandising-api`.

### Update the cached schema only

Expand Down
15 changes: 15 additions & 0 deletions src/pages/optimizer/data-ingestion/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ For example, if you're selling a t-shirt, the product variants might include dif

For details and examples of different product types and product variations, see the <a href="https://developer.adobe.com/commerce/services/reference/rest/#tag/Products" target="_blank" rel="noopener noreferrer">Products API</a> in the *Data Ingestion API Reference*.

## Product layers

Catalog layers allow you to modify product data without changing the original source data. Layers apply changes to specific product attributes, such as name, description, images, links, and metadata, by creating a layer on top of your base catalog. Your original product data remains intact, allowing you to safely customize products and revert changes at any time.

Use product layers to:

- Override product attributes for specific markets or channels
- Provide locale-specific content while maintaining a global base product
- Create seasonal or promotional variations without duplicating entire product records
- Implement A/B testing scenarios with different product presentations

Use the [Product layers](https://developer.adobe.com/commerce/services/reference/rest/#tag/Product-Layers) API to create and delete product layers.

Once layers are created, you can add them to catalog view definitions to customize the catalog data delivered to the storefront. For details on how layers are managed and applied, see [Catalog Layers](https://experienceleague.adobe.com/en/docs/commerce/optimizer/setup/catalog-layer) in the Adobe Commerce Optimizer documentation.

## Price books and prices

In Merchandising Services, a product SKU and its price are decoupled. This decoupling allows you to define multiple price books for a single SKU, supporting different customer tiers, business units, and geographies. When defining prices for a product SKU, you can set both regular and discounted prices within the catalog source for each price book.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ Use the following API operations to manage categories for Commerce projects that

- Create category data using the `categories` operations available in the [Data Ingestion REST API](https://developer.adobe.com/commerce/services/reference/rest/#tag/Categories), and using the `products` operations to manage product category assignments.

- Retrieve category navigation and hierarchy data using the `navigation` and `categoryTree` queries available in the [Merchandising Services GraphQL API](https://developer.adobe.com/commerce/webapi/graphql/merchandising/).
- Retrieve category navigation and hierarchy data using the [`navigation`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#query-navigation) and [`categoryTree`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#query-categoryTree) queries.

- Retrieve category context for products — such as breadcrumbs — using the `categories` field on product queries in the [Merchandising Services GraphQL API](https://developer.adobe.com/commerce/webapi/graphql/merchandising/).
- Retrieve category context for products — such as breadcrumbs — using the `categories` field on [product queries](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#query-products).

<InlineAlert variant="warning" slots="text" />

For Commerce sites with an Adobe Commerce as a Cloud Service or an Adobe Commerce on Cloud infrastructure or on-premises backend, manage [categories configuration](https://experienceleague.adobe.com/en/docs/commerce-admin/catalog/categories/categories) from the Commerce Amin, and use the [categories query](https://developer.adobe.com/commerce/webapi/graphql/schema/catalog-service/queries/categories/) available in the [Catalog Service GraphQL API](https://developer.adobe.com/commerce/webapi/graphql/schema/catalog-service/) to manage categories.

## Category types

The `navigation` query, `categoryTree` query, and `categories` field on product queries each return a different category type, optimized for its specific use case. All three types implement the `CategoryViewV2` interface, which defines the two required fields shared by every category: `slug` and `name`. For complete field details, see [`CategoryViewV2`](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryViewInterface) in the Merchandising Services GraphQL API reference.
The `navigation` query, `categoryTree` query, and `categories` field on product queries each return a different category type, optimized for its specific use case. All three types implement the `CategoryViewV2` interface, which defines the two required fields shared by every category: `slug` and `name`. For complete field details, see [`CategoryViewV2`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#definition-CategoryViewV2) in the Merchandising Services GraphQL API reference.

1. **[CategoryNavigationView](#categorynavigationview-type)** — For menu rendering and navigation
2. **[CategoryProductView](#categoryproductview-type)** — For category data returned with product queries
Expand All @@ -50,7 +50,8 @@ type CategoryNavigationView implements CategoryViewV2 {
}
```

For complete field details, see `[CategoryNavigationView](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryNavigationView)` in the Merchandising Services GraphQL API reference.
For complete field details, see [`CategoryNavigationView`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#definition-CategoryTreeView)
in the Merchandising Services GraphQL API reference.

See the [Navigation query examples](#navigation-query-examples) section for example queries and responses using this type.

Expand All @@ -77,7 +78,7 @@ type CategoryProductView implements CategoryViewV2 {

The `parents` field is self-referencing—each parent entry is itself a `CategoryProductView` with its own `name`, `slug`, `level`, and `parents`. This allows you to reconstruct the full breadcrumb path for any category a product belongs to.

For complete field details, see [`CategoryProductView`](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryProductView) in the Merchandising Services GraphQL API reference.
For complete field details, see [`CategoryProductView`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#definition-CategoryProductView) in the Merchandising Services GraphQL API reference.

See the [Products query with categories examples](#products-query-with-categories-examples) section for example queries and responses using this type.

Expand Down Expand Up @@ -121,7 +122,7 @@ type CategoryImage {
}
```

For complete field details, including the [`CategoryMetaTags`](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryMetaTags) and [`CategoryImage`](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryImage) types, see [`CategoryTreeView`](https://developer.adobe.com/commerce/webapi/graphql/merchandising/#definition-CategoryTreeView) in the Merchandising Services GraphQL API reference.
For complete field details, including the `CategoryMetaTags` and `CategoryImage` types, see [`CategoryTreeView`](https://developer.adobe.com/commerce/services/graphql-api/merchandising-api/index.html#definition-CategoryTreeView) in the Merchandising Services GraphQL API reference.

See the [CategoryTree query examples](#categorytree-query-examples) section for example queries and responses using this type.

Expand Down
Loading
Loading