@absolutejs/changelog 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // @bun
2
+ // src/apply.ts
3
+ import ts from "typescript";
4
+
2
5
  // src/types.ts
3
6
  var CHANGE_KINDS = [
4
7
  "breaking",
@@ -19,141 +22,272 @@ var isDisruptive = (change) => change.kind === "breaking" || change.kind === "re
19
22
  var isMechanical = (migration) => !("manual" in migration);
20
23
 
21
24
  // src/apply.ts
22
- var IS_CODE = -1;
23
- var IMPORT = /(?:import|export)\s+(?:type\s+)?(?<clause>[\s\S]*?)\s*from\s*(?<quote>['"])(?<module>[^'"]+)\k<quote>/gu;
24
- var importsOf = (source) => {
25
+ var IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
26
+ var SPECIFIER = /^[@a-zA-Z0-9._~-][a-zA-Z0-9._~/-]*$/u;
27
+ var applyEdits = (source, edits) => [...edits].sort((left, right) => right.start - left.start).reduce((out, edit) => out.slice(0, edit.start) + edit.text + out.slice(edit.end), source);
28
+ var kindOf = (path) => /\.[cm]?ts$/u.test(path) ? ts.ScriptKind.TS : ts.ScriptKind.TSX;
29
+ var parse = (source, path) => ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, kindOf(path));
30
+ var belongsTo = (specifier, name) => specifier === name || specifier.startsWith(`${name}/`);
31
+ var moduleTextOf = (statement) => statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : undefined;
32
+ var portsOf = (file) => {
25
33
  const found = [];
26
- for (const match of source.matchAll(IMPORT)) {
27
- const { clause, module } = match.groups ?? {};
28
- if (module === undefined || match.index === undefined)
34
+ for (const statement of file.statements) {
35
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
29
36
  continue;
30
- const braced = clause === undefined ? undefined : /\{([\s\S]*)\}/u.exec(clause);
31
- found.push({
32
- clause: braced?.[1],
33
- end: match.index + match[0].length,
34
- full: match[0],
35
- module,
36
- start: match.index
37
- });
37
+ const module = moduleTextOf(statement);
38
+ if (module !== undefined)
39
+ found.push({ module, statement });
38
40
  }
39
41
  return found;
40
42
  };
41
- var belongsTo = (specifier, name) => specifier === name || specifier.startsWith(`${name}/`);
42
- var skipEnd = (source, index) => {
43
- const character = source[index];
44
- const next = source[index + 1];
45
- if (character === "/" && next === "/") {
46
- const line = source.indexOf(`
47
- `, index);
48
- return line < 0 ? source.length : line;
49
- }
50
- if (character === "/" && next === "*") {
51
- const close = source.indexOf("*/", index + 2);
52
- return close < 0 ? source.length : close + 2;
53
- }
54
- if (character !== "'" && character !== '"' && character !== "`")
55
- return IS_CODE;
56
- let cursor = index + 1;
57
- while (cursor < source.length && source[cursor] !== character)
58
- cursor += source[cursor] === "\\" ? 2 : 1;
59
- return Math.min(cursor + 1, source.length);
60
- };
61
- var codeSpans = (source) => {
62
- const spans = [];
63
- let start = 0;
64
- let index = 0;
65
- const closeSpan = (ends, resumes) => {
66
- spans.push([start, ends]);
67
- start = resumes;
43
+ var namedElementsOf = (statement) => {
44
+ const clause = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : statement.exportClause;
45
+ if (clause === undefined)
46
+ return;
47
+ if (ts.isNamedImports(clause) || ts.isNamedExports(clause))
48
+ return clause;
49
+ return;
50
+ };
51
+ var exportedNameOf = (element) => (element.propertyName ?? element.name).text;
52
+ var declaresLocally = (file, name) => {
53
+ let found = false;
54
+ const visit = (node) => {
55
+ if (found)
56
+ return;
57
+ const declares = (ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) && node.name !== undefined && ts.isIdentifier(node.name) && node.name.text === name;
58
+ if (declares)
59
+ found = true;
60
+ else
61
+ ts.forEachChild(node, visit);
68
62
  };
69
- while (index < source.length) {
70
- const skipped = skipEnd(source, index);
71
- if (skipped > IS_CODE)
72
- closeSpan(index, skipped);
73
- index = skipped === IS_CODE ? index + 1 : skipped;
74
- }
75
- spans.push([start, source.length]);
76
- return spans;
77
- };
78
- var renameInCode = (source, from, into) => {
79
- const pattern = new RegExp(`(?<![.\\w$])${from.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}(?![\\w$])`, "gu");
80
- let out = "";
81
- let cursor = 0;
82
- for (const [start, end] of codeSpans(source)) {
83
- out += source.slice(cursor, start);
84
- out += source.slice(start, end).replace(pattern, into);
85
- cursor = end;
86
- }
87
- return out + source.slice(cursor);
88
- };
89
- var declaresLocally = (source, name) => new RegExp(`\\b(?:const|let|var|function|class|interface|type|enum)\\s+${name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\b`, "u").test(source);
90
- var applyRename = (source, packageName, rename) => {
91
- const statements = importsOf(source).filter((statement) => belongsTo(statement.module, packageName) && statement.clause !== undefined && new RegExp(`(?<![\\w$])${rename.from}(?![\\w$])`, "u").test(statement.clause));
92
- if (statements.length === 0)
93
- return { changed: false, notes: [], source };
94
- const aliased = statements.some((statement) => new RegExp(`(?<![\\w$])${rename.from}\\s+as\\s`, "u").test(statement.clause ?? ""));
95
- if (aliased) {
96
- let out = source;
97
- for (const statement of statements)
98
- out = out.replace(statement.full, statement.full.replace(new RegExp(`(?<![\\w$])${rename.from}(\\s+as\\s)`, "u"), `${rename.to}$1`));
99
- return { changed: true, notes: [], source: out };
100
- }
101
- if (declaresLocally(source, rename.from)) {
102
- let out = source;
103
- for (const statement of statements)
104
- out = out.replace(statement.full, statement.full.replace(new RegExp(`(?<![\\w$])${rename.from}(?![\\w$])`, "u"), `${rename.to} as ${rename.from}`));
63
+ ts.forEachChild(file, visit);
64
+ return found;
65
+ };
66
+ var isReference = (node) => {
67
+ const { parent } = node;
68
+ if (parent === undefined)
69
+ return false;
70
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node)
71
+ return false;
72
+ if (ts.isQualifiedName(parent) && parent.right === node)
73
+ return false;
74
+ if (ts.isPropertyAssignment(parent) && parent.name === node)
75
+ return false;
76
+ if (ts.isBindingElement(parent) && parent.propertyName === node)
77
+ return false;
78
+ if (ts.isJsxAttribute(parent) && parent.name === node)
79
+ return false;
80
+ if ((ts.isMethodDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isPropertySignature(parent) || ts.isMethodSignature(parent) || ts.isEnumMember(parent)) && parent.name === node)
81
+ return false;
82
+ if ((ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent)) && true)
83
+ return false;
84
+ return !ts.isDeclarationStatement(parent);
85
+ };
86
+ var referencesTo = (file, name) => {
87
+ const found = [];
88
+ const shorthand = [];
89
+ const visit = (node) => {
90
+ if (ts.isShorthandPropertyAssignment(node) && node.name.text === name)
91
+ shorthand.push(node);
92
+ else if (ts.isIdentifier(node) && node.text === name && isReference(node))
93
+ found.push(node);
94
+ ts.forEachChild(node, visit);
95
+ };
96
+ ts.forEachChild(file, visit);
97
+ return { references: found, shorthand };
98
+ };
99
+ var withoutElement = (elements, dropped, file) => {
100
+ const kept = elements.filter((element) => element !== dropped);
101
+ return kept.length === 0 ? undefined : kept.map((element) => element.getText(file)).join(", ");
102
+ };
103
+ var isTypeOnly = (statement, element) => {
104
+ if (element.isTypeOnly)
105
+ return true;
106
+ return ts.isImportDeclaration(statement) ? statement.importClause?.isTypeOnly === true : statement.isTypeOnly;
107
+ };
108
+ var quoteOf = (statement, file) => statement.moduleSpecifier?.getText(file).startsWith('"') === true ? '"' : "'";
109
+ var renameTargetsIn = (input) => portsOf(input.file).filter(({ module }) => belongsTo(module, input.packageName)).flatMap(({ statement }) => {
110
+ const clause = namedElementsOf(statement);
111
+ if (clause === undefined)
112
+ return [];
113
+ return clause.elements.filter((element) => exportedNameOf(element) === input.from).map((element) => ({ clause, element }));
114
+ });
115
+ var renameOneImport = (input) => {
116
+ const { clause, element } = input.target;
117
+ const { file, rename } = input;
118
+ const already = element.propertyName === undefined && clause.elements.some((other) => other !== element && exportedNameOf(other) === rename.to);
119
+ if (already) {
120
+ const kept = withoutElement(clause.elements, element, file);
105
121
  return {
106
- changed: true,
107
- notes: [
108
- `\`${rename.from}\` is also declared in this file, so the import was aliased instead of the uses being renamed`
122
+ edits: kept === undefined ? [] : [
123
+ {
124
+ end: clause.getEnd() - 1,
125
+ start: clause.getStart(file) + 1,
126
+ text: ` ${kept} `
127
+ }
128
+ ],
129
+ moves: true
130
+ };
131
+ }
132
+ if (element.propertyName !== undefined)
133
+ return {
134
+ edits: [
135
+ {
136
+ end: element.propertyName.getEnd(),
137
+ start: element.propertyName.getStart(file),
138
+ text: rename.to
139
+ }
109
140
  ],
110
- source: out
141
+ moves: false
142
+ };
143
+ const shadowed = declaresLocally(file, rename.from);
144
+ return {
145
+ edits: [
146
+ {
147
+ end: element.getEnd(),
148
+ start: element.getStart(file),
149
+ text: shadowed ? `${rename.to} as ${rename.from}` : rename.to
150
+ }
151
+ ],
152
+ moves: !shadowed,
153
+ ...shadowed ? {
154
+ note: `\`${rename.from}\` is also declared in this file, so the import was aliased instead of the uses being renamed`
155
+ } : {}
156
+ };
157
+ };
158
+ var applyRename = (input) => {
159
+ const { file, packageName, rename, source } = input;
160
+ if (!IDENTIFIER.test(rename.to))
161
+ return {
162
+ changed: false,
163
+ notes: [`\`${rename.to}\` is not a name, so this rename was not applied`],
164
+ source
111
165
  };
166
+ const done = renameTargetsIn({ file, from: rename.from, packageName }).map((target) => renameOneImport({ file, rename, target }));
167
+ const edits = done.flatMap((one) => one.edits);
168
+ if (edits.length === 0)
169
+ return { changed: false, notes: [], source };
170
+ if (done.some((one) => one.moves)) {
171
+ const { references, shorthand } = referencesTo(file, rename.from);
172
+ for (const reference of references)
173
+ edits.push({
174
+ end: reference.getEnd(),
175
+ start: reference.getStart(file),
176
+ text: rename.to
177
+ });
178
+ for (const property of shorthand)
179
+ edits.push({
180
+ end: property.getEnd(),
181
+ start: property.getStart(file),
182
+ text: `${rename.from}: ${rename.to}`
183
+ });
112
184
  }
113
185
  return {
114
186
  changed: true,
115
- notes: [],
116
- source: renameInCode(source, rename.from, rename.to)
187
+ notes: done.flatMap((one) => ("note" in one) && one.note ? [one.note] : []),
188
+ source: applyEdits(source, edits)
117
189
  };
118
190
  };
119
- var applyResubpath = (source, resubpath) => {
120
- const statements = importsOf(source).filter((statement) => statement.module === resubpath.from);
121
- if (statements.length === 0)
122
- return { changed: false, notes: [], source };
123
- let out = source;
124
- for (const statement of statements)
125
- out = out.replace(statement.full, statement.full.replace(resubpath.from, resubpath.to));
126
- return { changed: true, notes: [], source: out };
127
- };
128
- var applyMoved = (source, moved) => {
129
- const pattern = new RegExp(`(?<![\\w$])${moved.symbol}(?![\\w$])`, "u");
130
- const statements = importsOf(source).filter((statement) => statement.module === moved.from && statement.clause !== undefined && pattern.test(statement.clause));
131
- if (statements.length === 0)
191
+ var applyMoved = (input) => {
192
+ const { file, moved, source } = input;
193
+ if (!SPECIFIER.test(moved.to))
194
+ return {
195
+ changed: false,
196
+ notes: [
197
+ `\`${moved.to}\` is not a module specifier, so this move was not applied`
198
+ ],
199
+ source
200
+ };
201
+ const edits = [];
202
+ for (const { module, statement } of portsOf(file)) {
203
+ if (module !== moved.from)
204
+ continue;
205
+ const clause = namedElementsOf(statement);
206
+ if (clause === undefined)
207
+ continue;
208
+ const element = clause.elements.find((candidate) => exportedNameOf(candidate) === moved.symbol);
209
+ if (element === undefined)
210
+ continue;
211
+ const quote = quoteOf(statement, file);
212
+ const kept = withoutElement(clause.elements, element, file);
213
+ const carriesDefault = ts.isImportDeclaration(statement) && (statement.importClause?.name !== undefined || statement.importClause?.isTypeOnly === true);
214
+ const whole = kept === undefined && !carriesDefault && statement.moduleSpecifier;
215
+ if (whole)
216
+ edits.push({
217
+ end: whole.getEnd(),
218
+ start: whole.getStart(file),
219
+ text: `${quote}${moved.to}${quote}`
220
+ });
221
+ if (whole)
222
+ continue;
223
+ const keyword = ts.isExportDeclaration(statement) ? "export" : "import";
224
+ const typed = isTypeOnly(statement, element) ? "type " : "";
225
+ const carried = element.propertyName ? `${moved.symbol} as ${element.name.text}` : element.name.text;
226
+ const carrier = ts.isImportDeclaration(statement) && statement.importClause?.name;
227
+ const emptied = kept === undefined && carrier ? { end: clause.getEnd(), start: carrier.getEnd(), text: "" } : undefined;
228
+ const trimmed = kept === undefined ? undefined : {
229
+ end: clause.getEnd() - 1,
230
+ start: clause.getStart(file) + 1,
231
+ text: ` ${kept} `
232
+ };
233
+ edits.push(...[emptied, trimmed].filter((edit) => edit !== undefined));
234
+ edits.push({
235
+ end: statement.getEnd(),
236
+ start: statement.getEnd(),
237
+ text: `
238
+ ${keyword} ${typed}{ ${carried} } from ${quote}${moved.to}${quote};`
239
+ });
240
+ }
241
+ if (edits.length === 0)
132
242
  return { changed: false, notes: [], source };
133
- let out = source;
134
- for (const statement of statements) {
135
- const clause = statement.clause ?? "";
136
- const kept = clause.split(",").map((part) => part.trim()).filter((part) => part !== "" && !pattern.test(part));
137
- const replacement = kept.length === 0 ? statement.full.replace(moved.from, moved.to) : `${statement.full.replace(clause, ` ${kept.join(", ")} `).replace(/\s+/gu, " ")}
138
- import { ${moved.symbol} } from '${moved.to}';`;
139
- out = out.replace(statement.full, replacement);
243
+ return { changed: true, notes: [], source: applyEdits(source, edits) };
244
+ };
245
+ var applyResubpath = (input) => {
246
+ const { file, resubpath, source } = input;
247
+ if (!SPECIFIER.test(resubpath.to))
248
+ return {
249
+ changed: false,
250
+ notes: [
251
+ `\`${resubpath.to}\` is not a module specifier, so this move was not applied`
252
+ ],
253
+ source
254
+ };
255
+ const edits = [];
256
+ for (const { module, statement } of portsOf(file)) {
257
+ if (module !== resubpath.from || !statement.moduleSpecifier)
258
+ continue;
259
+ const quote = quoteOf(statement, file);
260
+ edits.push({
261
+ end: statement.moduleSpecifier.getEnd(),
262
+ start: statement.moduleSpecifier.getStart(file),
263
+ text: `${quote}${resubpath.to}${quote}`
264
+ });
140
265
  }
141
- return { changed: true, notes: [], source: out };
266
+ if (edits.length === 0)
267
+ return { changed: false, notes: [], source };
268
+ return { changed: true, notes: [], source: applyEdits(source, edits) };
142
269
  };
143
270
  var applyMigration = (source, input) => {
144
271
  const { migration, packageName } = input;
145
272
  if (!isMechanical(migration))
146
273
  return { changed: false, notes: [migration.instruction], source };
274
+ const file = parse(source, input.path ?? "file.tsx");
147
275
  if ("rename" in migration)
148
- return applyRename(source, packageName, migration.rename);
276
+ return applyRename({
277
+ file,
278
+ packageName,
279
+ rename: migration.rename,
280
+ source
281
+ });
149
282
  if ("resubpath" in migration)
150
- return applyResubpath(source, migration.resubpath);
151
- return applyMoved(source, migration.moved);
283
+ return applyResubpath({ file, resubpath: migration.resubpath, source });
284
+ return applyMoved({ file, moved: migration.moved, source });
152
285
  };
153
286
  var applyMigrations = (source, input) => input.migrations.reduce((carried, migration) => {
154
287
  const result = applyMigration(carried.source, {
155
288
  migration,
156
- packageName: input.packageName
289
+ packageName: input.packageName,
290
+ ...input.path === undefined ? {} : { path: input.path }
157
291
  });
158
292
  return {
159
293
  changed: carried.changed || result.changed,
@@ -433,6 +567,10 @@ var migrationsBetween = (changelog, from, upto) => disruptiveBetween(changelog,
433
567
  });
434
568
  var symbolsDisturbedBetween = (changelog, from, upto) => new Set(disruptiveBetween(changelog, from, upto).flatMap(({ change: change2 }) => change2.symbols));
435
569
 
570
+ // src/surface.ts
571
+ import path from "path";
572
+ import ts2 from "typescript";
573
+
436
574
  // src/tarball.ts
437
575
  var TAR_BLOCK = 512;
438
576
  var NAME_LENGTH = 100;
@@ -485,114 +623,185 @@ var readTar = (archive, keep, limits = {}) => {
485
623
  };
486
624
 
487
625
  // src/surface.ts
488
- var DECLARATION = /\bexport\s+(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(function|const|let|var|class|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/gu;
489
- var NAMED_EXPORT = /\bexport\s*\{([^}]*)\}/gu;
490
- var BODIED = new Set(["class", "interface", "enum", "namespace"]);
491
- var OPENS = new Set(["{", "(", "["]);
492
- var CLOSES = new Set(["}", ")", "]"]);
493
- var IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
494
- var LAST2 = -1;
495
- var NOWHERE = -1;
496
- var MEMBER = /([A-Za-z_$][\w$]*)\s*[?!]?\s*[:(<]/u;
497
- var isStub = (declaration) => declaration.startsWith("export {");
498
- var declarationEnd = (source, from, bodied) => {
499
- let depth = 0;
500
- for (let index = from;index < source.length; index += 1) {
501
- const character = source[index] ?? "";
502
- if (OPENS.has(character))
503
- depth += 1;
504
- else if (CLOSES.has(character))
505
- depth -= 1;
506
- else if (character === ";" && depth <= 0)
507
- return index + 1;
508
- if (CLOSES.has(character) && depth <= 0 && bodied)
509
- return index + 1;
510
- }
511
- return source.length;
512
- };
513
- var normalize = (declaration) => declaration.replace(/\s+/gu, " ").trim();
514
- var reExported = (body) => body.split(",").map((part) => part.trim().replace(/^type\s+/u, "")).filter((clause) => clause !== "").map((clause) => [
515
- clause.split(/\s+as\s+/u).at(LAST2)?.trim(),
516
- clause
517
- ]).filter((pair) => pair[0] !== undefined && IDENTIFIER.test(pair[0]));
518
- var exportedDeclarations = (source) => {
626
+ var DECLARATION = /\.d\.[cm]?ts$/u;
627
+ var parse2 = (source, name) => ts2.createSourceFile(name, source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
628
+ var normalize = (text) => text.replace(/\s+/gu, " ").trim();
629
+ var resolveFrom = (files, from, specifier) => {
630
+ if (!specifier.startsWith("."))
631
+ return;
632
+ const base = path.posix.join(path.posix.dirname(from), specifier);
633
+ const candidates = [
634
+ base,
635
+ `${base.replace(/\.js$/u, "")}.d.ts`,
636
+ `${base.replace(/\.js$/u, "")}.d.mts`,
637
+ `${base.replace(/\.js$/u, "")}.d.cts`,
638
+ `${base}/index.d.ts`,
639
+ `${base}/index.d.mts`,
640
+ `${base}/index.d.cts`
641
+ ];
642
+ return candidates.find((candidate) => files.has(candidate));
643
+ };
644
+ var declaredNameOf = (statement) => {
645
+ if (ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement) || ts2.isInterfaceDeclaration(statement) || ts2.isTypeAliasDeclaration(statement) || ts2.isEnumDeclaration(statement) || ts2.isModuleDeclaration(statement))
646
+ return statement.name !== undefined && ts2.isIdentifier(statement.name) ? statement.name.text : undefined;
647
+ return;
648
+ };
649
+ var isExported = (statement) => ts2.canHaveModifiers(statement) && ts2.getModifiers(statement)?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword) === true;
650
+ var variableNamesOf = (statement) => statement.declarationList.declarations.map((declaration) => declaration.name).filter((name) => ts2.isIdentifier(name)).map((name) => name.text);
651
+ var declarationsIn = (file) => {
519
652
  const found = new Map;
520
- const add = (name, declaration) => {
521
- const existing = found.get(name);
522
- found.set(name, existing === undefined ? declaration : `${existing}
523
- ${declaration}`);
524
- };
525
- for (const match of source.matchAll(DECLARATION)) {
526
- const [, keyword, name] = match;
527
- if (name === undefined || match.index === undefined)
653
+ for (const statement of file.statements) {
654
+ const text = normalize(statement.getText(file));
655
+ const named = declaredNameOf(statement);
656
+ if (named !== undefined)
657
+ found.set(named, text);
658
+ if (!ts2.isVariableStatement(statement))
528
659
  continue;
529
- add(name, normalize(source.slice(match.index, declarationEnd(source, match.index + match[0].length, BODIED.has(keyword ?? "")))));
660
+ for (const name of variableNamesOf(statement))
661
+ found.set(name, text);
530
662
  }
531
- for (const match of source.matchAll(NAMED_EXPORT))
532
- for (const [exported, clause] of reExported(match[1] ?? ""))
533
- add(exported, `export { ${clause} }`);
534
663
  return found;
535
664
  };
536
- var surfaceOfArchive = (archive) => surfaceOfFiles([...readTar(archive, (path) => path.endsWith(".d.ts")).values()].map(asText));
537
- var surfaceOfFiles = (files) => {
665
+ var declaresTypes = (manifest) => {
666
+ if (typeof manifest !== "object" || manifest === null)
667
+ return false;
668
+ const record = new Map(Object.entries(manifest));
669
+ if (typeof record.get("types") === "string")
670
+ return true;
671
+ if (typeof record.get("typings") === "string")
672
+ return true;
673
+ return JSON.stringify(record.get("exports") ?? {}).includes('"types"');
674
+ };
675
+ var contributionOf = (input) => {
676
+ const { file, files, from, local, seen, statement } = input;
677
+ const nothing = [];
678
+ if (ts2.isExportAssignment(statement))
679
+ return [
680
+ [
681
+ statement.isExportEquals === true ? "export=" : "default",
682
+ normalize(statement.getText(file))
683
+ ]
684
+ ];
685
+ const named = declaredNameOf(statement);
686
+ if (named !== undefined && isExported(statement))
687
+ return [
688
+ [named, local.get(named) ?? normalize(statement.getText(file))]
689
+ ];
690
+ if (ts2.isVariableStatement(statement) && isExported(statement)) {
691
+ const text = normalize(statement.getText(file));
692
+ return variableNamesOf(statement).map((name) => [name, text]);
693
+ }
694
+ if (!ts2.isExportDeclaration(statement))
695
+ return nothing;
696
+ const specifier = statement.moduleSpecifier !== undefined && ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : undefined;
697
+ const target = specifier === undefined ? undefined : resolveFrom(files, from, specifier);
698
+ const theirs = target === undefined ? new Map : exportsOf(files, target, new Set(seen));
699
+ const clause = statement.exportClause;
700
+ if (clause === undefined)
701
+ return [...theirs].filter(([name]) => name !== "default" && name !== "export=");
702
+ if (ts2.isNamespaceExport(clause))
703
+ return [[clause.name.text, `export * as ${clause.name.text}`]];
704
+ return clause.elements.map((element) => {
705
+ const sourceName = (element.propertyName ?? element.name).text;
706
+ const text = (specifier === undefined ? local.get(sourceName) : theirs.get(sourceName)) ?? `export { ${sourceName} }`;
707
+ return [element.name.text, text];
708
+ });
709
+ };
710
+ var exportsOf = (files, from, seen) => {
538
711
  const surface = new Map;
539
- for (const source of files)
540
- for (const [name, declaration] of exportedDeclarations(source)) {
541
- const known = surface.get(name);
542
- if (known === undefined || isStub(known) && !isStub(declaration))
543
- surface.set(name, declaration);
544
- }
712
+ const source = files.get(from);
713
+ if (source === undefined || seen.has(from))
714
+ return surface;
715
+ seen.add(from);
716
+ const file = parse2(source, from);
717
+ const local = declarationsIn(file);
718
+ const contributed = file.statements.flatMap((statement) => contributionOf({ file, files, from, local, seen, statement }));
719
+ for (const [name, text] of contributed)
720
+ surface.set(name, text);
545
721
  return surface;
546
722
  };
723
+ var surfaceOfArchive = (archive) => {
724
+ const held = readTar(archive, (name) => DECLARATION.test(name) || name === "package.json");
725
+ const files = new Map([...held].map(([name, bytes]) => [name, asText(bytes)]));
726
+ const manifest = files.get("package.json");
727
+ let parsed;
728
+ try {
729
+ parsed = manifest === undefined ? undefined : JSON.parse(manifest);
730
+ } catch {
731
+ parsed = undefined;
732
+ }
733
+ const entries = typeEntriesOf(parsed, files);
734
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
735
+ };
736
+ var surfaceOfFiles = (files, entries) => {
737
+ const surface = new Map;
738
+ for (const entry of entries)
739
+ for (const [name, text] of exportsOf(files, entry, new Set))
740
+ if (!surface.has(name))
741
+ surface.set(name, text);
742
+ return surface;
743
+ };
744
+ var typeCandidatesIn = (value) => {
745
+ if (typeof value === "string")
746
+ return [value];
747
+ if (typeof value !== "object" || value === null)
748
+ return [];
749
+ return Object.entries(value).flatMap(([key, nested]) => key === "types" || key === "typings" ? typeCandidatesIn(nested) : typeCandidatesIn(nested));
750
+ };
751
+ var typeEntriesOf = (manifest, files) => {
752
+ const record = typeof manifest === "object" && manifest !== null ? new Map(Object.entries(manifest)) : new Map;
753
+ const claimed = [
754
+ record.get("types"),
755
+ record.get("typings"),
756
+ ...typeCandidatesIn(record.get("exports"))
757
+ ];
758
+ const entries = claimed.filter((value) => typeof value === "string").map((value) => value.replace(/^\.\//u, "")).flatMap((cleaned) => DECLARATION.test(cleaned) ? [cleaned] : [
759
+ cleaned.replace(/\.[cm]?js$/u, ".d.ts"),
760
+ cleaned.replace(/\.[cm]?js$/u, ".d.mts"),
761
+ cleaned.replace(/\.[cm]?js$/u, ".d.cts")
762
+ ]).filter((candidate) => files.has(candidate));
763
+ const fallback = entries.length === 0 && files.has("dist/index.d.ts") ? ["dist/index.d.ts"] : [];
764
+ return [...new Set([...entries, ...fallback])];
765
+ };
547
766
  var SHAPE = {
548
767
  members: "members",
549
768
  parameters: "parameters",
550
769
  types: "types",
551
770
  unknown: "unknown"
552
771
  };
772
+ var isStub = (declaration) => declaration.startsWith("export {");
773
+ var signatureOf = (declaration) => {
774
+ const file = parse2(`${declaration}
775
+ `, "declaration.d.ts");
776
+ const [statement] = file.statements;
777
+ if (statement === undefined)
778
+ return;
779
+ return statement;
780
+ };
553
781
  var parametersOf = (declaration) => {
554
- let depth = 0;
555
- let start = NOWHERE;
556
- for (const [index, character] of [...declaration].entries()) {
557
- if (character === "{" && depth === 0)
558
- return "";
559
- depth += character === "(" ? 1 : 0;
560
- if (character === "(" && depth === 1)
561
- start = index;
562
- depth -= character === ")" ? 1 : 0;
563
- if (character === ")" && depth === 0 && start > NOWHERE)
564
- return declaration.slice(start, index + 1);
565
- }
566
- return "";
782
+ const statement = signatureOf(declaration);
783
+ if (statement === undefined)
784
+ return "";
785
+ const found = [];
786
+ const visit = (node) => {
787
+ if (ts2.isFunctionTypeNode(node) || ts2.isFunctionDeclaration(node) || ts2.isMethodSignature(node) || ts2.isMethodDeclaration(node) || ts2.isConstructSignatureDeclaration(node) || ts2.isConstructorDeclaration(node))
788
+ found.push(node.parameters.map((parameter) => normalize(parameter.getText())).join(", "));
789
+ ts2.forEachChild(node, visit);
790
+ };
791
+ visit(statement);
792
+ return found.join(" | ");
567
793
  };
568
794
  var membersOf = (declaration) => {
569
- const opened = declaration.indexOf("{");
570
- if (opened < 0)
571
- return new Set;
795
+ const statement = signatureOf(declaration);
572
796
  const members = new Set;
573
- let depth = 0;
574
- let segment = "";
575
- const take = () => {
576
- const [, name] = MEMBER.exec(segment.trim()) ?? [];
577
- if (name !== undefined)
578
- members.add(name);
579
- segment = "";
797
+ if (statement === undefined)
798
+ return members;
799
+ const visit = (node) => {
800
+ if ((ts2.isPropertySignature(node) || ts2.isMethodSignature(node) || ts2.isPropertyDeclaration(node) || ts2.isMethodDeclaration(node) || ts2.isEnumMember(node)) && node.name !== undefined && (ts2.isIdentifier(node.name) || ts2.isStringLiteral(node.name)))
801
+ members.add(node.name.text);
802
+ ts2.forEachChild(node, visit);
580
803
  };
581
- for (const character of declaration.slice(opened)) {
582
- const opening = OPENS.has(character);
583
- const closing = CLOSES.has(character);
584
- depth += opening ? 1 : 0;
585
- if (opening && depth === 1)
586
- segment = "";
587
- depth -= closing ? 1 : 0;
588
- if (opening || closing || depth !== 1)
589
- continue;
590
- if (character === ";" || character === ",")
591
- take();
592
- else
593
- segment += character;
594
- }
595
- take();
804
+ visit(statement);
596
805
  return members;
597
806
  };
598
807
  var sameNames = (left, right) => left.size === right.size && [...left].every((name) => right.has(name));
@@ -629,9 +838,9 @@ var diffSurface = (before, after) => {
629
838
  var notable = (diff) => diff.changed.filter((change2) => change2.shape === "parameters" || change2.shape === "members");
630
839
 
631
840
  // src/entries.ts
632
- var loadEntry = async (path) => {
841
+ var loadEntry = async (path2) => {
633
842
  try {
634
- const module = await import(pathToFileURL(path).href);
843
+ const module = await import(pathToFileURL(path2).href);
635
844
  const named = typeof module === "object" && module !== null && "change" in module ? module.change : undefined;
636
845
  const exported = named ?? (typeof module === "object" && module !== null && "default" in module ? module.default : undefined);
637
846
  const read = { exported };
@@ -644,23 +853,28 @@ var loadEntry = async (path) => {
644
853
  }
645
854
  };
646
855
  var ENTRY = /\.[cm]?ts$/u;
647
- var builtSurface = async (packageRoot, out = "dist") => {
648
- const root = join(packageRoot, out);
649
- const sources = [];
856
+ var DECLARATION2 = /\.d\.[cm]?ts$/u;
857
+ var OUTPUT_DIRECTORIES = ["dist", "build", "lib", "types", "out"];
858
+ var builtSurface = async (packageRoot) => {
859
+ const manifest = await readJsonFile(join(packageRoot, "package.json"));
860
+ const files = new Map;
650
861
  const walk = async (directory) => {
651
- const found = await readdir(directory, { withFileTypes: true }).catch(() => []);
862
+ const found = await readdir(join(packageRoot, directory), {
863
+ withFileTypes: true
864
+ }).catch(() => []);
652
865
  await Promise.all(found.map(async (entry) => {
653
- const path = join(directory, entry.name);
866
+ const relative = `${directory}/${entry.name}`;
654
867
  if (entry.isDirectory())
655
- return walk(path);
656
- if (!entry.name.endsWith(".d.ts"))
868
+ return walk(relative);
869
+ if (!DECLARATION2.test(entry.name))
657
870
  return;
658
- sources.push(await readFile(path, "utf8").catch(() => ""));
871
+ files.set(relative, await readFile(join(packageRoot, relative), "utf8").catch(() => ""));
659
872
  return;
660
873
  }));
661
874
  };
662
- await walk(root);
663
- return sources.length === 0 ? undefined : surfaceOfFiles(sources);
875
+ await Promise.all(OUTPUT_DIRECTORIES.map((directory) => walk(directory)));
876
+ const entries = typeEntriesOf(manifest, files);
877
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
664
878
  };
665
879
  var changelogPaths = (packageRoot) => ({
666
880
  history: join(packageRoot, "changelog", "history.md"),
@@ -686,8 +900,8 @@ var loadUnreleased = async (packageRoot) => {
686
900
  }
687
901
  return { entries, problems };
688
902
  };
689
- var readJsonFile = async (path) => {
690
- const source = await readFile(path, "utf8").catch(() => {
903
+ var readJsonFile = async (path2) => {
904
+ const source = await readFile(path2, "utf8").catch(() => {
691
905
  return;
692
906
  });
693
907
  if (source === undefined)
@@ -737,8 +951,14 @@ var migrationHolds = (input) => {
737
951
  };
738
952
  var describeReconciliation = (found) => {
739
953
  const lines = [];
954
+ const moved2 = {
955
+ members: "changed the members of its type",
956
+ parameters: "changed what it is called with",
957
+ removed: "is no longer exported",
958
+ types: "changed its declaration"
959
+ };
740
960
  for (const entry of found.unannounced)
741
- lines.push(entry.kind === "removed" ? `\`${entry.name}\` is no longer exported and no entry says so` : `\`${entry.name}\` changed its ${entry.kind} and no entry says so`);
961
+ lines.push(`\`${entry.name}\` ${moved2[entry.kind]} and no entry says so`);
742
962
  for (const name of found.unknownSymbols)
743
963
  lines.push(`an entry names \`${name}\`, which this package does not export`);
744
964
  lines.push(...found.wrongMigrations);
@@ -749,10 +969,7 @@ var reconcile = (input) => {
749
969
  const mentioned = new Set(input.changes.flatMap((change2) => [...change2.symbols ?? []]));
750
970
  const unannounced = [
751
971
  ...input.diff.removed.filter((name) => !disruptive.has(name)).map((name) => ({ kind: "removed", name })),
752
- ...notable(input.diff).filter((change2) => !mentioned.has(change2.name)).map((change2) => ({
753
- kind: change2.shape === "parameters" ? "parameters" : "members",
754
- name: change2.name
755
- }))
972
+ ...input.diff.changed.filter((change2) => !mentioned.has(change2.name) && change2.shape !== "unknown").map((change2) => ({ kind: change2.shape, name: change2.name }))
756
973
  ];
757
974
  const unknownSymbols = [
758
975
  ...new Set(input.changes.flatMap((change2) => [...change2.symbols ?? []]).filter((name) => !input.known.has(name)))
@@ -769,6 +986,16 @@ var reconcile = (input) => {
769
986
  import { gunzipSync } from "zlib";
770
987
  var ABBREVIATED = "application/vnd.npm.install-v1+json";
771
988
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
989
+ var KIBIBYTE2 = 1024;
990
+ var MEBIBYTE2 = KIBIBYTE2 * KIBIBYTE2;
991
+ var MAX_ARCHIVE_MEBIBYTES = 96;
992
+ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
993
+ var MAX_DOWNLOAD_MEBIBYTES = 32;
994
+ var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
995
+ var TIMEOUT_MS = 20000;
996
+ var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
997
+ var MAX_NAME_LENGTH = 214;
998
+ var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
772
999
  var isRecord2 = (value) => typeof value === "object" && value !== null;
773
1000
  var readPackument = (body) => {
774
1001
  if (!isRecord2(body) || !isRecord2(body.versions))
@@ -796,20 +1023,30 @@ var packageArchive = async (name, version, options = {}) => {
796
1023
  if (url === undefined)
797
1024
  return;
798
1025
  try {
799
- const response = await call(url);
1026
+ const response = await call(url, {
1027
+ signal: AbortSignal.timeout(TIMEOUT_MS)
1028
+ });
800
1029
  if (!response.ok)
801
1030
  return;
802
- return new Uint8Array(gunzipSync(new Uint8Array(await response.arrayBuffer())));
1031
+ const compressed = new Uint8Array(await response.arrayBuffer());
1032
+ if (compressed.byteLength > MAX_DOWNLOAD_BYTES)
1033
+ return;
1034
+ return new Uint8Array(gunzipSync(compressed, { maxOutputLength: MAX_ARCHIVE_BYTES }));
803
1035
  } catch {
804
1036
  return;
805
1037
  }
806
1038
  };
807
1039
  var packageVersions = async (name, options = {}) => {
1040
+ if (!isPackageName(name))
1041
+ return;
808
1042
  const call = options.fetch ?? fetch;
809
1043
  const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
810
1044
  try {
811
- const abbreviated = await call(url, { headers: { accept: ABBREVIATED } });
812
- const response = abbreviated.ok ? abbreviated : await call(url);
1045
+ const abbreviated = await call(url, {
1046
+ headers: { accept: ABBREVIATED },
1047
+ signal: AbortSignal.timeout(TIMEOUT_MS)
1048
+ });
1049
+ const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
813
1050
  if (!response.ok)
814
1051
  return;
815
1052
  return readPackument(await response.json());
@@ -821,7 +1058,7 @@ var publishedChangelog = async (name, version, options = {}) => {
821
1058
  const archive = await packageArchive(name, version, options);
822
1059
  if (!archive)
823
1060
  return;
824
- const file = readTar(archive, (path) => path === CHANGELOG_FILE).get(CHANGELOG_FILE);
1061
+ const file = readTar(archive, (path2) => path2 === CHANGELOG_FILE).get(CHANGELOG_FILE);
825
1062
  if (!file)
826
1063
  return;
827
1064
  try {
@@ -898,6 +1135,7 @@ var renderRelease = (release) => {
898
1135
  ].join(`
899
1136
  `);
900
1137
  };
1138
+ var GENERATED_MARKER = "This file is generated by `absolute-changelog`";
901
1139
  var renderChangelogJson = (changelog) => `${JSON.stringify({
902
1140
  contract: CHANGELOG_CONTRACT,
903
1141
  name: changelog.name,
@@ -923,7 +1161,7 @@ var renderMarkdown = (changelog, options = {}) => {
923
1161
  "",
924
1162
  `All notable changes to \`${changelog.name}\`.`,
925
1163
  "",
926
- "This file is generated by `absolute-changelog` from the entries in",
1164
+ `${GENERATED_MARKER} from the entries in`,
927
1165
  "`changelog/`. Edit an entry, not this file \u2014 and add new ones under",
928
1166
  "`changelog/unreleased/`."
929
1167
  ].join(`
@@ -954,12 +1192,13 @@ export {
954
1192
  changelogPaths,
955
1193
  changesBetween,
956
1194
  compareVersions,
1195
+ declaresTypes,
957
1196
  describeReconciliation,
958
1197
  diffSurface,
959
1198
  disruptiveBetween,
960
- exportedDeclarations,
961
1199
  isDisruptive,
962
1200
  isMechanical,
1201
+ isPackageName,
963
1202
  isPrerelease,
964
1203
  loadUnreleased,
965
1204
  manual,
@@ -985,8 +1224,9 @@ export {
985
1224
  surfaceOfArchive,
986
1225
  surfaceOfFiles,
987
1226
  symbolsDisturbedBetween,
1227
+ typeEntriesOf,
988
1228
  validateChange
989
1229
  };
990
1230
 
991
- //# debugId=7673E1441936CC9C64756E2164756E21
1231
+ //# debugId=7DC9C4F3C075733D64756E2164756E21
992
1232
  //# sourceMappingURL=index.js.map