@telorun/ide-support 0.7.10 → 0.9.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.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/dist/completions/import-source.js +2 -2
  3. package/dist/completions/resolve-node.d.ts +5 -0
  4. package/dist/completions/resolve-node.d.ts.map +1 -1
  5. package/dist/completions/resolve-node.js +4 -0
  6. package/dist/definition/alias-qualified-value.d.ts +23 -0
  7. package/dist/definition/alias-qualified-value.d.ts.map +1 -0
  8. package/dist/definition/alias-qualified-value.js +24 -0
  9. package/dist/definition/build-definition.d.ts +15 -8
  10. package/dist/definition/build-definition.d.ts.map +1 -1
  11. package/dist/definition/build-definition.js +35 -89
  12. package/dist/definition/manifest-navigation.d.ts +36 -0
  13. package/dist/definition/manifest-navigation.d.ts.map +1 -0
  14. package/dist/definition/manifest-navigation.js +92 -0
  15. package/dist/definition/resolve-cel-target.d.ts +17 -0
  16. package/dist/definition/resolve-cel-target.d.ts.map +1 -0
  17. package/dist/definition/resolve-cel-target.js +125 -0
  18. package/dist/definition/resolve-export-chain.d.ts +23 -0
  19. package/dist/definition/resolve-export-chain.d.ts.map +1 -0
  20. package/dist/definition/resolve-export-chain.js +89 -0
  21. package/dist/definition/resolve-kind-target.d.ts +26 -0
  22. package/dist/definition/resolve-kind-target.d.ts.map +1 -0
  23. package/dist/definition/resolve-kind-target.js +51 -0
  24. package/dist/definition/resolve-ref-target.d.ts +14 -0
  25. package/dist/definition/resolve-ref-target.d.ts.map +1 -0
  26. package/dist/definition/resolve-ref-target.js +25 -0
  27. package/dist/import-upgrades/build-import-upgrades.d.ts +78 -0
  28. package/dist/import-upgrades/build-import-upgrades.d.ts.map +1 -0
  29. package/dist/import-upgrades/build-import-upgrades.js +95 -0
  30. package/dist/import-upgrades/find-import-entries.d.ts +45 -0
  31. package/dist/import-upgrades/find-import-entries.d.ts.map +1 -0
  32. package/dist/import-upgrades/find-import-entries.js +102 -0
  33. package/dist/import-upgrades/index.d.ts +5 -0
  34. package/dist/import-upgrades/index.d.ts.map +1 -0
  35. package/dist/import-upgrades/index.js +2 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/package.json +2 -2
  40. package/src/completions/import-source.ts +2 -2
  41. package/src/completions/resolve-node.ts +9 -0
  42. package/src/definition/alias-qualified-value.ts +42 -0
  43. package/src/definition/build-definition.ts +34 -104
  44. package/src/definition/manifest-navigation.ts +124 -0
  45. package/src/definition/resolve-cel-target.ts +151 -0
  46. package/src/definition/resolve-export-chain.ts +119 -0
  47. package/src/definition/resolve-kind-target.ts +61 -0
  48. package/src/definition/resolve-ref-target.ts +34 -0
  49. package/src/import-upgrades/build-import-upgrades.ts +190 -0
  50. package/src/import-upgrades/find-import-entries.ts +175 -0
  51. package/src/import-upgrades/index.ts +10 -0
  52. package/src/index.ts +1 -0
