versioned-d.ts-tools 0.4.2 → 0.6.0

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.
package/README.md CHANGED
@@ -90,16 +90,66 @@ You can provide an optional JSON configuration file to specify additional text r
90
90
  Compares two TypeScript definition files and generates a markdown report of the differences.
91
91
 
92
92
  ```bash
93
- whats-new [new d.ts] [old d.ts] [output file name (minus extension)] [relative path]
93
+ whats-new [new d.ts] [old d.ts] [output file name (minus extension)] [relative path] [config file (optional)]
94
94
  ```
95
95
 
96
- **Example:**
96
+ **Examples:**
97
97
 
98
98
  ```bash
99
+ # Basic usage
99
100
  whats-new excel_1_9.d.ts excel_1_8.d.ts excel_whats_new javascript/api/excel/excel.
101
+
102
+ # With configuration file for custom link patterns
103
+ whats-new office-scripts.d.ts office-scripts-old.d.ts office-scripts-whats-new javascript/api/office-scripts/ my-link-config.json
100
104
  ```
101
105
 
102
- This generates a `excel_whats_new.md` file containing a markdown table showing what's new between the two versions.
106
+ This generates a markdown file (e.g., `excel_whats_new.md`) containing a table showing what's new between the two versions.
107
+
108
+ #### Link Configuration File
109
+
110
+ You can provide an optional JSON configuration file to customize how URLs are generated for different API types using flexible templates with placeholders.
111
+
112
+ **Configuration file format:**
113
+
114
+ ```json
115
+ {
116
+ "linkConfigs": [
117
+ {
118
+ "name": "Office Scripts",
119
+ "pathPattern": "office-scripts",
120
+ "description": "Special URL pattern for Office Scripts",
121
+ "globalFunctionTemplate": "/{basePath}#officescript-officescript-{fieldName}-function(1)",
122
+ "classTemplate": "/{basePath}/officescript.{className}",
123
+ "classMemberTemplate": "/{basePath}/officescript.{className}#officescript-officescript-{className}-{fieldName}{suffix}"
124
+ },
125
+ {
126
+ "name": "Standard Office Applications",
127
+ "pathPattern": ".*",
128
+ "description": "Standard URL pattern for Excel, Word, PowerPoint, Outlook",
129
+ "globalFunctionTemplate": "/{basePath}#{hostName}-{fileName}-{fieldName}-function(1)",
130
+ "classTemplate": "/{basePath}.{className}",
131
+ "classMemberTemplate": "/{basePath}.{className}#{hostName}-{fileName}-{className}-{fieldName}{suffix}"
132
+ }
133
+ ]
134
+ }
135
+ ```
136
+
137
+ **Available Template Placeholders:**
138
+ - `{basePath}` - relativePath without the trailing dot (e.g., 'javascript/api/excel/excel')
139
+ - `{hostName}` - extracted host name (e.g., 'excel', 'office-scripts')
140
+ - `{fileName}` - extracted file name (e.g., 'excel', 'officescript')
141
+ - `{className}` - class name in lowercase
142
+ - `{fieldName}` - field/method name in lowercase
143
+ - `{suffix}` - '-member(1)' for methods/functions, '-member' for properties
144
+
145
+ **Template Types:**
146
+ - `globalFunctionTemplate` - For namespace-level functions (global functions)
147
+ - `classTemplate` - For class/interface URLs in the first column
148
+ - `classMemberTemplate` - For class members (methods, properties, events)
149
+
150
+ When a configuration file is provided, the tool will use the appropriate URL template based on the pattern matching. The first matching `pathPattern` (regex) wins.
151
+
152
+ See `example-link-config.json` for a complete example.
103
153
 
104
154
  ## Programmatic Usage
105
155
 
