@tsdoctor/bundle 0.2.3 → 0.3.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/Bundle.js +2 -8
- package/BundleAssets.js +130 -0
- package/BundleDiscovery.js +2 -1
- package/BundleFetch.js +38 -0
- package/PlatformOverrides.js +1 -1
- package/index.d.ts +352 -208
- package/index.js +4 -3
- package/internal/asset-path.js +29 -0
- package/package.json +4 -2
- package/BundleManifest.js +0 -183
package/Bundle.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { BundleManifestError, decodeBundleManifest } from "
|
|
1
|
+
import { BundleManifestError, decodeBundleManifest } from "@tsdoctor/manifest";
|
|
2
2
|
import { PackageJsonFile } from "@effected/package-json";
|
|
3
3
|
import { TsconfigLoader } from "@effected/tsconfig-json";
|
|
4
4
|
import { Effect, FileSystem, Option, Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
//#region src/Bundle.ts
|
|
7
|
-
/**
|
|
8
|
-
* The sidecar manifest's file name inside a bundle folder.
|
|
9
|
-
*
|
|
10
|
-
* @public
|
|
11
|
-
*/
|
|
12
|
-
const TSDOCTOR_MANIFEST_FILENAME = "tsdoctor.json";
|
|
13
7
|
/** The minimal api.json shape the layer-0 reader validates. */
|
|
14
8
|
const ApiModelHeader = Schema.Struct({ name: Schema.String });
|
|
15
9
|
/**
|
|
@@ -124,4 +118,4 @@ function readBundle(descriptor) {
|
|
|
124
118
|
}
|
|
125
119
|
|
|
126
120
|
//#endregion
|
|
127
|
-
export { BundleLayerError,
|
|
121
|
+
export { BundleLayerError, readApiModelInfo, readBundle };
|
package/BundleAssets.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { isSafeAssetPath } from "./internal/asset-path.js";
|
|
2
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
3
|
+
import { imageSize } from "image-size";
|
|
4
|
+
|
|
5
|
+
//#region src/BundleAssets.ts
|
|
6
|
+
/**
|
|
7
|
+
* Publishing a resolved bundle's Open Graph images into a site's public
|
|
8
|
+
* directory.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* `resolveBundle` produces `ResolvedOpenGraphImage` values whose `path` field
|
|
12
|
+
* (when present) is bundle-relative — a location inside the bundle directory,
|
|
13
|
+
* not the site's own output tree. An adapter must copy that file somewhere a
|
|
14
|
+
* built site actually serves and hand back an absolute URL a `<head>` tag can
|
|
15
|
+
* use; `url` images need no copy and pass straight through.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* A bundle-relative Open Graph image could not be published: the source file
|
|
21
|
+
* could not be read, or the destination could not be written.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
var BundleAssetError = class extends Schema.TaggedError()("BundleAssetError", {
|
|
26
|
+
/** The source or destination path at fault. */
|
|
27
|
+
path: Schema.String,
|
|
28
|
+
cause: Schema.Defect()
|
|
29
|
+
}) {
|
|
30
|
+
get message() {
|
|
31
|
+
return `Could not publish bundle asset ${this.path}`;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const bytesEqual = (a, b) => a.byteLength === b.byteLength && a.every((value, index) => value === b[index]);
|
|
35
|
+
/** `imageSize` throws on bytes it cannot parse; that degrades to no measurement, never a failure. */
|
|
36
|
+
function safeSize(bytes) {
|
|
37
|
+
try {
|
|
38
|
+
const size = imageSize(bytes);
|
|
39
|
+
return {
|
|
40
|
+
...size.width !== void 0 ? { width: size.width } : {},
|
|
41
|
+
...size.height !== void 0 ? { height: size.height } : {}
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Copy every bundle-relative image into `<publicDir>/tsdoctor/<unscopedName>/`
|
|
49
|
+
* and return every image — bundle-relative or external — as a
|
|
50
|
+
* {@link PublishedOpenGraphImage} carrying an absolute URL.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Identical bytes are not rewritten, so a rebuild over an unchanged image
|
|
54
|
+
* leaves the published file's own metadata (mtime and any framework cache
|
|
55
|
+
* keyed on it) untouched. Width and height are read from the resolved image
|
|
56
|
+
* when the manifest declared them, and measured from the file's bytes only
|
|
57
|
+
* when it did not.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
const publishBundleAssets = Effect.fn("publishBundleAssets")(function* (input) {
|
|
62
|
+
const fs = yield* FileSystem.FileSystem;
|
|
63
|
+
const path = yield* Path.Path;
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const image of input.images) {
|
|
66
|
+
const common = {
|
|
67
|
+
...image.type !== void 0 ? { type: image.type.value } : {},
|
|
68
|
+
...image.width !== void 0 ? { width: image.width } : {},
|
|
69
|
+
...image.height !== void 0 ? { height: image.height } : {},
|
|
70
|
+
alt: image.alt.value
|
|
71
|
+
};
|
|
72
|
+
if (image.url !== void 0) {
|
|
73
|
+
out.push({
|
|
74
|
+
url: image.url,
|
|
75
|
+
...common
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (image.path === void 0) return yield* Effect.fail(new BundleAssetError({
|
|
80
|
+
path: input.bundleDir,
|
|
81
|
+
cause: "image has no path or url"
|
|
82
|
+
}));
|
|
83
|
+
if (!isSafeAssetPath(image.path)) return yield* Effect.fail(new BundleAssetError({
|
|
84
|
+
path: image.path,
|
|
85
|
+
cause: `openGraph image path escapes the bundle: "${image.path}"`
|
|
86
|
+
}));
|
|
87
|
+
const source = path.join(input.bundleDir, image.path);
|
|
88
|
+
const bytes = yield* fs.readFile(source).pipe(Effect.mapError((cause) => new BundleAssetError({
|
|
89
|
+
path: source,
|
|
90
|
+
cause
|
|
91
|
+
})));
|
|
92
|
+
const basename = path.basename(source);
|
|
93
|
+
const destSegments = [
|
|
94
|
+
input.publicDir,
|
|
95
|
+
"tsdoctor",
|
|
96
|
+
input.unscopedName,
|
|
97
|
+
...input.subdir ? [input.subdir] : []
|
|
98
|
+
];
|
|
99
|
+
const destDir = path.join(...destSegments);
|
|
100
|
+
const dest = path.join(destDir, basename);
|
|
101
|
+
const existing = yield* fs.readFile(dest).pipe(Effect.option);
|
|
102
|
+
if (existing._tag === "None" || !bytesEqual(existing.value, bytes)) {
|
|
103
|
+
yield* fs.makeDirectory(destDir, { recursive: true }).pipe(Effect.mapError((cause) => new BundleAssetError({
|
|
104
|
+
path: destDir,
|
|
105
|
+
cause
|
|
106
|
+
})));
|
|
107
|
+
yield* fs.writeFile(dest, bytes).pipe(Effect.mapError((cause) => new BundleAssetError({
|
|
108
|
+
path: dest,
|
|
109
|
+
cause
|
|
110
|
+
})));
|
|
111
|
+
}
|
|
112
|
+
const measured = image.width === void 0 || image.height === void 0 ? safeSize(bytes) : void 0;
|
|
113
|
+
const urlSegments = [
|
|
114
|
+
"tsdoctor",
|
|
115
|
+
input.unscopedName,
|
|
116
|
+
...input.subdir ? [input.subdir] : [],
|
|
117
|
+
basename
|
|
118
|
+
];
|
|
119
|
+
out.push({
|
|
120
|
+
url: `${input.siteUrl}/${urlSegments.join("/")}`,
|
|
121
|
+
...image.width === void 0 && measured?.width !== void 0 ? { width: measured.width } : {},
|
|
122
|
+
...image.height === void 0 && measured?.height !== void 0 ? { height: measured.height } : {},
|
|
123
|
+
...common
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { BundleAssetError, publishBundleAssets };
|
package/BundleDiscovery.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readApiModelInfo, readBundle } from "./Bundle.js";
|
|
2
|
+
import { TSDOCTOR_MANIFEST_FILENAME } from "@tsdoctor/manifest";
|
|
2
3
|
import { LenientManifest } from "@effected/package-json";
|
|
3
4
|
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
4
5
|
import { GlobPatternOptions } from "@effected/glob";
|
package/BundleFetch.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readBundle } from "./Bundle.js";
|
|
2
|
+
import { isSafeAssetPath } from "./internal/asset-path.js";
|
|
2
3
|
import { discoverBundle } from "./BundleDiscovery.js";
|
|
4
|
+
import { decodeBundleManifest } from "@tsdoctor/manifest";
|
|
3
5
|
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
4
6
|
import { GitHubRelease, Repo, RepoRef } from "@effected/github";
|
|
5
7
|
import { NpmRegistry, PackageTarball, PublishedVersion } from "@effected/npm";
|
|
@@ -31,6 +33,7 @@ var BundleFetchError = class extends Schema.TaggedError()("BundleFetchError", {
|
|
|
31
33
|
"assetAmbiguous",
|
|
32
34
|
"download",
|
|
33
35
|
"notABundle",
|
|
36
|
+
"missingAsset",
|
|
34
37
|
"cache"
|
|
35
38
|
]),
|
|
36
39
|
/** The remote coordinate: `name@version` or `owner/repo@tag#asset`. */
|
|
@@ -68,6 +71,21 @@ function validateSegments(source, ref, segments) {
|
|
|
68
71
|
}));
|
|
69
72
|
return Effect.void;
|
|
70
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The bundle-relative `openGraph.images[].path` entries a manifest declares,
|
|
76
|
+
* when the manifest at `manifestPath` exists and decodes. A manifest that
|
|
77
|
+
* fails to parse or decode here degrades to no assets — `readBundle` at the
|
|
78
|
+
* end of {@link persistAndRead}'s caller re-reads the cached manifest and
|
|
79
|
+
* surfaces the typed `BundleManifestError` itself; this probe must not fail
|
|
80
|
+
* the fetch on its own.
|
|
81
|
+
*/
|
|
82
|
+
function manifestImagePaths(manifestPath) {
|
|
83
|
+
return Effect.gen(function* () {
|
|
84
|
+
const text = yield* (yield* FileSystem.FileSystem).readFileString(manifestPath);
|
|
85
|
+
const parsed = yield* Effect.try(() => JSON.parse(text));
|
|
86
|
+
return ((yield* decodeBundleManifest(parsed, manifestPath)).openGraph?.images ?? []).map((image) => image.path).filter((imagePath) => imagePath !== void 0);
|
|
87
|
+
}).pipe(Effect.orElseSucceed(() => []));
|
|
88
|
+
}
|
|
71
89
|
/** The durable cache directory for one remote bundle coordinate. */
|
|
72
90
|
function bundleCacheDir(segments) {
|
|
73
91
|
return Effect.gen(function* () {
|
|
@@ -134,6 +152,26 @@ function persistAndRead(source, ref, key, extractedDir, cacheDir) {
|
|
|
134
152
|
yield* fs.copyFile(layerPath, path.join(cacheDir, fileName)).pipe(Effect.mapError(failCache));
|
|
135
153
|
files.push(fileName);
|
|
136
154
|
}
|
|
155
|
+
const assetPaths = discovered.manifestPath !== void 0 ? yield* manifestImagePaths(discovered.manifestPath) : [];
|
|
156
|
+
for (const assetPath of assetPaths) {
|
|
157
|
+
if (!isSafeAssetPath(assetPath)) return yield* Effect.fail(new BundleFetchError({
|
|
158
|
+
source,
|
|
159
|
+
reason: "invalidRef",
|
|
160
|
+
ref,
|
|
161
|
+
detail: `openGraph image path escapes the bundle: "${assetPath}"`
|
|
162
|
+
}));
|
|
163
|
+
const assetSource = path.join(extractedDir, assetPath);
|
|
164
|
+
if (!(yield* fs.exists(assetSource).pipe(Effect.orElseSucceed(() => false)))) return yield* Effect.fail(new BundleFetchError({
|
|
165
|
+
source,
|
|
166
|
+
reason: "missingAsset",
|
|
167
|
+
ref,
|
|
168
|
+
detail: `openGraph image declared at "${assetPath}" is not present in the fetched bundle`
|
|
169
|
+
}));
|
|
170
|
+
const assetDest = path.join(cacheDir, assetPath);
|
|
171
|
+
yield* fs.makeDirectory(path.dirname(assetDest), { recursive: true }).pipe(Effect.mapError(failCache));
|
|
172
|
+
yield* fs.copyFile(assetSource, assetDest).pipe(Effect.mapError(failCache));
|
|
173
|
+
files.push(assetPath);
|
|
174
|
+
}
|
|
137
175
|
yield* cache.set({
|
|
138
176
|
key,
|
|
139
177
|
value: utf8Encode(JSON.stringify({ files })),
|
package/PlatformOverrides.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BundleManifestError, OpenGraphConfig, RegistryRef, SbomRef } from "
|
|
1
|
+
import { BundleManifestError, OpenGraphConfig, RegistryRef, SbomRef } from "@tsdoctor/manifest";
|
|
2
2
|
import { Effect, Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/PlatformOverrides.ts
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { Crypto, Effect, FileSystem, Option, Path, PlatformError, Schema } from "effect";
|
|
1
2
|
import { PackageManifest } from "@effected/package-json";
|
|
2
3
|
import { CompilerOptions, ResolvedTsconfig } from "@effected/tsconfig-json";
|
|
3
|
-
import { Crypto, Effect, FileSystem, Option, Path, PlatformError, Schema } from "effect";
|
|
4
4
|
import { GitHubRelease } from "@effected/github";
|
|
5
5
|
import { NpmRegistry, PackageTarball, RegistryTarget } from "@effected/npm";
|
|
6
6
|
import { Cache } from "@effected/store";
|
|
7
7
|
import { AppDirs } from "@effected/xdg";
|
|
8
8
|
import { JsoncCanonicalizeError } from "@effected/jsonc";
|
|
9
|
+
//#region ../manifest/dist/dev/pkg/index.d.ts
|
|
9
10
|
//#region src/BundleManifest.d.ts
|
|
10
11
|
/**
|
|
11
12
|
* The registry protocol families this reader knows how to do more than link
|
|
@@ -266,14 +267,83 @@ declare class BundleManifestError extends BundleManifestError_base {
|
|
|
266
267
|
* @public
|
|
267
268
|
*/
|
|
268
269
|
declare function decodeBundleManifest(input: unknown, path?: string): Effect.Effect<BundleManifest, BundleManifestError>;
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
/**
|
|
271
|
+
* The manifest spec version this package reads and writes.
|
|
272
|
+
*
|
|
273
|
+
* @public
|
|
274
|
+
*/
|
|
275
|
+
declare const MANIFEST_SPEC: 1;
|
|
271
276
|
/**
|
|
272
277
|
* The sidecar manifest's file name inside a bundle folder.
|
|
273
278
|
*
|
|
274
279
|
* @public
|
|
275
280
|
*/
|
|
276
281
|
declare const TSDOCTOR_MANIFEST_FILENAME = "tsdoctor.json";
|
|
282
|
+
/**
|
|
283
|
+
* Encode a {@link (BundleManifest:type)} into the JSON-ready value a writer
|
|
284
|
+
* serializes as `tsdoctor.json`.
|
|
285
|
+
*
|
|
286
|
+
* @remarks
|
|
287
|
+
* The writer's boundary. Going through the schema rather than
|
|
288
|
+
* `JSON.stringify` means an emitted file is by construction what
|
|
289
|
+
* {@link decodeBundleManifest} accepts.
|
|
290
|
+
*
|
|
291
|
+
* @public
|
|
292
|
+
*/
|
|
293
|
+
declare function encodeBundleManifest(manifest: BundleManifest): Effect.Effect<unknown, BundleManifestError>;
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/ManifestSource.d.ts
|
|
296
|
+
/**
|
|
297
|
+
* The shape an author checks in as a `tsdoctor.json` SOURCE file, at a
|
|
298
|
+
* package root (the leaf tier) or a workspace root (the project tier).
|
|
299
|
+
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* `BundleManifest` minus `spec` and `project`: a source file never declares
|
|
302
|
+
* its own spec version, and it never declares its inherited tier — the
|
|
303
|
+
* bundler supplies both when it flattens the hierarchy at emit time. Decoded
|
|
304
|
+
* only by writers; readers never see this shape.
|
|
305
|
+
*
|
|
306
|
+
* @public
|
|
307
|
+
*/
|
|
308
|
+
declare const ManifestSource: Schema.Struct<{
|
|
309
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
310
|
+
readonly tagline: Schema.optionalKey<Schema.String>;
|
|
311
|
+
readonly description: Schema.optionalKey<Schema.String>;
|
|
312
|
+
readonly openGraph: Schema.optionalKey<Schema.Struct<{
|
|
313
|
+
readonly images: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
314
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
315
|
+
readonly url: Schema.optionalKey<Schema.String>;
|
|
316
|
+
readonly type: Schema.optionalKey<Schema.String>;
|
|
317
|
+
readonly width: Schema.optionalKey<Schema.Int>;
|
|
318
|
+
readonly height: Schema.optionalKey<Schema.Int>;
|
|
319
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
320
|
+
}>>>;
|
|
321
|
+
readonly themeColor: Schema.optionalKey<Schema.String>;
|
|
322
|
+
}>>;
|
|
323
|
+
readonly sbom: Schema.optionalKey<Schema.Struct<{
|
|
324
|
+
readonly path: Schema.String;
|
|
325
|
+
readonly format: Schema.optionalKey<Schema.String>;
|
|
326
|
+
}>>;
|
|
327
|
+
readonly registries: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
328
|
+
readonly type: Schema.String;
|
|
329
|
+
readonly name: Schema.String;
|
|
330
|
+
readonly url: Schema.String;
|
|
331
|
+
}>>>;
|
|
332
|
+
}>;
|
|
333
|
+
/**
|
|
334
|
+
* The decoded type of {@link (ManifestSource:variable)}.
|
|
335
|
+
*
|
|
336
|
+
* @public
|
|
337
|
+
*/
|
|
338
|
+
type ManifestSource = typeof ManifestSource.Type;
|
|
339
|
+
/**
|
|
340
|
+
* Decode an unknown value into a {@link (ManifestSource:type)}.
|
|
341
|
+
*
|
|
342
|
+
* @public
|
|
343
|
+
*/
|
|
344
|
+
declare function decodeManifestSource(input: unknown, path?: string): Effect.Effect<ManifestSource, BundleManifestError>;
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/Bundle.d.ts
|
|
277
347
|
/**
|
|
278
348
|
* The subset of a `<name>.api.json` model this package reads: the package
|
|
279
349
|
* name from the model's own metadata.
|
|
@@ -387,6 +457,283 @@ declare function readApiModelInfo(modelPath: string): Effect.Effect<ApiModelInfo
|
|
|
387
457
|
*/
|
|
388
458
|
declare function readBundle(descriptor: BundleDescriptor): Effect.Effect<Bundle, BundleLayerError | BundleManifestError, FileSystem.FileSystem | Path.Path>;
|
|
389
459
|
//#endregion
|
|
460
|
+
//#region src/PlatformOverrides.d.ts
|
|
461
|
+
/**
|
|
462
|
+
* The `manifest.platform` tier: a data-override object a consumer passes
|
|
463
|
+
* through platform options (e.g. `ApiExtractorPlugin(options)`), sitting at
|
|
464
|
+
* the TOP of the tier ranking.
|
|
465
|
+
*
|
|
466
|
+
* @remarks
|
|
467
|
+
* Same field surface as the authored manifest tiers — name, tagline,
|
|
468
|
+
* description, openGraph, sbom, registries — with no `spec` field (it is not
|
|
469
|
+
* a file with an independent version) and no `project` block (it is a single
|
|
470
|
+
* tier, not a flattened hierarchy). Lets a user with ONLY an api.json declare
|
|
471
|
+
* identity/OG/registries declaratively; the resolver does the merging.
|
|
472
|
+
*
|
|
473
|
+
* @public
|
|
474
|
+
*/
|
|
475
|
+
declare const PlatformOverrides: Schema.Struct<{
|
|
476
|
+
/** Human display name override. */
|
|
477
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
478
|
+
/** Tagline override. */
|
|
479
|
+
readonly tagline: Schema.optionalKey<Schema.String>;
|
|
480
|
+
/** Description override. */
|
|
481
|
+
readonly description: Schema.optionalKey<Schema.String>;
|
|
482
|
+
/** Open Graph override. */
|
|
483
|
+
readonly openGraph: Schema.optionalKey<Schema.Struct<{
|
|
484
|
+
readonly images: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
485
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
486
|
+
readonly url: Schema.optionalKey<Schema.String>;
|
|
487
|
+
readonly type: Schema.optionalKey<Schema.String>;
|
|
488
|
+
readonly width: Schema.optionalKey<Schema.Int>;
|
|
489
|
+
readonly height: Schema.optionalKey<Schema.Int>;
|
|
490
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
491
|
+
}>>>;
|
|
492
|
+
readonly themeColor: Schema.optionalKey<Schema.String>;
|
|
493
|
+
}>>;
|
|
494
|
+
/** SBOM pointer override. */
|
|
495
|
+
readonly sbom: Schema.optionalKey<Schema.Struct<{
|
|
496
|
+
readonly path: Schema.String;
|
|
497
|
+
readonly format: Schema.optionalKey<Schema.String>;
|
|
498
|
+
}>>;
|
|
499
|
+
/** Registries override. */
|
|
500
|
+
readonly registries: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
501
|
+
readonly type: Schema.String;
|
|
502
|
+
readonly name: Schema.String;
|
|
503
|
+
readonly url: Schema.String;
|
|
504
|
+
}>>>;
|
|
505
|
+
}>;
|
|
506
|
+
/**
|
|
507
|
+
* The decoded type of {@link (PlatformOverrides:variable)}.
|
|
508
|
+
*
|
|
509
|
+
* @public
|
|
510
|
+
*/
|
|
511
|
+
type PlatformOverrides = typeof PlatformOverrides.Type;
|
|
512
|
+
/**
|
|
513
|
+
* Decode an unknown value into a {@link (PlatformOverrides:type)}.
|
|
514
|
+
*
|
|
515
|
+
* @remarks
|
|
516
|
+
* For adapters decoding raw platform options. Failures share
|
|
517
|
+
* {@link BundleManifestError} — the platform tier is manifest data by another
|
|
518
|
+
* route, and a caller handles both boundaries with one tag.
|
|
519
|
+
*
|
|
520
|
+
* @public
|
|
521
|
+
*/
|
|
522
|
+
declare function decodePlatformOverrides(input: unknown): Effect.Effect<PlatformOverrides, BundleManifestError>;
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/BundleResolver.d.ts
|
|
525
|
+
/**
|
|
526
|
+
* Where a resolved field's value came from, highest-ranked tier first.
|
|
527
|
+
*
|
|
528
|
+
* @remarks
|
|
529
|
+
* The first six values are the spec's tier ladder. `"tsconfig"` is this
|
|
530
|
+
* package's one addition: the spec passes tsconfig compiler options through
|
|
531
|
+
* as a resolved field but its ladder has no source that names the tsconfig
|
|
532
|
+
* layer, so the union carries one.
|
|
533
|
+
*
|
|
534
|
+
* @public
|
|
535
|
+
*/
|
|
536
|
+
type ProvenanceSource = "manifest.platform" | "manifest.leaf" | "manifest.project" | "packageJson" | "apiModel" | "tsconfig" | "inferred";
|
|
537
|
+
/**
|
|
538
|
+
* A resolved value carrying its provenance.
|
|
539
|
+
*
|
|
540
|
+
* @remarks
|
|
541
|
+
* Provenance is load-bearing: a field is user-overridden iff its source
|
|
542
|
+
* outranks the derivation that would otherwise supply it, an `inferred`
|
|
543
|
+
* field tracks upstream changes while an authored field is pinned, and the
|
|
544
|
+
* change-detection fingerprints hash value AND source together so an
|
|
545
|
+
* override flip is a visible diff.
|
|
546
|
+
*
|
|
547
|
+
* @public
|
|
548
|
+
*/
|
|
549
|
+
interface Provenanced<A> {
|
|
550
|
+
/** The resolved value. */
|
|
551
|
+
readonly value: A;
|
|
552
|
+
/** The tier that supplied it. */
|
|
553
|
+
readonly source: ProvenanceSource;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* One Open Graph image after resolution: authored fields passed through,
|
|
557
|
+
* `type` and `alt` filled by the documented inference rules when absent.
|
|
558
|
+
*
|
|
559
|
+
* @public
|
|
560
|
+
*/
|
|
561
|
+
interface ResolvedOpenGraphImage {
|
|
562
|
+
/** Bundle-relative asset path, when the image is bundle-supplied. */
|
|
563
|
+
readonly path?: string;
|
|
564
|
+
/** Absolute external URL, when the image is external. */
|
|
565
|
+
readonly url?: string;
|
|
566
|
+
/** MIME type — authored, or inferred from the file extension. */
|
|
567
|
+
readonly type?: Provenanced<string>;
|
|
568
|
+
/** Pixel width, as authored. */
|
|
569
|
+
readonly width?: number;
|
|
570
|
+
/** Pixel height, as authored. */
|
|
571
|
+
readonly height?: number;
|
|
572
|
+
/** Alt text — authored, or inferred (tagline → description → `"<name> API documentation"`); never empty. */
|
|
573
|
+
readonly alt: Provenanced<string>;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* The Open Graph block after resolution.
|
|
577
|
+
*
|
|
578
|
+
* @public
|
|
579
|
+
*/
|
|
580
|
+
interface ResolvedOpenGraph {
|
|
581
|
+
/** Resolved images, first-declared-wins per OG array semantics. */
|
|
582
|
+
readonly images: ReadonlyArray<ResolvedOpenGraphImage>;
|
|
583
|
+
/** Embed accent color, when authored. */
|
|
584
|
+
readonly themeColor?: string;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* A bundle's manifest data resolved across the six tiers, every field
|
|
588
|
+
* carrying value + provenance.
|
|
589
|
+
*
|
|
590
|
+
* @remarks
|
|
591
|
+
* Fields that no tier supplies are absent — with two floors: `name` always
|
|
592
|
+
* resolves (the api.json model always has one) and every resolved image's
|
|
593
|
+
* `alt` always resolves (the inference chain bottoms out on `name`).
|
|
594
|
+
*
|
|
595
|
+
* @public
|
|
596
|
+
*/
|
|
597
|
+
interface ResolvedBundle {
|
|
598
|
+
/** Display name: platform → leaf manifest → package.json → api.json model. */
|
|
599
|
+
readonly name: Provenanced<string>;
|
|
600
|
+
/** Package version, from package.json. */
|
|
601
|
+
readonly version?: Provenanced<string>;
|
|
602
|
+
/** Tagline: platform → leaf manifest → project tier. */
|
|
603
|
+
readonly tagline?: Provenanced<string>;
|
|
604
|
+
/** Description: platform → leaf manifest → package.json. */
|
|
605
|
+
readonly description?: Provenanced<string>;
|
|
606
|
+
/** The project identity block, when the manifest carries one. */
|
|
607
|
+
readonly project?: Provenanced<ProjectIdentity>;
|
|
608
|
+
/** Open Graph block: platform → leaf manifest, with per-image inference applied. */
|
|
609
|
+
readonly openGraph?: Provenanced<ResolvedOpenGraph>;
|
|
610
|
+
/** SBOM pointer: platform → leaf manifest. */
|
|
611
|
+
readonly sbom?: Provenanced<SbomRef>;
|
|
612
|
+
/** Registries: platform → leaf manifest. */
|
|
613
|
+
readonly registries?: Provenanced<ReadonlyArray<RegistryRef>>;
|
|
614
|
+
/** Runtime dependencies from package.json, key-sorted. Feeds the type registry's rendering scope. */
|
|
615
|
+
readonly dependencies?: Provenanced<Readonly<Record<string, string>>>;
|
|
616
|
+
/** Peer dependencies from package.json, key-sorted. Feeds the type registry's rendering scope. */
|
|
617
|
+
readonly peerDependencies?: Provenanced<Readonly<Record<string, string>>>;
|
|
618
|
+
/** Extends-resolved compiler options from tsconfig.json. Feeds the Twoslash environment. */
|
|
619
|
+
readonly compilerOptions?: Provenanced<CompilerOptions.Type>;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* The parsed layers {@link resolveBundle} resolves — plain optional fields,
|
|
623
|
+
* so pure call sites (and tests) need no `Option` wrapping.
|
|
624
|
+
*
|
|
625
|
+
* @public
|
|
626
|
+
*/
|
|
627
|
+
interface ResolveBundleInput {
|
|
628
|
+
/** Layer 0: the model header (required — the one layer a bundle must have). */
|
|
629
|
+
readonly apiModel: ApiModelInfo;
|
|
630
|
+
/** Layer 1: the package.json manifest, when present. */
|
|
631
|
+
readonly packageJson?: PackageManifest;
|
|
632
|
+
/** Layer 2: the extends-resolved tsconfig, when present. */
|
|
633
|
+
readonly tsconfig?: ResolvedTsconfig;
|
|
634
|
+
/** Layer 3: the tsdoctor.json sidecar manifest, when present. */
|
|
635
|
+
readonly manifest?: BundleManifest;
|
|
636
|
+
/** The `manifest.platform` tier, from platform options. */
|
|
637
|
+
readonly platform?: PlatformOverrides;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Resolve a bundle's layers into a {@link ResolvedBundle}, pure.
|
|
641
|
+
*
|
|
642
|
+
* @remarks
|
|
643
|
+
* Highest tier wins per FIELD: `manifest.platform` → `manifest.leaf` →
|
|
644
|
+
* `manifest.project` → `packageJson` → `apiModel` → `inferred`. The project
|
|
645
|
+
* tier participates only in the fields it carries site/project identity for
|
|
646
|
+
* (tagline); the display `name` chain deliberately skips it — a project name
|
|
647
|
+
* outranking every leaf's own name would render each package in a monorepo
|
|
648
|
+
* under the same title, and the spec's `og:title` derivation reads
|
|
649
|
+
* `leaf name/tagline ← package name`. Inference (image `alt` and MIME
|
|
650
|
+
* `type`) runs on the RESOLVED tagline/description, so a tagline change at
|
|
651
|
+
* any tier propagates into inferred alt text.
|
|
652
|
+
*
|
|
653
|
+
* @public
|
|
654
|
+
*/
|
|
655
|
+
declare function resolveBundle(input: ResolveBundleInput): ResolvedBundle;
|
|
656
|
+
/**
|
|
657
|
+
* Resolve a read {@link Bundle}, unwrapping its `Option` layers.
|
|
658
|
+
*
|
|
659
|
+
* @public
|
|
660
|
+
*/
|
|
661
|
+
declare function resolveBundleFrom(bundle: Bundle, platform?: PlatformOverrides): ResolvedBundle;
|
|
662
|
+
//#endregion
|
|
663
|
+
//#region src/BundleAssets.d.ts
|
|
664
|
+
declare const BundleAssetError_base: Schema.Class<BundleAssetError, Schema.TaggedStruct<"BundleAssetError", {
|
|
665
|
+
/** The source or destination path at fault. */
|
|
666
|
+
readonly path: Schema.String;
|
|
667
|
+
readonly cause: Schema.Defect;
|
|
668
|
+
}>, import("effect/Cause").YieldableError>;
|
|
669
|
+
/**
|
|
670
|
+
* A bundle-relative Open Graph image could not be published: the source file
|
|
671
|
+
* could not be read, or the destination could not be written.
|
|
672
|
+
*
|
|
673
|
+
* @public
|
|
674
|
+
*/
|
|
675
|
+
declare class BundleAssetError extends BundleAssetError_base {
|
|
676
|
+
get message(): string;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* One Open Graph image after publication: an absolute URL a `<head>` tag can
|
|
680
|
+
* use directly, plus whatever facts are known about it.
|
|
681
|
+
*
|
|
682
|
+
* @public
|
|
683
|
+
*/
|
|
684
|
+
interface PublishedOpenGraphImage {
|
|
685
|
+
/** Absolute (or, when `siteUrl` is `""`, root-relative) URL. */
|
|
686
|
+
readonly url: string;
|
|
687
|
+
readonly type?: string;
|
|
688
|
+
readonly width?: number;
|
|
689
|
+
readonly height?: number;
|
|
690
|
+
readonly alt: string;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Input to {@link publishBundleAssets}.
|
|
694
|
+
*
|
|
695
|
+
* @public
|
|
696
|
+
*/
|
|
697
|
+
interface PublishBundleAssetsInput {
|
|
698
|
+
/** The bundle directory a resolved image's `path` is relative to. */
|
|
699
|
+
readonly bundleDir: string;
|
|
700
|
+
readonly images: ReadonlyArray<ResolvedOpenGraphImage>;
|
|
701
|
+
/** The site's static-asset root (e.g. `docs/public`). */
|
|
702
|
+
readonly publicDir: string;
|
|
703
|
+
/** The site's absolute origin; `""` yields root-relative URLs. */
|
|
704
|
+
readonly siteUrl: string;
|
|
705
|
+
/** The route segment a package's published assets are namespaced under. */
|
|
706
|
+
readonly unscopedName: string;
|
|
707
|
+
/**
|
|
708
|
+
* An additional route segment inserted after `unscopedName`, for a site
|
|
709
|
+
* that publishes more than one build of the same package (e.g. one
|
|
710
|
+
* version per `VersionConfig`) under one public directory.
|
|
711
|
+
*
|
|
712
|
+
* @remarks
|
|
713
|
+
* Without it, two versions of the same package that both carry an
|
|
714
|
+
* `openGraph` image publish to the identical
|
|
715
|
+
* `tsdoctor/<unscopedName>/<basename>` route and overwrite each other on
|
|
716
|
+
* every build — differing bytes defeat the identical-bytes skip, and
|
|
717
|
+
* whichever version built last wins for every version's pages.
|
|
718
|
+
*/
|
|
719
|
+
readonly subdir?: string;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Copy every bundle-relative image into `<publicDir>/tsdoctor/<unscopedName>/`
|
|
723
|
+
* and return every image — bundle-relative or external — as a
|
|
724
|
+
* {@link PublishedOpenGraphImage} carrying an absolute URL.
|
|
725
|
+
*
|
|
726
|
+
* @remarks
|
|
727
|
+
* Identical bytes are not rewritten, so a rebuild over an unchanged image
|
|
728
|
+
* leaves the published file's own metadata (mtime and any framework cache
|
|
729
|
+
* keyed on it) untouched. Width and height are read from the resolved image
|
|
730
|
+
* when the manifest declared them, and measured from the file's bytes only
|
|
731
|
+
* when it did not.
|
|
732
|
+
*
|
|
733
|
+
* @public
|
|
734
|
+
*/
|
|
735
|
+
declare const publishBundleAssets: (input: PublishBundleAssetsInput) => Effect.Effect<readonly PublishedOpenGraphImage[], BundleAssetError, FileSystem.FileSystem | Path.Path>;
|
|
736
|
+
//#endregion
|
|
390
737
|
//#region src/BundleDiscovery.d.ts
|
|
391
738
|
declare const BundleDiscoveryError_base: Schema.Class<BundleDiscoveryError, Schema.TaggedStruct<"BundleDiscoveryError", {
|
|
392
739
|
/** The directory (or file) the failure is about. */
|
|
@@ -493,7 +840,7 @@ declare const BundleFetchError_base: Schema.Class<BundleFetchError, Schema.Tagge
|
|
|
493
840
|
/** Which fetcher failed. */
|
|
494
841
|
readonly source: Schema.Literals<readonly ["npm", "github"]>;
|
|
495
842
|
/** What went wrong, structurally. */
|
|
496
|
-
readonly reason: Schema.Literals<readonly ["invalidRef", "versionNotFound", "releaseNotFound", "assetNotFound", "assetAmbiguous", "download", "notABundle", "cache"]>;
|
|
843
|
+
readonly reason: Schema.Literals<readonly ["invalidRef", "versionNotFound", "releaseNotFound", "assetNotFound", "assetAmbiguous", "download", "notABundle", "missingAsset", "cache"]>;
|
|
497
844
|
/** The remote coordinate: `name@version` or `owner/repo@tag#asset`. */
|
|
498
845
|
readonly ref: Schema.String;
|
|
499
846
|
/** Human context for the failure. */
|
|
@@ -606,209 +953,6 @@ declare function fetchNpmBundle(options: FetchNpmBundleOptions): Effect.Effect<B
|
|
|
606
953
|
*/
|
|
607
954
|
declare function fetchGitHubReleaseBundle(options: FetchGitHubReleaseBundleOptions): Effect.Effect<Bundle, BundleFetchError | BundleLayerError | BundleManifestError, GitHubRelease | PackageTarball | Cache | AppDirs | FileSystem.FileSystem | Path.Path>;
|
|
608
955
|
//#endregion
|
|
609
|
-
//#region src/PlatformOverrides.d.ts
|
|
610
|
-
/**
|
|
611
|
-
* The `manifest.platform` tier: a data-override object a consumer passes
|
|
612
|
-
* through platform options (e.g. `ApiExtractorPlugin(options)`), sitting at
|
|
613
|
-
* the TOP of the tier ranking.
|
|
614
|
-
*
|
|
615
|
-
* @remarks
|
|
616
|
-
* Same field surface as the authored manifest tiers — name, tagline,
|
|
617
|
-
* description, openGraph, sbom, registries — with no `spec` field (it is not
|
|
618
|
-
* a file with an independent version) and no `project` block (it is a single
|
|
619
|
-
* tier, not a flattened hierarchy). Lets a user with ONLY an api.json declare
|
|
620
|
-
* identity/OG/registries declaratively; the resolver does the merging.
|
|
621
|
-
*
|
|
622
|
-
* @public
|
|
623
|
-
*/
|
|
624
|
-
declare const PlatformOverrides: Schema.Struct<{
|
|
625
|
-
/** Human display name override. */
|
|
626
|
-
readonly name: Schema.optionalKey<Schema.String>;
|
|
627
|
-
/** Tagline override. */
|
|
628
|
-
readonly tagline: Schema.optionalKey<Schema.String>;
|
|
629
|
-
/** Description override. */
|
|
630
|
-
readonly description: Schema.optionalKey<Schema.String>;
|
|
631
|
-
/** Open Graph override. */
|
|
632
|
-
readonly openGraph: Schema.optionalKey<Schema.Struct<{
|
|
633
|
-
readonly images: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
634
|
-
readonly path: Schema.optionalKey<Schema.String>;
|
|
635
|
-
readonly url: Schema.optionalKey<Schema.String>;
|
|
636
|
-
readonly type: Schema.optionalKey<Schema.String>;
|
|
637
|
-
readonly width: Schema.optionalKey<Schema.Int>;
|
|
638
|
-
readonly height: Schema.optionalKey<Schema.Int>;
|
|
639
|
-
readonly alt: Schema.optionalKey<Schema.String>;
|
|
640
|
-
}>>>;
|
|
641
|
-
readonly themeColor: Schema.optionalKey<Schema.String>;
|
|
642
|
-
}>>;
|
|
643
|
-
/** SBOM pointer override. */
|
|
644
|
-
readonly sbom: Schema.optionalKey<Schema.Struct<{
|
|
645
|
-
readonly path: Schema.String;
|
|
646
|
-
readonly format: Schema.optionalKey<Schema.String>;
|
|
647
|
-
}>>;
|
|
648
|
-
/** Registries override. */
|
|
649
|
-
readonly registries: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
650
|
-
readonly type: Schema.String;
|
|
651
|
-
readonly name: Schema.String;
|
|
652
|
-
readonly url: Schema.String;
|
|
653
|
-
}>>>;
|
|
654
|
-
}>;
|
|
655
|
-
/**
|
|
656
|
-
* The decoded type of {@link (PlatformOverrides:variable)}.
|
|
657
|
-
*
|
|
658
|
-
* @public
|
|
659
|
-
*/
|
|
660
|
-
type PlatformOverrides = typeof PlatformOverrides.Type;
|
|
661
|
-
/**
|
|
662
|
-
* Decode an unknown value into a {@link (PlatformOverrides:type)}.
|
|
663
|
-
*
|
|
664
|
-
* @remarks
|
|
665
|
-
* For adapters decoding raw platform options. Failures share
|
|
666
|
-
* {@link BundleManifestError} — the platform tier is manifest data by another
|
|
667
|
-
* route, and a caller handles both boundaries with one tag.
|
|
668
|
-
*
|
|
669
|
-
* @public
|
|
670
|
-
*/
|
|
671
|
-
declare function decodePlatformOverrides(input: unknown): Effect.Effect<PlatformOverrides, BundleManifestError>;
|
|
672
|
-
//#endregion
|
|
673
|
-
//#region src/BundleResolver.d.ts
|
|
674
|
-
/**
|
|
675
|
-
* Where a resolved field's value came from, highest-ranked tier first.
|
|
676
|
-
*
|
|
677
|
-
* @remarks
|
|
678
|
-
* The first six values are the spec's tier ladder. `"tsconfig"` is this
|
|
679
|
-
* package's one addition: the spec passes tsconfig compiler options through
|
|
680
|
-
* as a resolved field but its ladder has no source that names the tsconfig
|
|
681
|
-
* layer, so the union carries one.
|
|
682
|
-
*
|
|
683
|
-
* @public
|
|
684
|
-
*/
|
|
685
|
-
type ProvenanceSource = "manifest.platform" | "manifest.leaf" | "manifest.project" | "packageJson" | "apiModel" | "tsconfig" | "inferred";
|
|
686
|
-
/**
|
|
687
|
-
* A resolved value carrying its provenance.
|
|
688
|
-
*
|
|
689
|
-
* @remarks
|
|
690
|
-
* Provenance is load-bearing: a field is user-overridden iff its source
|
|
691
|
-
* outranks the derivation that would otherwise supply it, an `inferred`
|
|
692
|
-
* field tracks upstream changes while an authored field is pinned, and the
|
|
693
|
-
* change-detection fingerprints hash value AND source together so an
|
|
694
|
-
* override flip is a visible diff.
|
|
695
|
-
*
|
|
696
|
-
* @public
|
|
697
|
-
*/
|
|
698
|
-
interface Provenanced<A> {
|
|
699
|
-
/** The resolved value. */
|
|
700
|
-
readonly value: A;
|
|
701
|
-
/** The tier that supplied it. */
|
|
702
|
-
readonly source: ProvenanceSource;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* One Open Graph image after resolution: authored fields passed through,
|
|
706
|
-
* `type` and `alt` filled by the documented inference rules when absent.
|
|
707
|
-
*
|
|
708
|
-
* @public
|
|
709
|
-
*/
|
|
710
|
-
interface ResolvedOpenGraphImage {
|
|
711
|
-
/** Bundle-relative asset path, when the image is bundle-supplied. */
|
|
712
|
-
readonly path?: string;
|
|
713
|
-
/** Absolute external URL, when the image is external. */
|
|
714
|
-
readonly url?: string;
|
|
715
|
-
/** MIME type — authored, or inferred from the file extension. */
|
|
716
|
-
readonly type?: Provenanced<string>;
|
|
717
|
-
/** Pixel width, as authored. */
|
|
718
|
-
readonly width?: number;
|
|
719
|
-
/** Pixel height, as authored. */
|
|
720
|
-
readonly height?: number;
|
|
721
|
-
/** Alt text — authored, or inferred (tagline → description → `"<name> API documentation"`); never empty. */
|
|
722
|
-
readonly alt: Provenanced<string>;
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* The Open Graph block after resolution.
|
|
726
|
-
*
|
|
727
|
-
* @public
|
|
728
|
-
*/
|
|
729
|
-
interface ResolvedOpenGraph {
|
|
730
|
-
/** Resolved images, first-declared-wins per OG array semantics. */
|
|
731
|
-
readonly images: ReadonlyArray<ResolvedOpenGraphImage>;
|
|
732
|
-
/** Embed accent color, when authored. */
|
|
733
|
-
readonly themeColor?: string;
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* A bundle's manifest data resolved across the six tiers, every field
|
|
737
|
-
* carrying value + provenance.
|
|
738
|
-
*
|
|
739
|
-
* @remarks
|
|
740
|
-
* Fields that no tier supplies are absent — with two floors: `name` always
|
|
741
|
-
* resolves (the api.json model always has one) and every resolved image's
|
|
742
|
-
* `alt` always resolves (the inference chain bottoms out on `name`).
|
|
743
|
-
*
|
|
744
|
-
* @public
|
|
745
|
-
*/
|
|
746
|
-
interface ResolvedBundle {
|
|
747
|
-
/** Display name: platform → leaf manifest → package.json → api.json model. */
|
|
748
|
-
readonly name: Provenanced<string>;
|
|
749
|
-
/** Package version, from package.json. */
|
|
750
|
-
readonly version?: Provenanced<string>;
|
|
751
|
-
/** Tagline: platform → leaf manifest → project tier. */
|
|
752
|
-
readonly tagline?: Provenanced<string>;
|
|
753
|
-
/** Description: platform → leaf manifest → package.json. */
|
|
754
|
-
readonly description?: Provenanced<string>;
|
|
755
|
-
/** The project identity block, when the manifest carries one. */
|
|
756
|
-
readonly project?: Provenanced<ProjectIdentity>;
|
|
757
|
-
/** Open Graph block: platform → leaf manifest, with per-image inference applied. */
|
|
758
|
-
readonly openGraph?: Provenanced<ResolvedOpenGraph>;
|
|
759
|
-
/** SBOM pointer: platform → leaf manifest. */
|
|
760
|
-
readonly sbom?: Provenanced<SbomRef>;
|
|
761
|
-
/** Registries: platform → leaf manifest. */
|
|
762
|
-
readonly registries?: Provenanced<ReadonlyArray<RegistryRef>>;
|
|
763
|
-
/** Runtime dependencies from package.json, key-sorted. Feeds the type registry's rendering scope. */
|
|
764
|
-
readonly dependencies?: Provenanced<Readonly<Record<string, string>>>;
|
|
765
|
-
/** Peer dependencies from package.json, key-sorted. Feeds the type registry's rendering scope. */
|
|
766
|
-
readonly peerDependencies?: Provenanced<Readonly<Record<string, string>>>;
|
|
767
|
-
/** Extends-resolved compiler options from tsconfig.json. Feeds the Twoslash environment. */
|
|
768
|
-
readonly compilerOptions?: Provenanced<CompilerOptions.Type>;
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* The parsed layers {@link resolveBundle} resolves — plain optional fields,
|
|
772
|
-
* so pure call sites (and tests) need no `Option` wrapping.
|
|
773
|
-
*
|
|
774
|
-
* @public
|
|
775
|
-
*/
|
|
776
|
-
interface ResolveBundleInput {
|
|
777
|
-
/** Layer 0: the model header (required — the one layer a bundle must have). */
|
|
778
|
-
readonly apiModel: ApiModelInfo;
|
|
779
|
-
/** Layer 1: the package.json manifest, when present. */
|
|
780
|
-
readonly packageJson?: PackageManifest;
|
|
781
|
-
/** Layer 2: the extends-resolved tsconfig, when present. */
|
|
782
|
-
readonly tsconfig?: ResolvedTsconfig;
|
|
783
|
-
/** Layer 3: the tsdoctor.json sidecar manifest, when present. */
|
|
784
|
-
readonly manifest?: BundleManifest;
|
|
785
|
-
/** The `manifest.platform` tier, from platform options. */
|
|
786
|
-
readonly platform?: PlatformOverrides;
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* Resolve a bundle's layers into a {@link ResolvedBundle}, pure.
|
|
790
|
-
*
|
|
791
|
-
* @remarks
|
|
792
|
-
* Highest tier wins per FIELD: `manifest.platform` → `manifest.leaf` →
|
|
793
|
-
* `manifest.project` → `packageJson` → `apiModel` → `inferred`. The project
|
|
794
|
-
* tier participates only in the fields it carries site/project identity for
|
|
795
|
-
* (tagline); the display `name` chain deliberately skips it — a project name
|
|
796
|
-
* outranking every leaf's own name would render each package in a monorepo
|
|
797
|
-
* under the same title, and the spec's `og:title` derivation reads
|
|
798
|
-
* `leaf name/tagline ← package name`. Inference (image `alt` and MIME
|
|
799
|
-
* `type`) runs on the RESOLVED tagline/description, so a tagline change at
|
|
800
|
-
* any tier propagates into inferred alt text.
|
|
801
|
-
*
|
|
802
|
-
* @public
|
|
803
|
-
*/
|
|
804
|
-
declare function resolveBundle(input: ResolveBundleInput): ResolvedBundle;
|
|
805
|
-
/**
|
|
806
|
-
* Resolve a read {@link Bundle}, unwrapping its `Option` layers.
|
|
807
|
-
*
|
|
808
|
-
* @public
|
|
809
|
-
*/
|
|
810
|
-
declare function resolveBundleFrom(bundle: Bundle, platform?: PlatformOverrides): ResolvedBundle;
|
|
811
|
-
//#endregion
|
|
812
956
|
//#region src/BundleHash.d.ts
|
|
813
957
|
/**
|
|
814
958
|
* Normalize text for hashing: CRLF/CR line endings become LF and trailing
|
|
@@ -885,5 +1029,5 @@ declare function hashLayerText(text: string): Effect.Effect<string, PlatformErro
|
|
|
885
1029
|
*/
|
|
886
1030
|
declare function fingerprintResolvedBundle(resolved: ResolvedBundle): Effect.Effect<Readonly<Record<string, string>>, PlatformError.PlatformError, Crypto.Crypto>;
|
|
887
1031
|
//#endregion
|
|
888
|
-
export { type ApiModelInfo, type Bundle, type BundleDescriptor, BundleDiscoveryError, BundleFetchError, BundleLayerError, BundleManifest, BundleManifestError, type BundleOverrides, type DiscoverBundleOptions, type DiscoverBundlesOptions, type FetchGitHubReleaseBundleOptions, type FetchNpmBundleOptions, KNOWN_REGISTRY_TYPES, type KnownRegistryType, OpenGraphConfig, OpenGraphImage, PlatformOverrides, ProjectIdentity, type ProvenanceSource, type Provenanced, RegistryRef, type ResolveBundleInput, type ResolvedBundle, type ResolvedOpenGraph, type ResolvedOpenGraphImage, SbomRef, TSDOCTOR_MANIFEST_FILENAME, decodeBundleManifest, decodePlatformOverrides, discoverBundle, discoverBundles, fetchGitHubReleaseBundle, fetchNpmBundle, fingerprintResolvedBundle, hashJsonValue, hashLayerText, hashText, isKnownRegistryType, loadBundle, loadBundles, normalizeText, readApiModelInfo, readBundle, resolveBundle, resolveBundleFrom };
|
|
1032
|
+
export { type ApiModelInfo, type Bundle, BundleAssetError, type BundleDescriptor, BundleDiscoveryError, BundleFetchError, BundleLayerError, BundleManifest, BundleManifestError, type BundleOverrides, type DiscoverBundleOptions, type DiscoverBundlesOptions, type FetchGitHubReleaseBundleOptions, type FetchNpmBundleOptions, KNOWN_REGISTRY_TYPES, type KnownRegistryType, MANIFEST_SPEC, ManifestSource, OpenGraphConfig, OpenGraphImage, PlatformOverrides, ProjectIdentity, type ProvenanceSource, type Provenanced, type PublishBundleAssetsInput, type PublishedOpenGraphImage, RegistryRef, type ResolveBundleInput, type ResolvedBundle, type ResolvedOpenGraph, type ResolvedOpenGraphImage, SbomRef, TSDOCTOR_MANIFEST_FILENAME, decodeBundleManifest, decodeManifestSource, decodePlatformOverrides, discoverBundle, discoverBundles, encodeBundleManifest, fetchGitHubReleaseBundle, fetchNpmBundle, fingerprintResolvedBundle, hashJsonValue, hashLayerText, hashText, isKnownRegistryType, loadBundle, loadBundles, normalizeText, publishBundleAssets, readApiModelInfo, readBundle, resolveBundle, resolveBundleFrom };
|
|
889
1033
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { BundleLayerError, readApiModelInfo, readBundle } from "./Bundle.js";
|
|
2
|
+
import { BundleAssetError, publishBundleAssets } from "./BundleAssets.js";
|
|
3
3
|
import { BundleDiscoveryError, discoverBundle, discoverBundles, loadBundle, loadBundles } from "./BundleDiscovery.js";
|
|
4
4
|
import { BundleFetchError, fetchGitHubReleaseBundle, fetchNpmBundle } from "./BundleFetch.js";
|
|
5
5
|
import { fingerprintResolvedBundle, hashJsonValue, hashLayerText, hashText, normalizeText } from "./BundleHash.js";
|
|
6
6
|
import { resolveBundle, resolveBundleFrom } from "./BundleResolver.js";
|
|
7
7
|
import { PlatformOverrides, decodePlatformOverrides } from "./PlatformOverrides.js";
|
|
8
|
+
import { BundleManifest, BundleManifestError, KNOWN_REGISTRY_TYPES, MANIFEST_SPEC, ManifestSource, OpenGraphConfig, OpenGraphImage, ProjectIdentity, RegistryRef, SbomRef, TSDOCTOR_MANIFEST_FILENAME, decodeBundleManifest, decodeManifestSource, encodeBundleManifest, isKnownRegistryType } from "@tsdoctor/manifest";
|
|
8
9
|
|
|
9
|
-
export { BundleDiscoveryError, BundleFetchError, BundleLayerError, BundleManifest, BundleManifestError, KNOWN_REGISTRY_TYPES, OpenGraphConfig, OpenGraphImage, PlatformOverrides, ProjectIdentity, RegistryRef, SbomRef, TSDOCTOR_MANIFEST_FILENAME, decodeBundleManifest, decodePlatformOverrides, discoverBundle, discoverBundles, fetchGitHubReleaseBundle, fetchNpmBundle, fingerprintResolvedBundle, hashJsonValue, hashLayerText, hashText, isKnownRegistryType, loadBundle, loadBundles, normalizeText, readApiModelInfo, readBundle, resolveBundle, resolveBundleFrom };
|
|
10
|
+
export { BundleAssetError, BundleDiscoveryError, BundleFetchError, BundleLayerError, BundleManifest, BundleManifestError, KNOWN_REGISTRY_TYPES, MANIFEST_SPEC, ManifestSource, OpenGraphConfig, OpenGraphImage, PlatformOverrides, ProjectIdentity, RegistryRef, SbomRef, TSDOCTOR_MANIFEST_FILENAME, decodeBundleManifest, decodeManifestSource, decodePlatformOverrides, discoverBundle, discoverBundles, encodeBundleManifest, fetchGitHubReleaseBundle, fetchNpmBundle, fingerprintResolvedBundle, hashJsonValue, hashLayerText, hashText, isKnownRegistryType, loadBundle, loadBundles, normalizeText, publishBundleAssets, readApiModelInfo, readBundle, resolveBundle, resolveBundleFrom };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/internal/asset-path.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared traversal guard for bundle-relative asset paths.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Not exported from `index.ts` — internal to the package. Both the fetch
|
|
7
|
+
* plane ({@link "../BundleFetch.js"}) and the publish plane
|
|
8
|
+
* ({@link "../BundleAssets.js"}) read a manifest-declared, bundle-relative
|
|
9
|
+
* `openGraph.images[].path` and must reject the same shapes before touching
|
|
10
|
+
* the filesystem: no absolute paths, no `.`/`..` traversal segments, no
|
|
11
|
+
* separators hiding inside a segment.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const SAFE_SEGMENT = /^(?!\.{1,2}$)[^/\\\s]+$/;
|
|
16
|
+
/**
|
|
17
|
+
* Whether `assetPath` is a relative path confined to the bundle directory —
|
|
18
|
+
* no leading slash, no `.`/`..` segment, no unsafe characters in any
|
|
19
|
+
* segment.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
function isSafeAssetPath(assetPath) {
|
|
24
|
+
if (assetPath.length === 0 || assetPath.startsWith("/") || assetPath.startsWith("\\")) return false;
|
|
25
|
+
return assetPath.split(/[/\\]/).every((segment) => SAFE_SEGMENT.test(segment));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { isSafeAssetPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsdoctor/bundle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The tsdoctor bundle spec: layered bundle discovery, the versioned tsdoctor.json sidecar manifest, provenance-carrying resolution and canonical input hashing for API documentation bundles.",
|
|
6
6
|
"keywords": [
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"@effected/tsconfig-json": "^0.7.0",
|
|
49
49
|
"@effected/walker": "^0.5.0",
|
|
50
50
|
"@effected/xdg": "^0.3.0",
|
|
51
|
-
"
|
|
51
|
+
"@tsdoctor/manifest": "0.1.0",
|
|
52
|
+
"effect": "4.0.0-rc.109",
|
|
53
|
+
"image-size": "^2.0.2"
|
|
52
54
|
},
|
|
53
55
|
"engines": {
|
|
54
56
|
"node": ">=24.11.0"
|
package/BundleManifest.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src/BundleManifest.ts
|
|
4
|
-
/**
|
|
5
|
-
* The registry protocol families this reader knows how to do more than link
|
|
6
|
-
* to. `"npm"` means an npm-compatible registry — install commands and tarball
|
|
7
|
-
* fetching work against any instance of it — and `"jsr"` the jsr protocol.
|
|
8
|
-
*
|
|
9
|
-
* @remarks
|
|
10
|
-
* The manifest's `type` field is deliberately NOT constrained to these
|
|
11
|
-
* values: unknown future types must degrade to link-only rendering, not
|
|
12
|
-
* reject the manifest. Use {@link isKnownRegistryType} to branch.
|
|
13
|
-
*
|
|
14
|
-
* @public
|
|
15
|
-
*/
|
|
16
|
-
const KNOWN_REGISTRY_TYPES = ["npm", "jsr"];
|
|
17
|
-
/**
|
|
18
|
-
* Whether a registry `type` value is a protocol family this reader
|
|
19
|
-
* recognizes. `false` means the registry entry should degrade to link-only
|
|
20
|
-
* rendering — it is never a validation failure.
|
|
21
|
-
*
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
function isKnownRegistryType(type) {
|
|
25
|
-
return KNOWN_REGISTRY_TYPES.includes(type);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* One registry the documented package is published to.
|
|
29
|
-
*
|
|
30
|
-
* @remarks
|
|
31
|
-
* `type` is the PROTOCOL FAMILY (`"npm"` covers every npm-compatible
|
|
32
|
-
* registry), `name` the human instance label, `url` the package's page on
|
|
33
|
-
* that instance. Unknown `type` values decode successfully and degrade to
|
|
34
|
-
* link-only rendering (see {@link isKnownRegistryType}).
|
|
35
|
-
*
|
|
36
|
-
* @public
|
|
37
|
-
*/
|
|
38
|
-
const RegistryRef = Schema.Struct({
|
|
39
|
-
/** The protocol family, e.g. `"npm"` or `"jsr"`. Unknown values are accepted. */
|
|
40
|
-
type: Schema.String,
|
|
41
|
-
/** The human instance label, e.g. `"npm"` or `"Savvy Web Registry"`. */
|
|
42
|
-
name: Schema.String,
|
|
43
|
-
/** The package's URL on that registry instance. */
|
|
44
|
-
url: Schema.String
|
|
45
|
-
});
|
|
46
|
-
/**
|
|
47
|
-
* One Open Graph image declared by the manifest.
|
|
48
|
-
*
|
|
49
|
-
* @remarks
|
|
50
|
-
* Exactly ONE of `path` (a bundle-relative asset the consuming platform
|
|
51
|
-
* publishes and resolves to a URL) or `url` (an absolute external URL used
|
|
52
|
-
* verbatim) must be present — the schema enforces the XOR. `type` is a MIME
|
|
53
|
-
* type, inferred from the file extension by the resolver when omitted; `alt`
|
|
54
|
-
* has a documented inference chain (tagline → description →
|
|
55
|
-
* `"<name> API documentation"`).
|
|
56
|
-
*
|
|
57
|
-
* @public
|
|
58
|
-
*/
|
|
59
|
-
const OpenGraphImage = Schema.Struct({
|
|
60
|
-
/** Bundle-relative asset path. Mutually exclusive with `url`. */
|
|
61
|
-
path: Schema.optionalKey(Schema.String),
|
|
62
|
-
/** Absolute external URL, used verbatim. Mutually exclusive with `path`. */
|
|
63
|
-
url: Schema.optionalKey(Schema.String),
|
|
64
|
-
/** MIME type; inferred from the extension when omitted. */
|
|
65
|
-
type: Schema.optionalKey(Schema.String),
|
|
66
|
-
/** Pixel width; 1200×630 (1.91:1) is the cross-platform safe default. */
|
|
67
|
-
width: Schema.optionalKey(Schema.Int),
|
|
68
|
-
/** Pixel height. */
|
|
69
|
-
height: Schema.optionalKey(Schema.Int),
|
|
70
|
-
/** Alt text; inferred (tagline → description → fallback) when omitted. */
|
|
71
|
-
alt: Schema.optionalKey(Schema.String)
|
|
72
|
-
}).check(Schema.makeFilter((image) => image.path === void 0 !== (image.url === void 0) ? void 0 : "exactly one of \"path\" or \"url\" must be present", { title: "openGraph image source" }));
|
|
73
|
-
/**
|
|
74
|
-
* The manifest's Open Graph block: the asset-ish pieces only — most OG tags
|
|
75
|
-
* are page-level and derive at render time in the consuming platform.
|
|
76
|
-
*
|
|
77
|
-
* @remarks
|
|
78
|
-
* Multiple images follow OG array semantics: the first declared wins, extras
|
|
79
|
-
* are alternates (e.g. a portrait 1000×1500 variant).
|
|
80
|
-
*
|
|
81
|
-
* @public
|
|
82
|
-
*/
|
|
83
|
-
const OpenGraphConfig = Schema.Struct({
|
|
84
|
-
/** Declared images, first-wins per OG array semantics. */
|
|
85
|
-
images: Schema.optionalKey(Schema.Array(OpenGraphImage)),
|
|
86
|
-
/** Embed accent color (e.g. Discord), a CSS color string. */
|
|
87
|
-
themeColor: Schema.optionalKey(Schema.String)
|
|
88
|
-
});
|
|
89
|
-
/**
|
|
90
|
-
* A pointer to the bundle's SBOM, computed by the bundler at publish and
|
|
91
|
-
* served as a downloadable static asset.
|
|
92
|
-
*
|
|
93
|
-
* @public
|
|
94
|
-
*/
|
|
95
|
-
const SbomRef = Schema.Struct({
|
|
96
|
-
/** Bundle-relative path to the SBOM file. */
|
|
97
|
-
path: Schema.String,
|
|
98
|
-
/** SBOM format label, e.g. `"spdx-json"`. Unknown values are accepted. */
|
|
99
|
-
format: Schema.optionalKey(Schema.String)
|
|
100
|
-
});
|
|
101
|
-
/**
|
|
102
|
-
* The inherited project tier, flattened into the emitted manifest by the
|
|
103
|
-
* bundler (a fetched bundle has no parent directory to walk). Kept nested —
|
|
104
|
-
* structurally distinguishable from the leaf fields — because provenance is
|
|
105
|
-
* load-bearing for override detection.
|
|
106
|
-
*
|
|
107
|
-
* @public
|
|
108
|
-
*/
|
|
109
|
-
const ProjectIdentity = Schema.Struct({
|
|
110
|
-
/** The project display name, e.g. `"Effected"` over leaf `@effected/store`. */
|
|
111
|
-
name: Schema.optionalKey(Schema.String),
|
|
112
|
-
/** The project tagline. */
|
|
113
|
-
tagline: Schema.optionalKey(Schema.String)
|
|
114
|
-
});
|
|
115
|
-
/**
|
|
116
|
-
* The versioned `tsdoctor.json` sidecar manifest — bundle layer 3.
|
|
117
|
-
*
|
|
118
|
-
* @remarks
|
|
119
|
-
* `spec` is the only required field; every other field enriches. Unknown
|
|
120
|
-
* top-level fields are ignored on decode (additive fields are minor spec
|
|
121
|
-
* revisions) and unknown enum-ish values (registry `type`, sbom `format`)
|
|
122
|
-
* degrade gracefully instead of rejecting — an old reader must be able to
|
|
123
|
-
* consume a new bundle.
|
|
124
|
-
*
|
|
125
|
-
* @public
|
|
126
|
-
*/
|
|
127
|
-
const BundleManifest = Schema.Struct({
|
|
128
|
-
/** The integer spec version. This reader understands spec 1. */
|
|
129
|
-
spec: Schema.Literal(1),
|
|
130
|
-
/** Human display name (the npm name is dry; this one is SEO-friendly). */
|
|
131
|
-
name: Schema.optionalKey(Schema.String),
|
|
132
|
-
/** Short tagline. */
|
|
133
|
-
tagline: Schema.optionalKey(Schema.String),
|
|
134
|
-
/** Long description; overrides the package.json description when present. */
|
|
135
|
-
description: Schema.optionalKey(Schema.String),
|
|
136
|
-
/** The inherited project tier, flattened in at emit time. */
|
|
137
|
-
project: Schema.optionalKey(ProjectIdentity),
|
|
138
|
-
/** Open Graph assets. */
|
|
139
|
-
openGraph: Schema.optionalKey(OpenGraphConfig),
|
|
140
|
-
/** SBOM pointer. */
|
|
141
|
-
sbom: Schema.optionalKey(SbomRef),
|
|
142
|
-
/** Registries the package is published to. */
|
|
143
|
-
registries: Schema.optionalKey(Schema.Array(RegistryRef))
|
|
144
|
-
});
|
|
145
|
-
/**
|
|
146
|
-
* Raised when a present `tsdoctor.json` cannot be parsed or does not satisfy
|
|
147
|
-
* the {@link (BundleManifest:variable)} schema.
|
|
148
|
-
*
|
|
149
|
-
* @remarks
|
|
150
|
-
* Absence of the manifest is NEVER this error — layers enrich, never gate,
|
|
151
|
-
* so a missing sidecar is the normal case and reads as `Option.none()`.
|
|
152
|
-
*
|
|
153
|
-
* @public
|
|
154
|
-
*/
|
|
155
|
-
var BundleManifestError = class extends Schema.TaggedError()("BundleManifestError", {
|
|
156
|
-
/** The manifest file path, when the failure is tied to a file on disk. */
|
|
157
|
-
path: Schema.optionalKey(Schema.String),
|
|
158
|
-
/** The underlying failure (JSON syntax or schema decode), preserved structurally. */
|
|
159
|
-
cause: Schema.Defect()
|
|
160
|
-
}) {
|
|
161
|
-
get message() {
|
|
162
|
-
return `Invalid tsdoctor.json manifest${this.path !== void 0 ? ` at ${this.path}` : ""}`;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
/**
|
|
166
|
-
* Decode an unknown value into a {@link (BundleManifest:type)}.
|
|
167
|
-
*
|
|
168
|
-
* @remarks
|
|
169
|
-
* The typed boundary for manifest input that has already been parsed from
|
|
170
|
-
* JSON (plugin options, fetched payloads). File-based reading lives in
|
|
171
|
-
* `readBundle`, which routes through this after parsing.
|
|
172
|
-
*
|
|
173
|
-
* @public
|
|
174
|
-
*/
|
|
175
|
-
function decodeBundleManifest(input, path) {
|
|
176
|
-
return Schema.decodeUnknownEffect(BundleManifest)(input).pipe(Effect.mapError((cause) => new BundleManifestError({
|
|
177
|
-
...path !== void 0 ? { path } : {},
|
|
178
|
-
cause
|
|
179
|
-
})));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
//#endregion
|
|
183
|
-
export { BundleManifest, BundleManifestError, KNOWN_REGISTRY_TYPES, OpenGraphConfig, OpenGraphImage, ProjectIdentity, RegistryRef, SbomRef, decodeBundleManifest, isKnownRegistryType };
|