@@ -0,0 +1,61 @@
1
+ import type { LoadedGraph, LoadedModule } from "@telorun/analyzer";
2
+ import type { DefinitionResult } from "../types.js";
3
+ import type { ResolvedCursor } from "../completions/resolve-node.js";
4
+ import type { AliasQualifiedValue } from "./alias-qualified-value.js";
5
+ import { locateImport, locateKindDefinition, moduleFiles } from "./manifest-navigation.js";
6
+ import { resolveExportedKind } from "./resolve-export-chain.js";
7
+
8
+ const DEFINITION_DOC_KINDS = new Set(["Telo.Definition", "Telo.Abstract"]);
9
+
10
+ /** Whether the cursor sits in a slot whose value is an alias-qualified kind.
11
+ *
12
+ * `x-telo-ref` is an `x-telo-*` annotation, unambiguous wherever it appears.
13
+ * `extends:` is only a kind at the top level of a definition doc, so it is
14
+ * gated on both. `kind:` is positional by nature: a map carrying a
15
+ * `<Alias>.<Kind>` value under `kind` IS an inline resource declaration as far
16
+ * as the analyzer is concerned too (`resourceKindOf`), so there is no further
17
+ * structure to check — an unresolvable value simply navigates nowhere.
18
+ *
19
+ * `capability:` is deliberately absent — its values are kernel built-ins with
20
+ * no manifest to jump to (hover documents them instead). */
21
+ export function isKindSlot(resolved: ResolvedCursor): boolean {
22
+ const key = resolved.path[resolved.path.length - 1];
23
+ if (key === "x-telo-ref" || key === "kind") return true;
24
+ return (
25
+ key === "extends" &&
26
+ resolved.path.length === 1 &&
27
+ DEFINITION_DOC_KINDS.has(resolved.docKind ?? "")
28
+ );
29
+ }
30
+
31
+ /** Resolve an alias-qualified kind (`Http.Server`) to the `Telo.Definition` /
32
+ * `Telo.Abstract` doc that registers it, or — when the cursor sits on the
33
+ * alias — to the import that declares the alias.
34
+ *
35
+ * `Self.<Kind>` is the declaring module's own kind; `Telo.<Kind>` is a kernel
36
+ * built-in with no manifest, so it resolves to nothing. Across an import the
37
+ * walk honors `exports.kinds` and follows `<Inner>.<Kind>` re-exports to the
38
+ * owning module, matching what the kernel resolves the kind to at runtime. */
39
+ export function resolveKindTarget(
40
+ graph: LoadedGraph,
41
+ currentModule: LoadedModule,
42
+ kind: AliasQualifiedValue,
43
+ ): DefinitionResult | undefined {
44
+ const { alias, name, onAlias } = kind;
45
+
46
+ if (alias === undefined) {
47
+ // The legacy `<namespace>/<module>#<Kind>` identity form of `x-telo-ref`
48
+ // still resolves for already-published module versions, but it names a
49
+ // module this manifest need not import, so there is no alias to follow.
50
+ if (name.includes("#")) return undefined;
51
+ return locateKindDefinition(moduleFiles(currentModule), name);
52
+ }
53
+ if (alias === "Self") return locateKindDefinition(moduleFiles(currentModule), name);
54
+ if (alias === "Telo") return undefined;
55
+
56
+ if (onAlias) return locateImport(currentModule, alias);
57
+
58
+ const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
59
+ if (!edge) return undefined;
60
+ return resolveExportedKind(graph, edge.targetSource, name);
61
+ }
@@ -0,0 +1,34 @@
1
+ import type { LoadedGraph, LoadedModule } from "@telorun/analyzer";
2
+ import type { DefinitionResult } from "../types.js";
3
+ import type { AliasQualifiedValue } from "./alias-qualified-value.js";
4
+ import { locateImport, locateResource, moduleFiles } from "./manifest-navigation.js";
5
+ import { resolveExportedResource } from "./resolve-export-chain.js";
6
+
7
+ /** Resolve a `!ref` target to the resource instance it names.
8
+ *
9
+ * The grammar mirrors `resolveRefSentinels`: a bare name (or `Self.name`) is a
10
+ * local resource in the current module; `Alias.name` is an exported instance of
11
+ * the module the import `Alias` points at, followed transitively through
12
+ * re-exports and gated on each module's `exports.resources`. The alias half
13
+ * navigates to the import that declares it. Returns `undefined` when the target
14
+ * can't be found (a scope-local name, an instance the target does not export,
15
+ * or an import that failed to load). */
16
+ export function resolveRefTarget(
17
+ graph: LoadedGraph,
18
+ currentModule: LoadedModule,
19
+ ref: AliasQualifiedValue,
20
+ ): DefinitionResult | undefined {
21
+ const { alias, name, onAlias } = ref;
22
+
23
+ if (alias === undefined || alias === "Self") {
24
+ // `Self` names the declaring module itself — there is no import to jump to,
25
+ // so the alias half resolves like the name half.
26
+ return locateResource(moduleFiles(currentModule), name);
27
+ }
28
+
29
+ if (onAlias) return locateImport(currentModule, alias);
30
+
31
+ const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
32
+ if (!edge) return undefined;
33
+ return resolveExportedResource(graph, edge.targetSource, name);
34
+ }
@@ -0,0 +1,190 @@
1
+ import {
2
+ buildLineOffsets,
3
+ isLocalPathSource,
4
+ isNewerModuleVersion,
5
+ newestModuleVersion,
6
+ parseToAst,
7
+ parseVersionedRef,
8
+ withRefVersion,
9
+ type AstDocument,
10
+ type Range,
11
+ } from "@telorun/analyzer";
12
+ import { findImportEntries, type ImportEntry } from "./find-import-entries.js";
13
+
14
+ /** Version enumeration for one version-independent base ref, newest first.
15
+ *
16
+ * Narrower than the full `IdeEnvironmentAdapter` on purpose. It is the only
17
+ * environment capability an upgrade check needs, and a host that caches or
18
+ * throttles hub traffic wraps just this — a CodeLens re-resolves far more
19
+ * often than a completion popup opens, so the refresh cadence is the host's
20
+ * policy, not this module's. A host backed by the hub passes
21
+ * `adapter.listVersionsForRef`. */
22
+ export type ModuleVersionLookup = (baseRef: string) => Promise<string[]>;
23
+
24
+ /** A source edit a host applies verbatim to upgrade an import. Ranges never
25
+ * overlap, within an upgrade or across a batch, so a host may apply the whole
26
+ * set in one pass without ordering them. */
27
+ export interface ImportUpgradeEdit {
28
+ range: Range;
29
+ newText: string;
30
+ }
31
+
32
+ /** One import that has a newer version available. */
33
+ export interface ImportUpgrade {
34
+ alias: string;
35
+ /** The source as written, with any object-form `integrity:` folded in. */
36
+ source: string;
37
+ currentVersion: string;
38
+ latestVersion: string;
39
+ /** The source that replaces it: re-pointed at `latestVersion` with the
40
+ * integrity pin dropped. */
41
+ newSource: string;
42
+ /** True when the replaced import carried a pin. A host that cannot recompute
43
+ * the hash should say so — the pin covers the version being replaced, and
44
+ * `telo upgrade` re-pins either shape. */
45
+ wasPinned: boolean;
46
+ /** Span of the alias key — where a per-entry affordance anchors. */
47
+ keyRange: Range;
48
+ /** Apply all of these to upgrade this one import. */
49
+ edits: ImportUpgradeEdit[];
50
+ }
51
+
52
+ /** An import that IS behind but that this module declines to rewrite. Carries
53
+ * the same anchor and versions an {@link ImportUpgrade} does, so a host can
54
+ * render it in place of the upgrade affordance rather than leaving the author
55
+ * wondering why a stale import shows nothing at all. */
56
+ export interface ImportUpgradeSkip {
57
+ alias: string;
58
+ currentVersion: string;
59
+ latestVersion: string;
60
+ /** Span of the alias key — where a per-entry affordance anchors. */
61
+ keyRange: Range;
62
+ /** Author-facing sentence: what was not done, and what to run instead. */
63
+ reason: string;
64
+ }
65
+
66
+ export interface ImportUpgradeSet {
67
+ /** Span of the `imports:` key — where a summary affordance anchors. */
68
+ importsKeyRange: Range;
69
+ upgrades: ImportUpgrade[];
70
+ skipped: ImportUpgradeSkip[];
71
+ /** Base refs whose version lookup failed. Never thrown: one unreachable ref
72
+ * must not blank the affordances for every other import in the file. The
73
+ * host decides whether to log or surface these. */
74
+ failures: Array<{ baseRef: string; message: string }>;
75
+ }
76
+
77
+ /**
78
+ * Find every `imports:` entry of a module document that names a version older
79
+ * than the newest one `listVersions` reports, and produce the source edits that
80
+ * re-point it.
81
+ *
82
+ * Skips what carries no upgradeable version: local path imports, bare URLs,
83
+ * untagged refs, and pins that are not SemVer (an OCI digest, a moving tag like
84
+ * `latest`) — `parseVersionedRef` and `isNewerModuleVersion` both decline to
85
+ * guess, so those simply produce no upgrade.
86
+ *
87
+ * Pure apart from `listVersions`: no filesystem, no direct network, no host
88
+ * API. Returns `undefined` when the file declares no module document or the
89
+ * module declares no `imports:`.
90
+ */
91
+ export async function buildImportUpgrades(
92
+ text: string,
93
+ listVersions: ModuleVersionLookup,
94
+ docs?: AstDocument[],
95
+ ): Promise<ImportUpgradeSet | undefined> {
96
+ const lineOffsets = buildLineOffsets(text);
97
+ const block = findImportEntries(text, docs ?? parseToAst(text), lineOffsets);
98
+ if (!block) return undefined;
99
+
100
+ const candidates = block.entries.flatMap((entry) => {
101
+ if (isLocalPathSource(entry.source)) return [];
102
+ const ref = parseVersionedRef(entry.source);
103
+ return ref ? [{ entry, ref }] : [];
104
+ });
105
+
106
+ const failures: Array<{ baseRef: string; message: string }> = [];
107
+ const latest = await resolveLatest(
108
+ [...new Set(candidates.map((c) => c.ref.baseRef))],
109
+ listVersions,
110
+ failures,
111
+ );
112
+
113
+ const upgrades: ImportUpgrade[] = [];
114
+ const skipped: ImportUpgradeSkip[] = [];
115
+
116
+ for (const { entry, ref } of candidates) {
117
+ const newest = latest.get(ref.baseRef);
118
+ if (!newest || !isNewerModuleVersion(newest, ref.version)) continue;
119
+
120
+ if (entry.integrityInline) {
121
+ skipped.push({
122
+ alias: entry.alias,
123
+ currentVersion: ref.version,
124
+ latestVersion: newest,
125
+ keyRange: entry.keyRange,
126
+ reason:
127
+ `'${entry.alias}' carries an inline 'integrity:' that shares a line with other ` +
128
+ `fields, so the stale pin cannot be removed by a line edit. Run \`telo upgrade\`.`,
129
+ });
130
+ continue;
131
+ }
132
+
133
+ upgrades.push({
134
+ alias: entry.alias,
135
+ source: entry.source,
136
+ currentVersion: ref.version,
137
+ latestVersion: newest,
138
+ newSource: withRefVersion(entry.source, newest),
139
+ wasPinned: ref.integrity != null,
140
+ keyRange: entry.keyRange,
141
+ edits: buildEdits(entry, withRefVersion(entry.source, newest)),
142
+ });
143
+ }
144
+
145
+ return { importsKeyRange: block.keyRange, upgrades, skipped, failures };
146
+ }
147
+
148
+ /** Re-point the source scalar, and delete a now-stale object-form `integrity:`
149
+ * line. `withRefVersion` already strips an inline `#sha256-…` fragment, so the
150
+ * scalar shorthand needs no second edit. Dropping the pin is not optional: it
151
+ * hashes the `telo.yaml` of the version being replaced, so carrying it onto a
152
+ * different version turns the next install into a tamper error. */
153
+ function buildEdits(entry: ImportEntry, newSource: string): ImportUpgradeEdit[] {
154
+ const edits: ImportUpgradeEdit[] = [{ range: entry.sourceRange, newText: newSource }];
155
+ if (entry.integrityLineRange) {
156
+ edits.push({ range: entry.integrityLineRange, newText: "" });
157
+ }
158
+ return edits;
159
+ }
160
+
161
+ /** Newest version per base ref, fetched once each. A ref whose lookup rejects
162
+ * is recorded in `failures` and left out of the map, so it yields no upgrade
163
+ * rather than a wrong one. */
164
+ async function resolveLatest(
165
+ baseRefs: string[],
166
+ listVersions: ModuleVersionLookup,
167
+ failures: Array<{ baseRef: string; message: string }>,
168
+ ): Promise<Map<string, string>> {
169
+ const results = await Promise.all(
170
+ baseRefs.map(async (baseRef) => {
171
+ try {
172
+ const versions = await listVersions(baseRef);
173
+ return { baseRef, newest: newestModuleVersion(versions) };
174
+ } catch (err) {
175
+ failures.push({
176
+ baseRef,
177
+ message: err instanceof Error ? err.message : String(err),
178
+ });
179
+ return { baseRef, newest: undefined };
180
+ }
181
+ }),
182
+ );
183
+
184
+ const map = new Map<string, string>();
185
+ for (const { baseRef, newest } of results) {
186
+ if (newest) map.set(baseRef, newest);
187
+ }
188
+ return map;
189
+ }
190
+
@@ -0,0 +1,175 @@
1
+ import {
2
+ foldIntegrity,
3
+ isModuleKind,
4
+ offsetToPosition,
5
+ type AstDocument,
6
+ type AstMap,
7
+ type AstNode,
8
+ type Range,
9
+ } from "@telorun/analyzer";
10
+
11
+ /** One `imports:` map entry, located in the source text.
12
+ *
13
+ * `source` is the *folded* form — an object-form `integrity:` sibling is
14
+ * folded into the source string as a `#sha256-…` fragment, exactly as
15
+ * `inlineImportManifests` does, so callers reason about a single
16
+ * representation regardless of which shape the author wrote. */
17
+ export interface ImportEntry {
18
+ alias: string;
19
+ /** The source with any `integrity:` sibling folded in as a fragment. */
20
+ source: string;
21
+ /** Span of the alias key — where a per-entry affordance anchors. */
22
+ keyRange: Range;
23
+ /** Span of the source scalar's value: the entry value itself for the scalar
24
+ * shorthand, the `source:` value for the object form. Replacing this span
25
+ * re-points the import. */
26
+ sourceRange: Range;
27
+ /** Whole-line span of an object-form `integrity:` entry, including its
28
+ * trailing newline, so a caller can delete the line. Absent for the scalar
29
+ * shorthand (where the pin rides inside `sourceRange`) and for an entry that
30
+ * declares no `integrity:`. */
31
+ integrityLineRange?: Range;
32
+ /** Set when the entry carries an `integrity:` sibling that does NOT occupy
33
+ * whole lines of its own (a flow-style `{source: …, integrity: …}` map).
34
+ * Deleting it would need a structural rewrite rather than a line splice, so
35
+ * a caller that cannot leave the pin behind must skip this entry rather
36
+ * than re-point it and strand a hash for the version it replaced. */
37
+ integrityInline?: boolean;
38
+ }
39
+
40
+ /** Where the `imports:` map lives in a module document. */
41
+ export interface ImportsBlock {
42
+ /** Span of the `imports:` key — where a summary affordance anchors. */
43
+ keyRange: Range;
44
+ entries: ImportEntry[];
45
+ }
46
+
47
+ /** Locate the `imports:` map of the file's module document (`Telo.Application`
48
+ * / `Telo.Library`). Returns `undefined` when the file declares no module doc
49
+ * or the doc has no `imports:` map — a partial file, or a module with no
50
+ * dependencies.
51
+ *
52
+ * Reads the AST rather than the analyzer's flattened manifests because the
53
+ * affordances built on top of this write back to the source: the exact span of
54
+ * each source scalar is the deliverable, not the resolved value. */
55
+ export function findImportEntries(
56
+ text: string,
57
+ docs: AstDocument[],
58
+ lineOffsets: number[],
59
+ ): ImportsBlock | undefined {
60
+ for (const doc of docs) {
61
+ const root = doc.root;
62
+ if (!root || root.kind !== "map") continue;
63
+ if (!isModuleKind(scalarString(mapGet(root, "kind")))) continue;
64
+
65
+ const importsPair = root.entries.find(
66
+ (p) => p.key.kind === "scalar" && p.key.value === "imports",
67
+ );
68
+ if (!importsPair?.value || importsPair.value.kind !== "map") return undefined;
69
+
70
+ return {
71
+ keyRange: toRange(importsPair.key, lineOffsets),
72
+ entries: importsPair.value.entries.flatMap((pair) => {
73
+ const alias = scalarString(pair.key);
74
+ if (alias === undefined || !pair.value) return [];
75
+ const entry = readEntry(alias, pair.key, pair.value, text, lineOffsets);
76
+ return entry ? [entry] : [];
77
+ }),
78
+ };
79
+ }
80
+ return undefined;
81
+ }
82
+
83
+ /** Read one entry in either authored shape. Returns `undefined` for a malformed
84
+ * entry (an object with no string `source:`) — the module document's own
85
+ * schema validation already reports those against `imports.<Alias>.source`. */
86
+ function readEntry(
87
+ alias: string,
88
+ keyNode: AstNode,
89
+ valueNode: AstNode,
90
+ text: string,
91
+ lineOffsets: number[],
92
+ ): ImportEntry | undefined {
93
+ const keyRange = toRange(keyNode, lineOffsets);
94
+
95
+ if (valueNode.kind === "scalar") {
96
+ const source = scalarString(valueNode);
97
+ if (source === undefined) return undefined;
98
+ return { alias, source, keyRange, sourceRange: toRange(valueNode, lineOffsets) };
99
+ }
100
+
101
+ if (valueNode.kind !== "map") return undefined;
102
+ const sourceNode = mapGet(valueNode, "source");
103
+ const source = scalarString(sourceNode);
104
+ if (sourceNode === undefined || source === undefined) return undefined;
105
+
106
+ const integrityPair = valueNode.entries.find(
107
+ (p) => p.key.kind === "scalar" && p.key.value === "integrity",
108
+ );
109
+ const integrity = scalarString(integrityPair?.value);
110
+
111
+ const entry: ImportEntry = {
112
+ alias,
113
+ source: foldIntegrity(source, integrity),
114
+ keyRange,
115
+ sourceRange: toRange(sourceNode, lineOffsets),
116
+ };
117
+
118
+ if (integrityPair?.value && integrity !== undefined) {
119
+ const lineSpan = wholeLineSpan(
120
+ integrityPair.key.range[0],
121
+ integrityPair.value.range[1],
122
+ text,
123
+ lineOffsets,
124
+ );
125
+ if (lineSpan) entry.integrityLineRange = lineSpan;
126
+ else entry.integrityInline = true;
127
+ }
128
+
129
+ return entry;
130
+ }
131
+
132
+ /** The whole-lines span covering `[start, end)` plus its trailing newline, or
133
+ * `undefined` when the span shares a line with other content — only leading
134
+ * indentation may precede it and nothing but spacing may follow. A flow-style
135
+ * map (`{source: …, integrity: …}`) fails this test, which is what keeps a
136
+ * line splice from eating a sibling field. */
137
+ function wholeLineSpan(
138
+ start: number,
139
+ end: number,
140
+ text: string,
141
+ lineOffsets: number[],
142
+ ): Range | undefined {
143
+ const lineStart = lineOffsets[offsetToPosition(start, lineOffsets).line];
144
+ if (text.slice(lineStart, start).trim() !== "") return undefined;
145
+
146
+ const nextNewline = text.indexOf("\n", end);
147
+ const lineEnd = nextNewline === -1 ? text.length : nextNewline + 1;
148
+ if (text.slice(end, nextNewline === -1 ? text.length : nextNewline).trim() !== "") {
149
+ return undefined;
150
+ }
151
+
152
+ return {
153
+ start: offsetToPosition(lineStart, lineOffsets),
154
+ end: offsetToPosition(lineEnd, lineOffsets),
155
+ };
156
+ }
157
+
158
+ function toRange(node: AstNode, lineOffsets: number[]): Range {
159
+ return {
160
+ start: offsetToPosition(node.range[0], lineOffsets),
161
+ end: offsetToPosition(node.range[1], lineOffsets),
162
+ };
163
+ }
164
+
165
+ function mapGet(node: AstMap, key: string): AstNode | undefined {
166
+ return node.entries.find((p) => p.key.kind === "scalar" && p.key.value === key)?.value;
167
+ }
168
+
169
+ /** The node's value when it is a plain (untagged) string scalar. A `!cel` /
170
+ * `!ref` scalar resolves to a sentinel object, not a string, so it falls out
171
+ * here — an import source is never an expression. */
172
+ function scalarString(node: AstNode | undefined): string | undefined {
173
+ if (!node || node.kind !== "scalar") return undefined;
174
+ return typeof node.value === "string" ? node.value : undefined;
175
+ }
@@ -0,0 +1,10 @@
1
+ export { buildImportUpgrades } from "./build-import-upgrades.js";
2
+ export type {
3
+ ImportUpgrade,
4
+ ImportUpgradeEdit,
5
+ ImportUpgradeSet,
6
+ ImportUpgradeSkip,
7
+ ModuleVersionLookup,
8
+ } from "./build-import-upgrades.js";
9
+ export { findImportEntries } from "./find-import-entries.js";
10
+ export type { ImportEntry, ImportsBlock } from "./find-import-entries.js";
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./diagnostics/index.js";
4
4
  export * from "./hover/index.js";
5
5
  export * from "./semantic-tokens/index.js";
6
6
  export * from "./definition/index.js";
7
+ export * from "./import-upgrades/index.js";