@@ -1,3 +1,10 @@
1
+ export interface LinkConfig {
2
+ pathPattern: string;
3
+ globalFunctionTemplate: string;
4
+ classTemplate: string;
5
+ classMemberTemplate: string;
6
+ }
7
+ export declare const DEFAULT_LINK_CONFIGS: LinkConfig[];
1
8
  declare enum ClassType {
2
9
  Class = "Class",
3
10
  Interface = "Interface",
@@ -35,7 +42,7 @@ export declare class APISet {
35
42
  containsField(clas: ClassStruct, field: FieldStruct): boolean;
36
43
  diff(other: APISet): APISet;
37
44
  getAsDTS(): string;
38
- getAsMarkdown(relativePath: string): string;
45
+ getAsMarkdown(relativePath: string, linkConfigs?: LinkConfig[]): string;
39
46
  sort(): void;
40
47
  }
41
48
  export declare function parseDTS(fileName: string, fileContents: string): APISet;
@@ -33,9 +33,53 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.APISet = void 0;
36
+ exports.APISet = exports.DEFAULT_LINK_CONFIGS = void 0;
37
37
  exports.parseDTS = parseDTS;
38
38
  const ts = __importStar(require("typescript"));
39
+ // Available placeholders:
40
+ // {basePath} - relativePath without the trailing dot
41
+ // {hostName} - extracted host name (e.g., "excel", "office-scripts")
42
+ // {fileName} - extracted file name (e.g., "excel", "officescript")
43
+ // {className} - class name in lowercase
44
+ // {fieldName} - field/method name in lowercase
45
+ // {suffix} - "-member(1)" for methods/functions, "-member" for properties
46
+ // Default link builder using templates
47
+ function buildLinkFromTemplate(template, relativePath, className, field) {
48
+ const basePath = relativePath.substring(0, relativePath.lastIndexOf("."));
49
+ const hostName = relativePath.includes("/api/")
50
+ ? relativePath.substring(relativePath.indexOf("/api/") + 5, relativePath.lastIndexOf("/"))
51
+ : "";
52
+ const fileName = relativePath.substring(relativePath.lastIndexOf("/") + 1, relativePath.lastIndexOf("."));
53
+ let result = template
54
+ .replace(/{basePath}/g, basePath)
55
+ .replace(/{hostName}/g, hostName)
56
+ .replace(/{fileName}/g, fileName);
57
+ if (className && className !== "<global>") {
58
+ result = result.replace(/{className}/g, className.toLowerCase());
59
+ }
60
+ if (field) {
61
+ const suffix = (field.type === FieldType.Method || field.type === FieldType.Function) ? "-member(1)" : "-member";
62
+ result = result
63
+ .replace(/{fieldName}/g, field.name.toLowerCase())
64
+ .replace(/{suffix}/g, suffix);
65
+ }
66
+ return result;
67
+ }
68
+ // Default configurations for known Office applications
69
+ exports.DEFAULT_LINK_CONFIGS = [
70
+ {
71
+ pathPattern: "office-scripts",
72
+ globalFunctionTemplate: "/{basePath}#officescript-officescript-{fieldName}-function(1)",
73
+ classTemplate: "/{basePath}/officescript.{className}",
74
+ classMemberTemplate: "/{basePath}/officescript.{className}#officescript-officescript-{className}-{fieldName}{suffix}"
75
+ },
76
+ {
77
+ pathPattern: ".*", // matches any path as fallback
78
+ globalFunctionTemplate: "/{basePath}#{hostName}-{fileName}-{fieldName}-function(1)",
79
+ classTemplate: "/{basePath}.{className}",
80
+ classMemberTemplate: "/{basePath}.{className}#{hostName}-{fileName}-{className}-{fieldName}{suffix}"
81
+ }
82
+ ];
39
83
  // capturing these because of eccentricities with the compiler ordering
40
84
  let topClass = null;
41
85
  let lastItem = null;
@@ -156,7 +200,7 @@ class APISet {
156
200
  });
157
201
  return output.join("\n");
158
202
  }
159
- getAsMarkdown(relativePath) {
203
+ getAsMarkdown(relativePath, linkConfigs = exports.DEFAULT_LINK_CONFIGS) {
160
204
  this.sort();
161
205
  // table header
162
206
  let output = "| Class | Fields | Description |\n|:---|:---|:---|\n";
@@ -174,8 +218,8 @@ class APISet {
174
218
  output += "|*global*|";
175
219
  }
176
220
  else {
177
- output += "|[" + className + "](/"
178
- + relativePath + className.toLowerCase() + ")|";
221
+ output += "|[" + className + "]("
222
+ + buildClassLink(relativePath, className, linkConfigs) + ")|";
179
223
  }
180
224
  // Ignore the following:
181
225
  // - String literal overloads.
@@ -226,7 +270,7 @@ class APISet {
226
270
  newItemText = newItemText.replace(/[\s][\s]+/g, " ").replace(/\( /g, "(").replace(/ \)/g, ")").replace(/,\)/g, ")").replace(/([\w]\??: )\\\| /g, "$1"); // dprint formatting quirks
227
271
  newItemText = newItemText.replace(/\<any\>/g, "");
228
272
  let tableLine = "[" + newItemText + "]("
229
- + buildFieldLink(relativePath, className, field) + ")|";
273
+ + buildFieldLink(relativePath, className, field, linkConfigs) + ")|";
230
274
  tableLine += removeAtLink(extractFirstSentenceFromComment(field.comment));
231
275
  output += tableLine + "|\n";
232
276
  });
