@salesforce/plugin-bre-to-cml 1.1.5 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import { SfCommand } from '@salesforce/sf-plugins-core';
2
2
  import { Connection, Org } from '@salesforce/core';
3
- import { ParsedRuleDefinition, RuleKeyEntry, RuleRecord } from './models.js';
3
+ import { ParsedRuleDefinition, RecordUpdate, RecordUpdatePlan, RuleKeyEntry, RuleRecord } from './models.js';
4
4
  export type InsuranceRuleConvertResult = {
5
5
  cmlFile: string;
6
6
  associationsFile: string;
7
7
  ruleKeyMapping: RuleKeyEntry[];
8
+ recordUpdateFile?: string;
8
9
  };
9
10
  export type InsuranceRuleConvertContext = {
10
11
  targetOrg: Org;
@@ -13,6 +14,11 @@ export type InsuranceRuleConvertContext = {
13
14
  workspaceDir: string | undefined;
14
15
  inputFile: string | undefined;
15
16
  updateRecords: boolean;
17
+ mergeWithOrg?: boolean;
18
+ };
19
+ export type ParsedRuleEntry = {
20
+ record: RuleRecord;
21
+ ruleDef: ParsedRuleDefinition;
16
22
  };
17
23
  export declare abstract class InsuranceRuleConvertCommand<R extends RuleRecord> extends SfCommand<InsuranceRuleConvertResult> {
18
24
  static readonly flags: {
@@ -22,17 +28,55 @@ export declare abstract class InsuranceRuleConvertCommand<R extends RuleRecord>
22
28
  'workspace-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
29
  'update-records': import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
30
  };
31
+ protected readonly ruleType?: string;
25
32
  protected abstract readonly recordLabel: string;
26
33
  protected abstract readonly keyPrefix: string;
27
34
  protected abstract readonly constraintLabel: string;
28
35
  protected abstract readonly apiNamePrefix: string;
29
36
  protected abstract readonly soql: string;
37
+ /** Discriminator that selects the on-disk file kind and name suffix for the emitted update file. */
38
+ protected abstract readonly recordUpdateKind: RecordUpdatePlan['kind'];
30
39
  protected runConvert(ctx: InsuranceRuleConvertContext): Promise<InsuranceRuleConvertResult>;
40
+ /**
41
+ * Merge-mode entry point. The default errors because merging only makes sense for rule-statement
42
+ * subclasses (surcharge); constraint-form subclasses (underwriting) leave it unimplemented.
43
+ */
44
+ protected runMergeConvert(ctx: InsuranceRuleConvertContext, conn: Connection, records: R[], ruleDefs: ParsedRuleEntry[], productIdToCode: Map<string, string>, api: string, safeApi: string, workspaceDir: string): Promise<InsuranceRuleConvertResult>;
45
+ /**
46
+ * Writes the merged outputs: the full merged CML, a header-only associations CSV (merge mode
47
+ * touches only existing type blocks, so no new Type associations are needed — but the common
48
+ * import errors on an empty CSV, so the header line must be present), and the rule-key mapping.
49
+ */
50
+ protected writeMergedOutputFiles(mergedCml: string, ruleKeyMapping: RuleKeyEntry[], safeApi: string, workspaceDir: string, api: string, recordUpdateFile: string): Promise<InsuranceRuleConvertResult>;
51
+ /**
52
+ * Build the record-update plan envelope and write it to `<safeApi>_<kind>Update.json`. This is the
53
+ * ONLY way org-record changes leave convert now — convert never writes to the org itself. The file
54
+ * is a reviewable manifest the operator applies to the org separately; this plugin does not apply it.
55
+ */
56
+ protected writeRecordUpdateFile(records: R[], ruleKeyMapping: RuleKeyEntry[], api: string, safeApi: string, workspaceDir: string): Promise<string>;
31
57
  private loadRecords;
32
58
  private parseAllRecords;
33
59
  private resolveProductCodes;
60
+ /**
61
+ * Validates the Id -> Name map that feeds each Type association's reference value, mutating it in
62
+ * place so only safe, importer-resolvable Names survive. The Name is written into the naive
63
+ * comma-joined associations CSV and, downstream, into the common importer's single-quoted SOQL
64
+ * `WHERE Name IN ('<Name>')`. Two hazards, neither of which we can fix in the (Rev-Cloud-owned)
65
+ * importer. Unsafe characters (comma, quote, backslash, newline) corrupt the CSV column or break
66
+ * out of the SOQL literal — such a Name is dropped from the map (the generator falls back to the
67
+ * ProductCode) and warned, so the association may not resolve but the output stays valid and
68
+ * injection-free instead of silently wrong. Duplicate Names across distinct product ids make the
69
+ * importer's by-Name lookup ambiguous (last-writer-wins), so a Type could bind to the wrong
70
+ * Product2 — we warn but keep the entries; the documented precondition is that Product Name is
71
+ * unique per migration scope.
72
+ */
73
+ private validateAssociationNames;
34
74
  private discoverCmlApi;
35
75
  private writeOutputFiles;
36
76
  protected abstract parseRecord(record: R): ParsedRuleDefinition | null;
37
- protected abstract updateOrgRecords(records: R[], ruleKeyMapping: RuleKeyEntry[], conn: Connection): Promise<void>;
77
+ /**
78
+ * Pure transform: given the parsed records and the rule-key mapping, return the list of org-record
79
+ * changes convert WOULD have made. No org writes — the result is serialized into the update file.
80
+ */
81
+ protected abstract buildRecordUpdates(records: R[], ruleKeyMapping: RuleKeyEntry[]): RecordUpdate[];
38
82
  }
@@ -17,13 +17,25 @@
17
17
  // This file is a shared abstract base, not a user-invocable command, so the
18
18
  // command-shape lint rules do not apply.
19
19
  import * as fs from 'node:fs/promises';
20
+ import * as path from 'node:path';
20
21
  import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
21
22
  import { Messages } from '@salesforce/core';
22
23
  import { generateCsvForAssociations } from '../utils/association.utils.js';
23
- import { buildCmlModel } from './insurance-rule-generator.js';
24
+ import { buildCmlModel, isSafeAssociationReferenceValue, sanitizeName } from './insurance-rule-generator.js';
24
25
  import { discoverCmlApiByProducts, fetchProductCodes } from './insurance-org.js';
26
+ import { splitProductPath } from './insurance-cml-merge.js';
25
27
  Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
26
28
  const messages = Messages.loadMessages('@salesforce/plugin-bre-to-cml', 'cml.convert.insurance-shared');
29
+ // Header row for the header-only `_Associations.csv` the merge path writes (merge mode creates no
30
+ // new Type associations). Kept here in the insurance layer — the common association util owns the
31
+ // build-path CSV and its own copy of this header.
32
+ const ASSOCIATIONS_CSV_HEADER = 'ExpressionSet.ApiName,ConstraintModelTag,ConstraintModelTagType,ReferenceObjectId,$Product2ReferenceId,$ProductClassificationName,$ProductRelatedComponentKey';
33
+ // File-name suffix per record-update kind. The convert commands emit `<safeApi>_<suffix>.json` as a
34
+ // reviewable manifest of the org-record changes; this plugin does NOT apply it (convert is file-only).
35
+ const RECORD_UPDATE_FILE_SUFFIX = {
36
+ 'underwriting-update': 'UnderwritingUpdate',
37
+ 'surcharge-update': 'SurchargeUpdate',
38
+ };
27
39
  export class InsuranceRuleConvertCommand extends SfCommand {
28
40
  static flags = {
29
41
  'target-org': Flags.requiredOrg(),
@@ -42,31 +54,128 @@ export class InsuranceRuleConvertCommand extends SfCommand {
42
54
  default: false,
43
55
  }),
44
56
  };
57
+ // When defined, eligibility is emitted as a CML `rule(...)` statement tagged with this rule
58
+ // type instead of a `constraint`. Subclasses that want the constraint form leave it undefined.
59
+ ruleType;
45
60
  async runConvert(ctx) {
61
+ // Tripwire: convert is now strictly file-only. The legacy --update-records flag mutated the org
62
+ // as a side effect; it is removed, and passing it errors with a pointer to the new flow.
63
+ if (ctx.updateRecords) {
64
+ throw messages.createError('error.updateRecordsRemoved');
65
+ }
46
66
  const workspaceDir = ctx.workspaceDir ?? '.';
47
67
  this.log(`Using Workspace Directory: ${ctx.workspaceDir ?? 'not specified'}`);
48
68
  const conn = ctx.targetOrg.getConnection(ctx.apiVersion);
49
69
  const records = await this.loadRecords(conn, ctx.inputFile);
50
70
  if (records.length === 0) {
51
71
  this.log(`No ${this.recordLabel} rules to convert.`);
72
+ // Still emit an (empty) record-update manifest when we can name it, so automation always has a
73
+ // concrete file to consume. Without --cml-api there are no products to discover an API name
74
+ // from, so there is nothing to name the file after.
75
+ if (ctx.cmlApi) {
76
+ const safeApi = ctx.cmlApi.replace(/[^a-zA-Z0-9_-]/g, '_');
77
+ const recordUpdateFile = await this.writeRecordUpdateFile([], [], ctx.cmlApi, safeApi, workspaceDir);
78
+ return { cmlFile: '', associationsFile: '', ruleKeyMapping: [], recordUpdateFile };
79
+ }
52
80
  return { cmlFile: '', associationsFile: '', ruleKeyMapping: [] };
53
81
  }
54
82
  const ruleDefs = this.parseAllRecords(records);
55
- const productIdToCode = await this.resolveProductCodes(conn, ruleDefs);
83
+ const { productIdToCode, productIdToName } = await this.resolveProductCodes(conn, ruleDefs);
56
84
  const api = ctx.cmlApi ?? (await this.discoverCmlApi(conn, ruleDefs, productIdToCode));
57
85
  const safeApi = api.replace(/[^a-zA-Z0-9_-]/g, '_');
58
- const { cmlModel, ruleKeyMapping } = buildCmlModel(ruleDefs, productIdToCode, this.keyPrefix, this.constraintLabel);
59
- ruleKeyMapping.forEach((m) => this.log(` -> ${m.name} => ${m.ruleKey}`));
60
- if (ctx.updateRecords) {
61
- await this.updateOrgRecords(records, ruleKeyMapping, conn);
86
+ if (ctx.mergeWithOrg) {
87
+ return this.runMergeConvert(ctx, conn, records, ruleDefs, productIdToCode, api, safeApi, workspaceDir);
62
88
  }
63
- return this.writeOutputFiles(cmlModel, ruleKeyMapping, safeApi, workspaceDir, api);
89
+ const { cmlModel, ruleKeyMapping } = buildCmlModel(ruleDefs, productIdToCode, this.keyPrefix, this.constraintLabel, this.ruleType, productIdToName);
90
+ ruleKeyMapping.forEach((m) => this.log(` -> ${m.name} => ${m.ruleKey}`));
91
+ const recordUpdateFile = await this.writeRecordUpdateFile(records, ruleKeyMapping, api, safeApi, workspaceDir);
92
+ return this.writeOutputFiles(cmlModel, ruleKeyMapping, safeApi, workspaceDir, api, recordUpdateFile);
93
+ }
94
+ /**
95
+ * Merge-mode entry point. The default errors because merging only makes sense for rule-statement
96
+ * subclasses (surcharge); constraint-form subclasses (underwriting) leave it unimplemented.
97
+ */
98
+ /* eslint-disable @typescript-eslint/no-unused-vars */
99
+ runMergeConvert(ctx, conn, records, ruleDefs, productIdToCode, api, safeApi, workspaceDir) {
100
+ this.error(`Merge mode is not supported for ${this.recordLabel} rules.`);
101
+ }
102
+ /* eslint-enable @typescript-eslint/no-unused-vars */
103
+ /**
104
+ * Writes the merged outputs: the full merged CML, a header-only associations CSV (merge mode
105
+ * touches only existing type blocks, so no new Type associations are needed — but the common
106
+ * import errors on an empty CSV, so the header line must be present), and the rule-key mapping.
107
+ */
108
+ async writeMergedOutputFiles(mergedCml, ruleKeyMapping, safeApi, workspaceDir, api, recordUpdateFile) {
109
+ // [Fix #15] Use path.join so emitted paths use the OS-native separator. Hardcoded '/' template
110
+ // literals produced forward-slash paths on Windows; tests that assert against path.join then
111
+ // diverged at the separator (`\` vs `/`). path.join keeps Linux unchanged ('/' verbatim) and
112
+ // emits backslashes on Windows, matching the test's normalized expectation.
113
+ const cmlPath = path.join(workspaceDir, `${safeApi}.cml`);
114
+ const associationsPath = path.join(workspaceDir, `${safeApi}_Associations.csv`);
115
+ const mappingPath = path.join(workspaceDir, `${safeApi}_RuleKeyMapping.json`);
116
+ await fs.writeFile(cmlPath, mergedCml, 'utf8');
117
+ await fs.writeFile(associationsPath, `${ASSOCIATIONS_CSV_HEADER}\n`, 'utf8');
118
+ await fs.writeFile(mappingPath, JSON.stringify(ruleKeyMapping, null, 2), 'utf8');
119
+ this.log(`\nMerged CML written to: ${cmlPath}`);
120
+ this.log(`Associations (header-only, no new type associations) written to: ${associationsPath}`);
121
+ this.log(`Rule key mapping written to: ${mappingPath}`);
122
+ this.log(`Record update file written to: ${recordUpdateFile}`);
123
+ this.log('\nNext steps (this command wrote nothing to the org):');
124
+ this.log(' 1. Review the merged .cml file (diff against the org model) and the record update file');
125
+ this.log(` 2. Activate the CML: sf cml import as-expression-set --cml-api ${api} --context-definition <CD_NAME> --target-org <org>`);
126
+ this.log(` 3. Apply the org-record changes enumerated in ${recordUpdateFile} to the target org`);
127
+ return { cmlFile: cmlPath, associationsFile: associationsPath, ruleKeyMapping, recordUpdateFile };
128
+ }
129
+ /**
130
+ * Build the record-update plan envelope and write it to `<safeApi>_<kind>Update.json`. This is the
131
+ * ONLY way org-record changes leave convert now — convert never writes to the org itself. The file
132
+ * is a reviewable manifest the operator applies to the org separately; this plugin does not apply it.
133
+ */
134
+ async writeRecordUpdateFile(records, ruleKeyMapping, api, safeApi, workspaceDir) {
135
+ const plan = {
136
+ schemaVersion: 1,
137
+ kind: this.recordUpdateKind,
138
+ cmlApi: api,
139
+ generatedAt: new Date().toISOString(),
140
+ updates: this.buildRecordUpdates(records, ruleKeyMapping),
141
+ };
142
+ // [Fix #15] path.join for cross-platform path separators (was hardcoded '/'). Note: this local
143
+ // is intentionally named `filePath` rather than `path` to avoid shadowing the `path` module import.
144
+ const filePath = path.join(workspaceDir, `${safeApi}_${RECORD_UPDATE_FILE_SUFFIX[this.recordUpdateKind]}.json`);
145
+ await fs.writeFile(filePath, JSON.stringify(plan, null, 2), 'utf8');
146
+ return filePath;
64
147
  }
65
148
  async loadRecords(conn, inputFile) {
66
149
  if (inputFile) {
67
150
  this.log(`Reading ${this.recordLabel} records from file: ${inputFile}`);
68
151
  const contents = await fs.readFile(inputFile, 'utf8');
69
- return JSON.parse(contents);
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(contents);
155
+ }
156
+ catch (e) {
157
+ throw new Error(`${inputFile}: not valid JSON (${e.message})`);
158
+ }
159
+ // [Fix #6] The file is operator-supplied input that drives ProductPath splits, SOQL id
160
+ // resolution, and rule-key derivation. A non-array root, or records with missing/non-string
161
+ // Id / Name / ProductPath, would otherwise propagate `undefined`s through the pipeline and
162
+ // surface as confusing downstream failures (or silently malformed output). Refuse upfront.
163
+ if (!Array.isArray(parsed)) {
164
+ throw new Error(`${inputFile}: expected a top-level JSON array of ${this.recordLabel} records`);
165
+ }
166
+ parsed.forEach((entry, index) => {
167
+ if (entry === null || typeof entry !== 'object') {
168
+ throw new Error(`${inputFile}: record #${index} is not an object`);
169
+ }
170
+ const rec = entry;
171
+ for (const field of ['Id', 'Name', 'ProductPath']) {
172
+ const value = rec[field];
173
+ if (typeof value !== 'string' || value.length === 0) {
174
+ throw new Error(`${inputFile}: record #${index} has missing or non-string ${field}`);
175
+ }
176
+ }
177
+ });
178
+ return parsed;
70
179
  }
71
180
  this.log(`Querying ${this.recordLabel} records from org...`);
72
181
  const result = await conn.query(this.soql);
@@ -90,13 +199,71 @@ export class InsuranceRuleConvertCommand extends SfCommand {
90
199
  return parsed;
91
200
  }
92
201
  async resolveProductCodes(conn, ruleDefs) {
93
- const productIds = collectRootProductIds(ruleDefs);
202
+ // Collect EVERY ProductPath segment, not just the root: merge mode needs the code of each
203
+ // segment to build the platform-compatible pathed rule key, and the leaf segment's code to
204
+ // resolve its CML type. The non-merge path only reads the root code, so this is a superset.
205
+ const productIds = collectAllProductIds(ruleDefs);
206
+ const productIdToName = new Map();
94
207
  try {
95
- return await fetchProductCodes(conn, productIds);
208
+ const productIdToCode = await fetchProductCodes(conn, productIds, {
209
+ // M2: a blank ProductCode forces a Name/Id fallback, so the pathed rule key is derived from a
210
+ // non-authoritative field and will NOT match the platform-generated RuleKey — the surcharge
211
+ // then imports cleanly but silently never fires. Surface it instead of substituting silently.
212
+ onFallback: (productId) => this.warn(`Product ${productId} has no ProductCode; rule key derived from Name/Id and may not match the platform-generated RuleKey (surcharge may silently not fire)`),
213
+ // Capture Names so convert can emit them as the Type association reference value — the field
214
+ // the common importer resolves the Product2 by. Validated below before they reach the model.
215
+ collectNames: productIdToName,
216
+ });
217
+ // [Fix #8] An id we ASKED Product2 about and got nothing back for is distinct from a found
218
+ // product with a null ProductCode (which `onFallback` already covers). Missing-from-result
219
+ // ids mean the Product2 row is invalid / deleted / outside the visible scope — generation
220
+ // will silently fall back to the raw Id for that path segment, again yielding a non-matching
221
+ // platform RuleKey. Surface this case explicitly so the operator can investigate.
222
+ for (const id of productIds) {
223
+ if (!productIdToCode.has(id)) {
224
+ this.warn(`Product ${id} was not returned by Product2 query (not found, not visible, or filtered out); ` +
225
+ 'rule key for paths through this product will fall back to the raw Id and may not match the platform-generated RuleKey');
226
+ }
227
+ }
228
+ this.validateAssociationNames(productIdToName);
229
+ return { productIdToCode, productIdToName };
96
230
  }
97
231
  catch (e) {
98
232
  this.warn(`Could not fetch product codes: ${e.message}. Using product IDs instead.`);
99
- return new Map();
233
+ return { productIdToCode: new Map(), productIdToName: new Map() };
234
+ }
235
+ }
236
+ /**
237
+ * Validates the Id -> Name map that feeds each Type association's reference value, mutating it in
238
+ * place so only safe, importer-resolvable Names survive. The Name is written into the naive
239
+ * comma-joined associations CSV and, downstream, into the common importer's single-quoted SOQL
240
+ * `WHERE Name IN ('<Name>')`. Two hazards, neither of which we can fix in the (Rev-Cloud-owned)
241
+ * importer. Unsafe characters (comma, quote, backslash, newline) corrupt the CSV column or break
242
+ * out of the SOQL literal — such a Name is dropped from the map (the generator falls back to the
243
+ * ProductCode) and warned, so the association may not resolve but the output stays valid and
244
+ * injection-free instead of silently wrong. Duplicate Names across distinct product ids make the
245
+ * importer's by-Name lookup ambiguous (last-writer-wins), so a Type could bind to the wrong
246
+ * Product2 — we warn but keep the entries; the documented precondition is that Product Name is
247
+ * unique per migration scope.
248
+ */
249
+ validateAssociationNames(productIdToName) {
250
+ const nameToIds = new Map();
251
+ for (const [productId, name] of productIdToName) {
252
+ if (!isSafeAssociationReferenceValue(name)) {
253
+ this.warn(`Product ${productId} Name "${name}" contains characters unsafe for the associations CSV / import SOQL; ` +
254
+ 'falling back to ProductCode for its Type association (the association may not resolve on import)');
255
+ productIdToName.delete(productId);
256
+ continue;
257
+ }
258
+ const ids = nameToIds.get(name) ?? [];
259
+ ids.push(productId);
260
+ nameToIds.set(name, ids);
261
+ }
262
+ for (const [name, ids] of nameToIds) {
263
+ if (ids.length > 1) {
264
+ this.warn(`Product Name "${name}" is shared by ${ids.length} products (${ids.join(', ')}); ` +
265
+ 'the import resolves Type associations by Name, so bindings may be ambiguous. Product Name must be unique.');
266
+ }
100
267
  }
101
268
  }
102
269
  async discoverCmlApi(conn, ruleDefs, productIdToCode) {
@@ -111,33 +278,57 @@ export class InsuranceRuleConvertCommand extends SfCommand {
111
278
  catch (e) {
112
279
  this.warn(`CML auto-discovery failed: ${e.message}`);
113
280
  }
114
- const codes = Array.from(productIds).map((id) => productIdToCode.get(id) ?? id);
281
+ // [Fix #5] The fallback API name is downstream written to disk as `<safeApi>.cml` and used in
282
+ // SOQL/log lines. Raw ProductCodes can contain spaces, slashes, quotes, or other characters
283
+ // that break path/SOQL semantics; sanitize each segment to the same `[A-Za-z0-9_]` alphabet
284
+ // the rest of the generator uses so the generated name is injection-/path-traversal-safe.
285
+ const codes = Array.from(productIds).map((id) => sanitizeName(productIdToCode.get(id) ?? id));
115
286
  const generated = `${this.apiNamePrefix}${codes.join('_')}`;
116
287
  this.log(`No existing CML found. Generated new CML API name: ${generated}`);
117
288
  return generated;
118
289
  }
119
- async writeOutputFiles(cmlModel, ruleKeyMapping, safeApi, workspaceDir, api) {
120
- const cmlPath = `${workspaceDir}/${safeApi}.cml`;
121
- const associationsPath = `${workspaceDir}/${safeApi}_Associations.csv`;
122
- const mappingPath = `${workspaceDir}/${safeApi}_RuleKeyMapping.json`;
290
+ async writeOutputFiles(cmlModel, ruleKeyMapping, safeApi, workspaceDir, api, recordUpdateFile) {
291
+ // [Fix #15] path.join for cross-platform path separators (was hardcoded '/'). See companion site
292
+ // in writeMergedOutputFiles — same rationale: Windows-unit-tests assert against path.join, and
293
+ // forward-slash literals broke the equality there.
294
+ const cmlPath = path.join(workspaceDir, `${safeApi}.cml`);
295
+ const associationsPath = path.join(workspaceDir, `${safeApi}_Associations.csv`);
296
+ const mappingPath = path.join(workspaceDir, `${safeApi}_RuleKeyMapping.json`);
123
297
  await fs.writeFile(cmlPath, cmlModel.generateCml(), 'utf8');
124
298
  await fs.writeFile(associationsPath, generateCsvForAssociations(safeApi, cmlModel.associations), 'utf8');
125
299
  await fs.writeFile(mappingPath, JSON.stringify(ruleKeyMapping, null, 2), 'utf8');
126
300
  this.log(`\nCML written to: ${cmlPath}`);
127
301
  this.log(`Associations written to: ${associationsPath}`);
128
302
  this.log(`Rule key mapping written to: ${mappingPath}`);
303
+ this.log(`Record update file written to: ${recordUpdateFile}`);
129
304
  this.log(`\nConverted ${ruleKeyMapping.length} ${this.recordLabel} rules to CML`);
130
- this.log('\nNext steps:');
131
- this.log(' 1. Review the generated .cml file');
132
- this.log(` 2. Import: sf cml import as-expression-set --cml-api ${api} --context-definition <CD_NAME> --target-org <org>`);
133
- this.log(' 3. Update records with RuleKey from mapping file');
134
- return { cmlFile: cmlPath, associationsFile: associationsPath, ruleKeyMapping };
305
+ this.log('\nNext steps (this command wrote nothing to the org):');
306
+ this.log(' 1. Review the generated .cml file and the record update file');
307
+ this.log(` 2. Activate the CML: sf cml import as-expression-set --cml-api ${api} --context-definition <CD_NAME> --target-org <org>`);
308
+ this.log(` 3. Apply the org-record changes enumerated in ${recordUpdateFile} to the target org`);
309
+ return { cmlFile: cmlPath, associationsFile: associationsPath, ruleKeyMapping, recordUpdateFile };
135
310
  }
136
311
  }
312
+ function collectAllProductIds(ruleDefs) {
313
+ const productIds = new Set();
314
+ for (const { record } of ruleDefs) {
315
+ // [Fix #11] One canonical split helper so trim + drop-empty semantics never diverge from the
316
+ // merge module's parsing.
317
+ for (const id of splitProductPath(record.ProductPath))
318
+ productIds.add(id);
319
+ }
320
+ return productIds;
321
+ }
322
+ // CML auto-discovery and the generated API name key off the ROOT product of each path only, so
323
+ // this stays a root-only collector (the merge path uses collectAllProductIds for the full set).
137
324
  function collectRootProductIds(ruleDefs) {
138
325
  const productIds = new Set();
139
326
  for (const { record } of ruleDefs) {
140
- productIds.add(record.ProductPath.split('/')[0]);
327
+ // [Fix #11] splitProductPath already trims and drops empty segments, so the root is the first
328
+ // emitted id (or undefined when the entire path is blank).
329
+ const root = splitProductPath(record.ProductPath)[0];
330
+ if (root)
331
+ productIds.add(root);
141
332
  }
142
333
  return productIds;
143
334
  }
@@ -1 +1 @@
1
- {"version":3,"file":"insurance-rule-convert-command.js","sourceRoot":"","sources":["../../../src/shared/insurance/insurance-rule-convert-command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"insurance-rule-convert-command.js","sourceRoot":"","sources":["../../../src/shared/insurance/insurance-rule-convert-command.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,18 @@ export declare function sanitizeName(name: string): string;
9
9
  export declare function decodeHtmlEntities(value: string): string;
10
10
  export declare function buildStageTransition(ruleGroup?: UnderwritingRuleGroup): string | undefined;
11
11
  export declare function generateRuleKey(prefix: string, productCode: string, apiName: string, stageTransition?: string): string;
12
+ /**
13
+ * Whether a product Name is safe to emit as a Type association's reference value
14
+ * (`$Product2ReferenceId`). That value travels two unescaped hops we do NOT own and cannot change:
15
+ * the naive comma-joined `_Associations.csv` column (a comma shifts every later column), and the
16
+ * common `cml import as-expression-set` resolver's single-quoted SOQL `WHERE Name IN ('<value>')`
17
+ * (a single quote breaks out of the literal; a backslash or newline corrupts the row).
18
+ * A Name failing this guard is dropped by the convert layer (it falls back to the ProductCode and
19
+ * warns) rather than silently producing a corrupt CSV / injecting SOQL. An empty / whitespace-only
20
+ * Name is also rejected — it can never match a real Product2 by name. Mirrors the reject-don't-escape
21
+ * stance of {@link isSafeQuotableString} for condition values.
22
+ */
23
+ export declare function isSafeAssociationReferenceValue(value: string): boolean;
12
24
  export declare function buildConstraintDeclaration(ruleDef: {
13
25
  ruleCriteria?: RuleCriteria[];
14
26
  }): string;
@@ -17,10 +29,35 @@ export declare function collectAttributes(ruleDefs: Array<{
17
29
  ruleCriteria?: RuleCriteria[];
18
30
  };
19
31
  }>): Set<string>;
32
+ /**
33
+ * Collects only the attributes that actually reach the emitted CML — i.e. those on conditions whose
34
+ * `buildConditionExpression` returned a non-null expression. Conditions dropped by the safe-literal
35
+ * guard, an unknown operator, or missing values do NOT contribute their attribute. This is the
36
+ * companion to `collectAttributes` for merge-mode attribute-presence warnings: warning about an
37
+ * attribute the declaration never emitted (because the guard dropped its condition) is spurious
38
+ * noise on exactly the inputs the guard sanitized. Unlike `collectAttributes`, names are returned
39
+ * sanitized to match how they appear in the emitted declaration.
40
+ */
41
+ export declare function collectEmittedAttributes(ruleDefs: Array<{
42
+ ruleDef: {
43
+ ruleCriteria?: RuleCriteria[];
44
+ };
45
+ }>): Set<string>;
46
+ /**
47
+ * Derives the real CML data type for each collected attribute from its condition `dataType`.
48
+ * When an attribute appears with more than one distinct CML type (a conflict), or its type is
49
+ * unknown, it falls back to STRING. Returned alongside (not replacing) the Set from
50
+ * collectAttributes, which merge mode still depends on.
51
+ */
52
+ export declare function collectAttributeTypes(ruleDefs: Array<{
53
+ ruleDef: {
54
+ ruleCriteria?: RuleCriteria[];
55
+ };
56
+ }>): Map<string, string>;
20
57
  export declare function buildCmlModel(ruleDefs: Array<{
21
58
  record: RuleRecord;
22
59
  ruleDef: ParsedRuleDefinition;
23
- }>, productIdToCode: Map<string, string>, keyPrefix: string, constraintLabel: string): {
60
+ }>, productIdToCode: Map<string, string>, keyPrefix: string, constraintLabel: string, ruleType?: string, productIdToName?: Map<string, string>): {
24
61
  cmlModel: CmlModel;
25
62
  ruleKeyMapping: RuleKeyEntry[];
26
63
  };