@telorun/ide-support 0.8.0 → 0.10.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 +3 -1
- package/dist/completions/import-source.js +2 -2
- package/dist/completions/resolve-node.d.ts +5 -0
- package/dist/completions/resolve-node.d.ts.map +1 -1
- package/dist/completions/resolve-node.js +4 -0
- package/dist/definition/alias-qualified-value.d.ts +23 -0
- package/dist/definition/alias-qualified-value.d.ts.map +1 -0
- package/dist/definition/alias-qualified-value.js +24 -0
- package/dist/definition/build-definition.d.ts +15 -8
- package/dist/definition/build-definition.d.ts.map +1 -1
- package/dist/definition/build-definition.js +35 -89
- package/dist/definition/manifest-navigation.d.ts +36 -0
- package/dist/definition/manifest-navigation.d.ts.map +1 -0
- package/dist/definition/manifest-navigation.js +92 -0
- package/dist/definition/resolve-cel-target.d.ts +17 -0
- package/dist/definition/resolve-cel-target.d.ts.map +1 -0
- package/dist/definition/resolve-cel-target.js +125 -0
- package/dist/definition/resolve-export-chain.d.ts +23 -0
- package/dist/definition/resolve-export-chain.d.ts.map +1 -0
- package/dist/definition/resolve-export-chain.js +89 -0
- package/dist/definition/resolve-kind-target.d.ts +26 -0
- package/dist/definition/resolve-kind-target.d.ts.map +1 -0
- package/dist/definition/resolve-kind-target.js +51 -0
- package/dist/definition/resolve-ref-target.d.ts +14 -0
- package/dist/definition/resolve-ref-target.d.ts.map +1 -0
- package/dist/definition/resolve-ref-target.js +25 -0
- package/dist/import-upgrades/build-import-upgrades.d.ts +46 -9
- package/dist/import-upgrades/build-import-upgrades.d.ts.map +1 -1
- package/dist/import-upgrades/build-import-upgrades.js +92 -27
- package/dist/import-upgrades/find-import-entries.d.ts +17 -11
- package/dist/import-upgrades/find-import-entries.d.ts.map +1 -1
- package/dist/import-upgrades/find-import-entries.js +4 -5
- package/dist/import-upgrades/index.d.ts +3 -2
- package/dist/import-upgrades/index.d.ts.map +1 -1
- package/dist/import-upgrades/index.js +1 -0
- package/dist/import-upgrades/parse-module-versions.d.ts +19 -0
- package/dist/import-upgrades/parse-module-versions.d.ts.map +1 -0
- package/dist/import-upgrades/parse-module-versions.js +29 -0
- package/package.json +2 -2
- package/src/completions/import-source.ts +2 -2
- package/src/completions/resolve-node.ts +9 -0
- package/src/definition/alias-qualified-value.ts +42 -0
- package/src/definition/build-definition.ts +34 -104
- package/src/definition/manifest-navigation.ts +124 -0
- package/src/definition/resolve-cel-target.ts +151 -0
- package/src/definition/resolve-export-chain.ts +119 -0
- package/src/definition/resolve-kind-target.ts +61 -0
- package/src/definition/resolve-ref-target.ts +34 -0
- package/src/import-upgrades/build-import-upgrades.ts +150 -36
- package/src/import-upgrades/find-import-entries.ts +27 -19
- package/src/import-upgrades/index.ts +8 -1
- package/src/import-upgrades/parse-module-versions.ts +30 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { parseExportEntry, resolveExportedKinds, } from "@telorun/analyzer";
|
|
2
|
+
import { locateKindDefinition, locateResource, moduleDoc, moduleFiles, moduleName, } from "./manifest-navigation.js";
|
|
3
|
+
/** One `exports.*` list as authored. `undefined` means the block is ABSENT,
|
|
4
|
+
* which is not the same as an empty list — the distinction is the gate itself,
|
|
5
|
+
* and it is load-bearing in both halves (`AliasResolver.registerImport`,
|
|
6
|
+
* `ModuleContext.buildExportTable`), so it must survive here too. */
|
|
7
|
+
function exportList(mod, block) {
|
|
8
|
+
const exports = moduleDoc(mod)?.exports;
|
|
9
|
+
const list = exports?.[block];
|
|
10
|
+
if (!Array.isArray(list))
|
|
11
|
+
return undefined;
|
|
12
|
+
return list.filter((e) => typeof e === "string");
|
|
13
|
+
}
|
|
14
|
+
/** Every loaded module keyed by its `metadata.name` — what `resolveExportedKinds`
|
|
15
|
+
* speaks, since a canonical kind names its owning module rather than a URL. */
|
|
16
|
+
function modulesByName(graph) {
|
|
17
|
+
const byName = new Map();
|
|
18
|
+
for (const mod of graph.modules.values()) {
|
|
19
|
+
const name = moduleName(mod);
|
|
20
|
+
if (name && !byName.has(name))
|
|
21
|
+
byName.set(name, mod);
|
|
22
|
+
}
|
|
23
|
+
return byName;
|
|
24
|
+
}
|
|
25
|
+
/** Resolve the kind suffix `name` exported by the module at `targetSource` to
|
|
26
|
+
* the `Telo.Definition` / `Telo.Abstract` doc that owns it.
|
|
27
|
+
*
|
|
28
|
+
* The gate and the transitive re-export chain come from the analyzer's own
|
|
29
|
+
* `resolveExportedKinds` fixpoint rather than a local walk, so navigation
|
|
30
|
+
* cannot disagree with `telo check` about what an import exposes — including
|
|
31
|
+
* the two rules a hand-rolled walk gets wrong: `exports.kinds: []` gates
|
|
32
|
+
* everything while an absent block gates nothing, and a re-export FROM an
|
|
33
|
+
* ungated module resolves straight to it. */
|
|
34
|
+
export function resolveExportedKind(graph, targetSource, name) {
|
|
35
|
+
const target = graph.modules.get(targetSource);
|
|
36
|
+
if (!target)
|
|
37
|
+
return undefined;
|
|
38
|
+
// No `exports.kinds` block → every kind the module defines is importable (the
|
|
39
|
+
// legacy permissive default the kernel still honors for already-published
|
|
40
|
+
// versions). The fixpoint builds tables only from declared entries, so an
|
|
41
|
+
// ungated module has none to look this up in — and this is also the cheap
|
|
42
|
+
// path, so it settles before anything is indexed.
|
|
43
|
+
if (exportList(target, "kinds") === undefined) {
|
|
44
|
+
return locateKindDefinition(moduleFiles(target), name);
|
|
45
|
+
}
|
|
46
|
+
const targetName = moduleName(target);
|
|
47
|
+
if (!targetName)
|
|
48
|
+
return undefined;
|
|
49
|
+
const byName = modulesByName(graph);
|
|
50
|
+
const tables = resolveExportedKinds([...byName].map(([module, mod]) => ({ module, exportsKinds: exportList(mod, "kinds") })), (module, alias) => {
|
|
51
|
+
const owner = byName.get(module)?.owner.source;
|
|
52
|
+
return owner ? graph.importEdges.get(owner)?.get(alias)?.targetModuleName ?? undefined : undefined;
|
|
53
|
+
});
|
|
54
|
+
const canonical = tables.get(targetName)?.get(name);
|
|
55
|
+
if (!canonical)
|
|
56
|
+
return undefined;
|
|
57
|
+
// `<owningModule>.<Kind>` — a re-export resolves to its true owner, so the
|
|
58
|
+
// jump lands where the kind is actually declared, however many hops away.
|
|
59
|
+
const dot = canonical.lastIndexOf(".");
|
|
60
|
+
const owner = byName.get(canonical.slice(0, dot));
|
|
61
|
+
return owner ? locateKindDefinition(moduleFiles(owner), canonical.slice(dot + 1)) : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Resolve the instance `name` exported by the module at `targetSource`,
|
|
64
|
+
* following `<Alias>.<name>` re-exports transitively. `seen` bounds cyclic
|
|
65
|
+
* import graphs.
|
|
66
|
+
*
|
|
67
|
+
* Unlike kinds there is no permissive default: the kernel reads
|
|
68
|
+
* `exports.resources ?? []` and builds its export table strictly from that, so
|
|
69
|
+
* an absent block exports nothing and an unlisted name is an
|
|
70
|
+
* `UNRESOLVED_REFERENCE` — navigating to it would tell the author a wiring is
|
|
71
|
+
* real that the analyzer rejects. */
|
|
72
|
+
export function resolveExportedResource(graph, targetSource, name, seen = new Set()) {
|
|
73
|
+
if (seen.has(targetSource))
|
|
74
|
+
return undefined;
|
|
75
|
+
seen.add(targetSource);
|
|
76
|
+
const target = graph.modules.get(targetSource);
|
|
77
|
+
if (!target)
|
|
78
|
+
return undefined;
|
|
79
|
+
const entries = (exportList(target, "resources") ?? []).map(parseExportEntry);
|
|
80
|
+
const entry = entries.find((e) => e.name === name);
|
|
81
|
+
if (!entry)
|
|
82
|
+
return undefined;
|
|
83
|
+
// `Self.<name>` names the declaring module's own instance, exactly as a bare
|
|
84
|
+
// name does — `ModuleContext.buildExportTable` treats the two alike.
|
|
85
|
+
if (!entry.alias || entry.alias === "Self")
|
|
86
|
+
return locateResource(moduleFiles(target), name);
|
|
87
|
+
const edge = graph.importEdges.get(target.owner.source)?.get(entry.alias);
|
|
88
|
+
return edge ? resolveExportedResource(graph, edge.targetSource, name, seen) : undefined;
|
|
89
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
/** Whether the cursor sits in a slot whose value is an alias-qualified kind.
|
|
6
|
+
*
|
|
7
|
+
* `x-telo-ref` is an `x-telo-*` annotation, unambiguous wherever it appears.
|
|
8
|
+
* `extends:` is only a kind at the top level of a definition doc, so it is
|
|
9
|
+
* gated on both. `kind:` is positional by nature: a map carrying a
|
|
10
|
+
* `<Alias>.<Kind>` value under `kind` IS an inline resource declaration as far
|
|
11
|
+
* as the analyzer is concerned too (`resourceKindOf`), so there is no further
|
|
12
|
+
* structure to check — an unresolvable value simply navigates nowhere.
|
|
13
|
+
*
|
|
14
|
+
* `capability:` is deliberately absent — its values are kernel built-ins with
|
|
15
|
+
* no manifest to jump to (hover documents them instead). */
|
|
16
|
+
export declare function isKindSlot(resolved: ResolvedCursor): boolean;
|
|
17
|
+
/** Resolve an alias-qualified kind (`Http.Server`) to the `Telo.Definition` /
|
|
18
|
+
* `Telo.Abstract` doc that registers it, or — when the cursor sits on the
|
|
19
|
+
* alias — to the import that declares the alias.
|
|
20
|
+
*
|
|
21
|
+
* `Self.<Kind>` is the declaring module's own kind; `Telo.<Kind>` is a kernel
|
|
22
|
+
* built-in with no manifest, so it resolves to nothing. Across an import the
|
|
23
|
+
* walk honors `exports.kinds` and follows `<Inner>.<Kind>` re-exports to the
|
|
24
|
+
* owning module, matching what the kernel resolves the kind to at runtime. */
|
|
25
|
+
export declare function resolveKindTarget(graph: LoadedGraph, currentModule: LoadedModule, kind: AliasQualifiedValue): DefinitionResult | undefined;
|
|
26
|
+
//# sourceMappingURL=resolve-kind-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-kind-target.d.ts","sourceRoot":"","sources":["../../src/definition/resolve-kind-target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMtE;;;;;;;;;;6DAU6D;AAC7D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAQ5D;AAED;;;;;;;+EAO+E;AAC/E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,YAAY,EAC3B,IAAI,EAAE,mBAAmB,GACxB,gBAAgB,GAAG,SAAS,CAkB9B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { locateImport, locateKindDefinition, moduleFiles } from "./manifest-navigation.js";
|
|
2
|
+
import { resolveExportedKind } from "./resolve-export-chain.js";
|
|
3
|
+
const DEFINITION_DOC_KINDS = new Set(["Telo.Definition", "Telo.Abstract"]);
|
|
4
|
+
/** Whether the cursor sits in a slot whose value is an alias-qualified kind.
|
|
5
|
+
*
|
|
6
|
+
* `x-telo-ref` is an `x-telo-*` annotation, unambiguous wherever it appears.
|
|
7
|
+
* `extends:` is only a kind at the top level of a definition doc, so it is
|
|
8
|
+
* gated on both. `kind:` is positional by nature: a map carrying a
|
|
9
|
+
* `<Alias>.<Kind>` value under `kind` IS an inline resource declaration as far
|
|
10
|
+
* as the analyzer is concerned too (`resourceKindOf`), so there is no further
|
|
11
|
+
* structure to check — an unresolvable value simply navigates nowhere.
|
|
12
|
+
*
|
|
13
|
+
* `capability:` is deliberately absent — its values are kernel built-ins with
|
|
14
|
+
* no manifest to jump to (hover documents them instead). */
|
|
15
|
+
export function isKindSlot(resolved) {
|
|
16
|
+
const key = resolved.path[resolved.path.length - 1];
|
|
17
|
+
if (key === "x-telo-ref" || key === "kind")
|
|
18
|
+
return true;
|
|
19
|
+
return (key === "extends" &&
|
|
20
|
+
resolved.path.length === 1 &&
|
|
21
|
+
DEFINITION_DOC_KINDS.has(resolved.docKind ?? ""));
|
|
22
|
+
}
|
|
23
|
+
/** Resolve an alias-qualified kind (`Http.Server`) to the `Telo.Definition` /
|
|
24
|
+
* `Telo.Abstract` doc that registers it, or — when the cursor sits on the
|
|
25
|
+
* alias — to the import that declares the alias.
|
|
26
|
+
*
|
|
27
|
+
* `Self.<Kind>` is the declaring module's own kind; `Telo.<Kind>` is a kernel
|
|
28
|
+
* built-in with no manifest, so it resolves to nothing. Across an import the
|
|
29
|
+
* walk honors `exports.kinds` and follows `<Inner>.<Kind>` re-exports to the
|
|
30
|
+
* owning module, matching what the kernel resolves the kind to at runtime. */
|
|
31
|
+
export function resolveKindTarget(graph, currentModule, kind) {
|
|
32
|
+
const { alias, name, onAlias } = kind;
|
|
33
|
+
if (alias === undefined) {
|
|
34
|
+
// The legacy `<namespace>/<module>#<Kind>` identity form of `x-telo-ref`
|
|
35
|
+
// still resolves for already-published module versions, but it names a
|
|
36
|
+
// module this manifest need not import, so there is no alias to follow.
|
|
37
|
+
if (name.includes("#"))
|
|
38
|
+
return undefined;
|
|
39
|
+
return locateKindDefinition(moduleFiles(currentModule), name);
|
|
40
|
+
}
|
|
41
|
+
if (alias === "Self")
|
|
42
|
+
return locateKindDefinition(moduleFiles(currentModule), name);
|
|
43
|
+
if (alias === "Telo")
|
|
44
|
+
return undefined;
|
|
45
|
+
if (onAlias)
|
|
46
|
+
return locateImport(currentModule, alias);
|
|
47
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
|
|
48
|
+
if (!edge)
|
|
49
|
+
return undefined;
|
|
50
|
+
return resolveExportedKind(graph, edge.targetSource, name);
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
/** Resolve a `!ref` target to the resource instance it names.
|
|
5
|
+
*
|
|
6
|
+
* The grammar mirrors `resolveRefSentinels`: a bare name (or `Self.name`) is a
|
|
7
|
+
* local resource in the current module; `Alias.name` is an exported instance of
|
|
8
|
+
* the module the import `Alias` points at, followed transitively through
|
|
9
|
+
* re-exports and gated on each module's `exports.resources`. The alias half
|
|
10
|
+
* navigates to the import that declares it. Returns `undefined` when the target
|
|
11
|
+
* can't be found (a scope-local name, an instance the target does not export,
|
|
12
|
+
* or an import that failed to load). */
|
|
13
|
+
export declare function resolveRefTarget(graph: LoadedGraph, currentModule: LoadedModule, ref: AliasQualifiedValue): DefinitionResult | undefined;
|
|
14
|
+
//# sourceMappingURL=resolve-ref-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-ref-target.d.ts","sourceRoot":"","sources":["../../src/definition/resolve-ref-target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE;;;;;;;;yCAQyC;AACzC,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,YAAY,EAC3B,GAAG,EAAE,mBAAmB,GACvB,gBAAgB,GAAG,SAAS,CAc9B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { locateImport, locateResource, moduleFiles } from "./manifest-navigation.js";
|
|
2
|
+
import { resolveExportedResource } from "./resolve-export-chain.js";
|
|
3
|
+
/** Resolve a `!ref` target to the resource instance it names.
|
|
4
|
+
*
|
|
5
|
+
* The grammar mirrors `resolveRefSentinels`: a bare name (or `Self.name`) is a
|
|
6
|
+
* local resource in the current module; `Alias.name` is an exported instance of
|
|
7
|
+
* the module the import `Alias` points at, followed transitively through
|
|
8
|
+
* re-exports and gated on each module's `exports.resources`. The alias half
|
|
9
|
+
* navigates to the import that declares it. Returns `undefined` when the target
|
|
10
|
+
* can't be found (a scope-local name, an instance the target does not export,
|
|
11
|
+
* or an import that failed to load). */
|
|
12
|
+
export function resolveRefTarget(graph, currentModule, ref) {
|
|
13
|
+
const { alias, name, onAlias } = ref;
|
|
14
|
+
if (alias === undefined || alias === "Self") {
|
|
15
|
+
// `Self` names the declaring module itself — there is no import to jump to,
|
|
16
|
+
// so the alias half resolves like the name half.
|
|
17
|
+
return locateResource(moduleFiles(currentModule), name);
|
|
18
|
+
}
|
|
19
|
+
if (onAlias)
|
|
20
|
+
return locateImport(currentModule, alias);
|
|
21
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
|
|
22
|
+
if (!edge)
|
|
23
|
+
return undefined;
|
|
24
|
+
return resolveExportedResource(graph, edge.targetSource, name);
|
|
25
|
+
}
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { type AstDocument, type Range } from "@telorun/analyzer";
|
|
2
|
+
/** One version of a module, as the hub reports it.
|
|
3
|
+
*
|
|
4
|
+
* `integrity` is the import pin for exactly this version (`sha256-<base64url>`
|
|
5
|
+
* over whatever the owning transport verifies its own reads against). It is
|
|
6
|
+
* absent for a version the hub tracked before it recorded pins, and for a ref
|
|
7
|
+
* no transport can hash — never an error, just no pin to write. */
|
|
8
|
+
export interface ModuleVersion {
|
|
9
|
+
version: string;
|
|
10
|
+
integrity?: string;
|
|
11
|
+
}
|
|
2
12
|
/** Version enumeration for one version-independent base ref, newest first.
|
|
3
13
|
*
|
|
4
14
|
* Narrower than the full `IdeEnvironmentAdapter` on purpose. It is the only
|
|
5
15
|
* environment capability an upgrade check needs, and a host that caches or
|
|
6
16
|
* throttles hub traffic wraps just this — a CodeLens re-resolves far more
|
|
7
17
|
* often than a completion popup opens, so the refresh cadence is the host's
|
|
8
|
-
* policy, not this module's.
|
|
9
|
-
*
|
|
10
|
-
|
|
18
|
+
* policy, not this module's.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT `adapter.listVersionsForRef`, which answers `string[]`:
|
|
21
|
+
* completion offers names, an upgrade writes a pin, so the two want different
|
|
22
|
+
* things from one route. A host backed by the hub fetches
|
|
23
|
+
* `GET /module/versions` and passes the body through
|
|
24
|
+
* {@link parseModuleVersions}. */
|
|
25
|
+
export type ModuleVersionLookup = (baseRef: string) => Promise<ModuleVersion[]>;
|
|
11
26
|
/** A source edit a host applies verbatim to upgrade an import. Ranges never
|
|
12
27
|
* overlap, within an upgrade or across a batch, so a host may apply the whole
|
|
13
28
|
* set in one pass without ordering them. */
|
|
@@ -22,18 +37,38 @@ export interface ImportUpgrade {
|
|
|
22
37
|
source: string;
|
|
23
38
|
currentVersion: string;
|
|
24
39
|
latestVersion: string;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
40
|
+
/** What the import resolves to after the edits, in the same folded form as
|
|
41
|
+
* `source` — re-pointed at `latestVersion`, carrying the new pin as a
|
|
42
|
+
* `#sha256-…` fragment when one was available. Folded rather than literal
|
|
43
|
+
* because where the pin physically lands depends on the shape the author
|
|
44
|
+
* wrote (fragment vs `integrity:` sibling), and a host showing this as a
|
|
45
|
+
* preview wants the resolved import, not one of two spellings of it. */
|
|
27
46
|
newSource: string;
|
|
28
|
-
/** True when the replaced import carried a pin.
|
|
29
|
-
* the hash should say so — the pin covers the version being replaced, and
|
|
30
|
-
* `telo upgrade` re-pins either shape. */
|
|
47
|
+
/** True when the replaced import carried a pin. */
|
|
31
48
|
wasPinned: boolean;
|
|
49
|
+
/** True when the edits leave the import pinned to `latestVersion`. False
|
|
50
|
+
* means no pin was available for the target version — a host should say so
|
|
51
|
+
* when `wasPinned`, since the rewrite silently drops a hash the author had. */
|
|
52
|
+
repinned: boolean;
|
|
32
53
|
/** Span of the alias key — where a per-entry affordance anchors. */
|
|
33
54
|
keyRange: Range;
|
|
34
55
|
/** Apply all of these to upgrade this one import. */
|
|
35
56
|
edits: ImportUpgradeEdit[];
|
|
36
57
|
}
|
|
58
|
+
/** One import that is already at the newest version but carries no integrity
|
|
59
|
+
* pin, and for which a pin is available. Mirrors what `telo upgrade` does with
|
|
60
|
+
* `ensurePinned`: a rarely-released module whose version never moves would
|
|
61
|
+
* otherwise stay unpinned forever, because nothing ever offers to rewrite it. */
|
|
62
|
+
export interface ImportPin {
|
|
63
|
+
alias: string;
|
|
64
|
+
source: string;
|
|
65
|
+
version: string;
|
|
66
|
+
/** The source that replaces it: unchanged but for the appended pin. */
|
|
67
|
+
newSource: string;
|
|
68
|
+
/** Span of the alias key — where a per-entry affordance anchors. */
|
|
69
|
+
keyRange: Range;
|
|
70
|
+
edits: ImportUpgradeEdit[];
|
|
71
|
+
}
|
|
37
72
|
/** An import that IS behind but that this module declines to rewrite. Carries
|
|
38
73
|
* the same anchor and versions an {@link ImportUpgrade} does, so a host can
|
|
39
74
|
* render it in place of the upgrade affordance rather than leaving the author
|
|
@@ -51,6 +86,7 @@ export interface ImportUpgradeSet {
|
|
|
51
86
|
/** Span of the `imports:` key — where a summary affordance anchors. */
|
|
52
87
|
importsKeyRange: Range;
|
|
53
88
|
upgrades: ImportUpgrade[];
|
|
89
|
+
pins: ImportPin[];
|
|
54
90
|
skipped: ImportUpgradeSkip[];
|
|
55
91
|
/** Base refs whose version lookup failed. Never thrown: one unreachable ref
|
|
56
92
|
* must not blank the affordances for every other import in the file. The
|
|
@@ -63,7 +99,8 @@ export interface ImportUpgradeSet {
|
|
|
63
99
|
/**
|
|
64
100
|
* Find every `imports:` entry of a module document that names a version older
|
|
65
101
|
* than the newest one `listVersions` reports, and produce the source edits that
|
|
66
|
-
* re-point it
|
|
102
|
+
* re-point it — plus every entry already at the newest version that carries no
|
|
103
|
+
* integrity pin, and the edits that pin it.
|
|
67
104
|
*
|
|
68
105
|
* Skips what carries no upgradeable version: local path imports, bare URLs,
|
|
69
106
|
* untagged refs, and pins that are not SemVer (an OCI digest, a moving tag like
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-import-upgrades.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/build-import-upgrades.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"build-import-upgrades.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/build-import-upgrades.ts"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,WAAW,EAChB,KAAK,KAAK,EACX,MAAM,mBAAmB,CAAC;AAG3B;;;;;oEAKoE;AACpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;mCAYmC;AACnC,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AAEhF;;6CAE6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;6EAKyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB;;oFAEgF;IAChF,QAAQ,EAAE,OAAO,CAAC;IAClB,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB,qDAAqD;IACrD,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED;;;kFAGkF;AAClF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED;;;yDAGyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,eAAe,EAAE,KAAK,CAAC;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B;;wDAEoD;IACpD,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,mBAAmB,EACjC,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAmEvC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { buildLineOffsets, isLocalPathSource, isNewerModuleVersion, newestModuleVersion, parseToAst, parseVersionedRef, withRefVersion, } from "@telorun/analyzer";
|
|
1
|
+
import { buildLineOffsets, foldIntegrity, isCanonicalIntegrity, isLocalPathSource, isNewerModuleVersion, isSameModuleVersion, newestModuleVersion, parseModuleVersion, parseToAst, parseVersionedRef, withRefVersion, } from "@telorun/analyzer";
|
|
2
2
|
import { findImportEntries } from "./find-import-entries.js";
|
|
3
3
|
/**
|
|
4
4
|
* Find every `imports:` entry of a module document that names a version older
|
|
5
5
|
* than the newest one `listVersions` reports, and produce the source edits that
|
|
6
|
-
* re-point it
|
|
6
|
+
* re-point it — plus every entry already at the newest version that carries no
|
|
7
|
+
* integrity pin, and the edits that pin it.
|
|
7
8
|
*
|
|
8
9
|
* Skips what carries no upgradeable version: local path imports, bare URLs,
|
|
9
10
|
* untagged refs, and pins that are not SemVer (an OCI digest, a moving tag like
|
|
@@ -26,70 +27,134 @@ export async function buildImportUpgrades(text, listVersions, docs) {
|
|
|
26
27
|
return ref ? [{ entry, ref }] : [];
|
|
27
28
|
});
|
|
28
29
|
const failures = [];
|
|
29
|
-
const
|
|
30
|
+
const known = await resolveVersions([...new Set(candidates.map((c) => c.ref.baseRef))], listVersions, failures);
|
|
30
31
|
const upgrades = [];
|
|
32
|
+
const pins = [];
|
|
31
33
|
const skipped = [];
|
|
32
34
|
for (const { entry, ref } of candidates) {
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
35
|
+
const versions = known.get(ref.baseRef);
|
|
36
|
+
if (!versions)
|
|
35
37
|
continue;
|
|
36
|
-
|
|
38
|
+
const newest = newestModuleVersion(versions.map((v) => v.version));
|
|
39
|
+
if (!newest)
|
|
40
|
+
continue;
|
|
41
|
+
if (!isNewerModuleVersion(newest, ref.version)) {
|
|
42
|
+
const pin = pinInPlace(entry, ref.version, ref.integrity, versions);
|
|
43
|
+
if (pin)
|
|
44
|
+
pins.push(pin);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const integrity = integrityFor(versions, newest);
|
|
48
|
+
// A stale pin that can be neither replaced nor removed is the one case left
|
|
49
|
+
// that has to be declined: re-pointing the source while leaving a hash for
|
|
50
|
+
// the version it replaced turns the next install into a tamper error.
|
|
51
|
+
if (ref.integrity && !integrity && entry.integrity && !entry.integrity.lineRange) {
|
|
37
52
|
skipped.push({
|
|
38
53
|
alias: entry.alias,
|
|
39
54
|
currentVersion: ref.version,
|
|
40
55
|
latestVersion: newest,
|
|
41
56
|
keyRange: entry.keyRange,
|
|
42
57
|
reason: `'${entry.alias}' carries an inline 'integrity:' that shares a line with other ` +
|
|
43
|
-
`fields,
|
|
58
|
+
`fields, and no pin is published for ${newest}, so the stale one cannot be ` +
|
|
59
|
+
`removed by a line edit. Run \`telo upgrade\`.`,
|
|
44
60
|
});
|
|
45
61
|
continue;
|
|
46
62
|
}
|
|
63
|
+
const newSource = withRefVersion(entry.source, newest);
|
|
47
64
|
upgrades.push({
|
|
48
65
|
alias: entry.alias,
|
|
49
66
|
source: entry.source,
|
|
50
67
|
currentVersion: ref.version,
|
|
51
68
|
latestVersion: newest,
|
|
52
|
-
newSource:
|
|
69
|
+
newSource: foldIntegrity(newSource, integrity),
|
|
53
70
|
wasPinned: ref.integrity != null,
|
|
71
|
+
repinned: integrity != null,
|
|
54
72
|
keyRange: entry.keyRange,
|
|
55
|
-
edits: buildEdits(entry,
|
|
73
|
+
edits: buildEdits(entry, newSource, integrity),
|
|
56
74
|
});
|
|
57
75
|
}
|
|
58
|
-
return { importsKeyRange: block.keyRange, upgrades, skipped, failures };
|
|
76
|
+
return { importsKeyRange: block.keyRange, upgrades, pins, skipped, failures };
|
|
59
77
|
}
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
78
|
+
/** The edits that re-point an entry at `newSource` and settle its pin.
|
|
79
|
+
*
|
|
80
|
+
* Where the pin is written follows the shape the author chose: an entry with an
|
|
81
|
+
* `integrity:` sibling keeps it (its value is replaced in place, which works in
|
|
82
|
+
* block and flow style alike), and everything else carries the pin inside the
|
|
83
|
+
* source as a `#sha256-…` fragment — the form `telo upgrade` writes.
|
|
84
|
+
*
|
|
85
|
+
* With no pin available the sibling is deleted instead. That is not optional:
|
|
86
|
+
* it hashes the `telo.yaml` of the version being replaced, so carrying it onto
|
|
87
|
+
* a different version turns the next install into a tamper error.
|
|
88
|
+
* `withRefVersion` has already stripped a fragment-form pin for the same
|
|
89
|
+
* reason, so the scalar shorthand needs no second edit. */
|
|
90
|
+
function buildEdits(entry, newSource, integrity) {
|
|
91
|
+
if (!entry.integrity) {
|
|
92
|
+
return [{ range: entry.sourceRange, newText: foldIntegrity(newSource, integrity) }];
|
|
93
|
+
}
|
|
66
94
|
const edits = [{ range: entry.sourceRange, newText: newSource }];
|
|
67
|
-
if (
|
|
68
|
-
edits.push({ range: entry.
|
|
95
|
+
if (integrity) {
|
|
96
|
+
edits.push({ range: entry.integrity.valueRange, newText: integrity });
|
|
97
|
+
}
|
|
98
|
+
else if (entry.integrity.lineRange) {
|
|
99
|
+
edits.push({ range: entry.integrity.lineRange, newText: "" });
|
|
69
100
|
}
|
|
70
101
|
return edits;
|
|
71
102
|
}
|
|
72
|
-
/**
|
|
73
|
-
* is
|
|
103
|
+
/** Pin an entry that is already at the newest version, or `undefined` when
|
|
104
|
+
* there is nothing to do — it is pinned already, the hub published no pin for
|
|
105
|
+
* the version it names, or that version is not one this module will order.
|
|
106
|
+
*
|
|
107
|
+
* The version gate matters: a moving tag (`latest`) or a digest names bytes
|
|
108
|
+
* that are expected to change, so pinning it would break the next release
|
|
109
|
+
* rather than protect it. `telo upgrade` draws the same line. */
|
|
110
|
+
function pinInPlace(entry, version, existingIntegrity, versions) {
|
|
111
|
+
if (existingIntegrity != null || entry.integrity)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (parseModuleVersion(version) === null)
|
|
114
|
+
return undefined;
|
|
115
|
+
const integrity = integrityFor(versions, version);
|
|
116
|
+
if (!integrity)
|
|
117
|
+
return undefined;
|
|
118
|
+
const newSource = foldIntegrity(entry.source, integrity);
|
|
119
|
+
return {
|
|
120
|
+
alias: entry.alias,
|
|
121
|
+
source: entry.source,
|
|
122
|
+
version,
|
|
123
|
+
newSource,
|
|
124
|
+
keyRange: entry.keyRange,
|
|
125
|
+
edits: [{ range: entry.sourceRange, newText: newSource }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** The pin the hub reports for one version, matched by SemVer identity rather
|
|
129
|
+
* than string equality so a `v`-prefixed tag on either side still lines up.
|
|
130
|
+
*
|
|
131
|
+
* Re-checked here even though the host's parse already did: this value is
|
|
132
|
+
* spliced into the author's YAML, and a caller reaching the builder through
|
|
133
|
+
* its own `ModuleVersionLookup` never passed through that parse. */
|
|
134
|
+
function integrityFor(versions, version) {
|
|
135
|
+
const integrity = versions.find((v) => isSameModuleVersion(v.version, version))?.integrity;
|
|
136
|
+
return isCanonicalIntegrity(integrity) ? integrity : undefined;
|
|
137
|
+
}
|
|
138
|
+
/** Versions per base ref, fetched once each. A ref whose lookup rejects is
|
|
139
|
+
* recorded in `failures` and left out of the map, so it yields no upgrade
|
|
74
140
|
* rather than a wrong one. */
|
|
75
|
-
async function
|
|
141
|
+
async function resolveVersions(baseRefs, listVersions, failures) {
|
|
76
142
|
const results = await Promise.all(baseRefs.map(async (baseRef) => {
|
|
77
143
|
try {
|
|
78
|
-
|
|
79
|
-
return { baseRef, newest: newestModuleVersion(versions) };
|
|
144
|
+
return { baseRef, versions: await listVersions(baseRef) };
|
|
80
145
|
}
|
|
81
146
|
catch (err) {
|
|
82
147
|
failures.push({
|
|
83
148
|
baseRef,
|
|
84
149
|
message: err instanceof Error ? err.message : String(err),
|
|
85
150
|
});
|
|
86
|
-
return { baseRef,
|
|
151
|
+
return { baseRef, versions: undefined };
|
|
87
152
|
}
|
|
88
153
|
}));
|
|
89
154
|
const map = new Map();
|
|
90
|
-
for (const { baseRef,
|
|
91
|
-
if (
|
|
92
|
-
map.set(baseRef,
|
|
155
|
+
for (const { baseRef, versions } of results) {
|
|
156
|
+
if (versions)
|
|
157
|
+
map.set(baseRef, versions);
|
|
93
158
|
}
|
|
94
159
|
return map;
|
|
95
160
|
}
|
|
@@ -15,17 +15,23 @@ export interface ImportEntry {
|
|
|
15
15
|
* shorthand, the `source:` value for the object form. Replacing this span
|
|
16
16
|
* re-points the import. */
|
|
17
17
|
sourceRange: Range;
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
/** Where an object-form `integrity:` sibling sits, when the entry declares
|
|
19
|
+
* one. Absent for the scalar shorthand — there the pin rides inside
|
|
20
|
+
* `sourceRange` as a `#sha256-…` fragment — and for an unpinned entry. */
|
|
21
|
+
integrity?: ImportEntryIntegrity;
|
|
22
|
+
}
|
|
23
|
+
/** The two spans a caller needs to act on an object-form `integrity:`: one to
|
|
24
|
+
* re-pin it, one to remove it. They are separate because a flow-style map
|
|
25
|
+
* (`{source: …, integrity: …}`) supports the first and not the second. */
|
|
26
|
+
export interface ImportEntryIntegrity {
|
|
27
|
+
/** Span of the hash value itself. Replacing it re-pins the entry in the shape
|
|
28
|
+
* the author wrote, in any YAML style. */
|
|
29
|
+
valueRange: Range;
|
|
30
|
+
/** Whole-line span of the `integrity:` entry including its trailing newline,
|
|
31
|
+
* so a caller with no replacement hash can delete the line outright. Absent
|
|
32
|
+
* when the pair shares a line with other content, where removing it would
|
|
33
|
+
* need a structural rewrite rather than a line splice. */
|
|
34
|
+
lineRange?: Range;
|
|
29
35
|
}
|
|
30
36
|
/** Where the `imports:` map lives in a module document. */
|
|
31
37
|
export interface ImportsBlock {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-import-entries.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/find-import-entries.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,WAAW,EAGhB,KAAK,KAAK,EACX,MAAM,mBAAmB,CAAC;AAE3B;;;;;iEAKiE;AACjE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB;;gCAE4B;IAC5B,WAAW,EAAE,KAAK,CAAC;IACnB
|
|
1
|
+
{"version":3,"file":"find-import-entries.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/find-import-entries.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,WAAW,EAGhB,KAAK,KAAK,EACX,MAAM,mBAAmB,CAAC;AAE3B;;;;;iEAKiE;AACjE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,KAAK,CAAC;IAChB;;gCAE4B;IAC5B,WAAW,EAAE,KAAK,CAAC;IACnB;;+EAE2E;IAC3E,SAAS,CAAC,EAAE,oBAAoB,CAAC;CAClC;AAED;;2EAE2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC;+CAC2C;IAC3C,UAAU,EAAE,KAAK,CAAC;IAClB;;;+DAG2D;IAC3D,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,QAAQ,EAAE,KAAK,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED;;;;;;;qEAOqE;AACrE,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EAAE,EACnB,WAAW,EAAE,MAAM,EAAE,GACpB,YAAY,GAAG,SAAS,CAsB1B"}
|
|
@@ -56,11 +56,10 @@ function readEntry(alias, keyNode, valueNode, text, lineOffsets) {
|
|
|
56
56
|
sourceRange: toRange(sourceNode, lineOffsets),
|
|
57
57
|
};
|
|
58
58
|
if (integrityPair?.value && integrity !== undefined) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
entry.integrityInline = true;
|
|
59
|
+
entry.integrity = {
|
|
60
|
+
valueRange: toRange(integrityPair.value, lineOffsets),
|
|
61
|
+
lineRange: wholeLineSpan(integrityPair.key.range[0], integrityPair.value.range[1], text, lineOffsets),
|
|
62
|
+
};
|
|
64
63
|
}
|
|
65
64
|
return entry;
|
|
66
65
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { buildImportUpgrades } from "./build-import-upgrades.js";
|
|
2
|
-
export type { ImportUpgrade, ImportUpgradeEdit, ImportUpgradeSet, ImportUpgradeSkip, ModuleVersionLookup, } from "./build-import-upgrades.js";
|
|
2
|
+
export type { ImportPin, ImportUpgrade, ImportUpgradeEdit, ImportUpgradeSet, ImportUpgradeSkip, ModuleVersion, ModuleVersionLookup, } from "./build-import-upgrades.js";
|
|
3
|
+
export { parseModuleVersions } from "./parse-module-versions.js";
|
|
3
4
|
export { findImportEntries } from "./find-import-entries.js";
|
|
4
|
-
export type { ImportEntry, ImportsBlock } from "./find-import-entries.js";
|
|
5
|
+
export type { ImportEntry, ImportEntryIntegrity, ImportsBlock, } from "./find-import-entries.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EACV,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,YAAY,GACb,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ModuleVersion } from "./build-import-upgrades.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read the hub's `GET /module/versions` payload into {@link ModuleVersion}s,
|
|
4
|
+
* newest first (the route's own ordering, preserved).
|
|
5
|
+
*
|
|
6
|
+
* Pure — the host owns the `fetch`, as everything else in this package does.
|
|
7
|
+
* It lives here rather than in each host because the shape is this package's:
|
|
8
|
+
* three hosts parsing the same route by hand is how one of them was left
|
|
9
|
+
* filtering for strings after the route started returning objects, which
|
|
10
|
+
* TypeScript could not catch behind the response cast and which surfaced only
|
|
11
|
+
* as a silently empty version list.
|
|
12
|
+
*
|
|
13
|
+
* Tolerant by design: an entry with no usable `version` is dropped rather than
|
|
14
|
+
* throwing, and an `integrity` that is not a canonical `sha256-<base64url>` is
|
|
15
|
+
* discarded rather than carried — a pin is spliced into the author's YAML, so a
|
|
16
|
+
* malformed one has to become "no pin", never corrupt text.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseModuleVersions(payload: unknown): ModuleVersion[];
|
|
19
|
+
//# sourceMappingURL=parse-module-versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-module-versions.d.ts","sourceRoot":"","sources":["../../src/import-upgrades/parse-module-versions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,CAUrE"}
|