@@ -277,20 +321,21 @@ function removeAtLink(commentText) {
277
321
  commentText = commentText.replace(/{@link ([^}]*?) \| (http.*?)}/gm, "[$1]($2)");
278
322
  return commentText;
279
323
  }
280
- function buildFieldLink(relativePath, className, field) {
281
- // Special handling for global functions - use a different link format
282
- if (className === "<global>") {
283
- let hostName = relativePath.substring(relativePath.indexOf("/api/") + 5, relativePath.lastIndexOf("/"));
284
- let fileName = relativePath.substring(relativePath.lastIndexOf("/") + 1, relativePath.lastIndexOf("."));
285
- let anchorPrefix = hostName + "-" + fileName + "-";
286
- return "/" + relativePath.substring(0, relativePath.lastIndexOf(".")) + "#" + anchorPrefix + field.name.toLowerCase() + "-function(1)";
324
+ function buildClassLink(relativePath, className, linkConfigs = exports.DEFAULT_LINK_CONFIGS) {
325
+ // Find the first matching configuration
326
+ const config = linkConfigs.find(config => new RegExp(config.pathPattern).test(relativePath)) || exports.DEFAULT_LINK_CONFIGS[exports.DEFAULT_LINK_CONFIGS.length - 1]; // fallback to last config
327
+ return buildLinkFromTemplate(config.classTemplate, relativePath, className);
328
+ }
329
+ function buildFieldLink(relativePath, className, field, linkConfigs = exports.DEFAULT_LINK_CONFIGS) {
330
+ // Find the first matching configuration
331
+ const config = linkConfigs.find(config => new RegExp(config.pathPattern).test(relativePath)) || exports.DEFAULT_LINK_CONFIGS[exports.DEFAULT_LINK_CONFIGS.length - 1]; // fallback to last config
332
+ // Use appropriate template based on whether it's a global function or class member
333
+ if (className.trim() === "<global>") {
334
+ return buildLinkFromTemplate(config.globalFunctionTemplate, relativePath, className, field);
335
+ }
336
+ else {
337
+ return buildLinkFromTemplate(config.classMemberTemplate, relativePath, className, field);
287
338
  }
288
- // Build the standard link anchor format based on host.
289
- let hostName = relativePath.substring(relativePath.indexOf("/api/") + 5, relativePath.lastIndexOf("/"));
290
- let fileName = relativePath.substring(relativePath.lastIndexOf("/") + 1, relativePath.lastIndexOf("."));
291
- let anchorPrefix = hostName + "-" + fileName + "-";
292
- let fieldLink = "/" + relativePath + className.toLowerCase() + "#" + anchorPrefix + className.toLowerCase() + "-" + field.name.toLowerCase() + ((field.type === FieldType.Method || field.type === FieldType.Function) ? "-member(1)" : "-member");
293
- return fieldLink;
294
339
  }
295
340
  function parseDTS(fileName, fileContents) {
296
341
  // Reset global state for new parse
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export declare function generateWhatsNew(newDtsPath: string, oldDtsPath: string, outputPath: string, relativePath: string): void;
2
+ import { LinkConfig } from './dts-utilities';
3
+ export declare function generateWhatsNew(newDtsPath: string, oldDtsPath: string, outputPath: string, relativePath: string, linkConfigs?: LinkConfig[], configFilePath?: string): void;
package/dist/whats-new.js CHANGED
@@ -37,7 +37,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.generateWhatsNew = generateWhatsNew;
38
38
  const fsx = __importStar(require("fs-extra"));
39
39
  const dts_utilities_1 = require("./dts-utilities");
40
- function generateWhatsNew(newDtsPath, oldDtsPath, outputPath, relativePath) {
40
+ /**
41
+ * Loads link configuration from a JSON file
42
+ * @param configFilePath Path to the configuration file
43
+ * @returns Array of LinkConfig objects
44
+ */
45
+ function loadLinkConfig(configFilePath) {
46
+ try {
47
+ const configContent = fsx.readFileSync(configFilePath, 'utf8');
48
+ const config = JSON.parse(configContent);
49
+ // Validate that the config has the required template properties
50
+ return config.linkConfigs.map((item) => {
51
+ if (!item.globalFunctionTemplate || !item.classTemplate || !item.classMemberTemplate) {
52
+ throw new Error(`Configuration item missing required template properties: ${JSON.stringify(item)}`);
53
+ }
54
+ return {
55
+ pathPattern: item.pathPattern,
56
+ globalFunctionTemplate: item.globalFunctionTemplate,
57
+ classTemplate: item.classTemplate,
58
+ classMemberTemplate: item.classMemberTemplate
59
+ };
60
+ });
61
+ }
62
+ catch (error) {
63
+ console.warn(`Could not load config file ${configFilePath}: ${error}. Using default configuration.`);
64
+ return [];
65
+ }
66
+ }
67
+ function generateWhatsNew(newDtsPath, oldDtsPath, outputPath, relativePath, linkConfigs, configFilePath) {
68
+ // Load configuration from file if provided
69
+ let effectiveLinkConfigs = linkConfigs;
70
+ if (configFilePath && !linkConfigs) {
71
+ effectiveLinkConfigs = loadLinkConfig(configFilePath);
72
+ }
41
73
  // read whole files
42
74
  let wholeRelease = fsx.readFileSync(oldDtsPath).toString();
43
75
  let wholePreview = fsx.readFileSync(newDtsPath).toString();
@@ -47,22 +79,27 @@ function generateWhatsNew(newDtsPath, oldDtsPath, outputPath, relativePath) {
47
79
  if (!fsx.existsSync(outputPath + ".md")) {
48
80
  fsx.createFileSync(outputPath + ".md");
49
81
  }
50
- fsx.writeFileSync(outputPath + ".md", diffAPI.getAsMarkdown(relativePath));
82
+ fsx.writeFileSync(outputPath + ".md", diffAPI.getAsMarkdown(relativePath, effectiveLinkConfigs));
51
83
  }
52
84
  // CLI entry point
53
85
  if (require.main === module) {
54
- if (process.argv.length !== 6 || process.argv.find((x) => { return x === "-?"; })) {
55
- console.log("usage: whats-new [new d.ts] [old d.ts] [output file name (minus extension)] [relative path]");
86
+ if (process.argv.length < 6 || process.argv.length > 7 || process.argv.find((x) => { return x === "-?"; })) {
87
+ console.log("usage: whats-new [new d.ts] [old d.ts] [output file name (minus extension)] [relative path] [config file (optional)]");
56
88
  console.log("example: whats-new excel_1_9.d.ts excel_1_8.d.ts excel_whats_new javascript/api/excel/excel.");
89
+ console.log("example with config: whats-new excel_1_9.d.ts excel_1_8.d.ts excel_whats_new javascript/api/excel/excel. my-config.json");
57
90
  process.exit(0);
58
91
  }
59
92
  const newDtsPath = process.argv[2];
60
93
  const oldDtsPath = process.argv[3];
61
94
  const outputPath = process.argv[4];
62
95
  const relativePath = process.argv[5];
96
+ const configFilePath = process.argv.length === 7 ? process.argv[6] : undefined;
63
97
  console.log(`What's New between ${newDtsPath} and ${oldDtsPath}?`);
98
+ if (configFilePath) {
99
+ console.log(`Using configuration file: ${configFilePath}`);
100
+ }
64
101
  tryCatch(async () => {
65
- generateWhatsNew(newDtsPath, oldDtsPath, outputPath, relativePath);
102
+ generateWhatsNew(newDtsPath, oldDtsPath, outputPath, relativePath, undefined, configFilePath);
66
103
  });
67
104
  }
68
105
  async function tryCatch(call) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versioned-d.ts-tools",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Tools for managing versioned TypeScript definition files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {