@savvy-web/tsdown-plugins 2.8.21 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1090,6 +1090,17 @@ interface OgImageInfo {
1090
1090
  readonly tagline?: string | undefined;
1091
1091
  } | undefined;
1092
1092
  }
1093
+ /**
1094
+ * An `openGraph.generate` renderer. A renderer whose output is fully determined by its input can
1095
+ * carry its own `cacheSalt`, naming everything else that shapes the bytes (its version, its
1096
+ * options); that turns on the cross-build cache without any config. `ogImage.satori()` does.
1097
+ *
1098
+ * @public
1099
+ */
1100
+ interface OgImageGenerator {
1101
+ (info: OgImageInfo): Promise<Uint8Array>;
1102
+ readonly cacheSalt?: string | undefined;
1103
+ }
1093
1104
  /**
1094
1105
  * The `meta.tsdoctor` block: the CONFIG tier of the emitted `tsdoctor.json`,
1095
1106
  * ranked over the package's `tsdoctor.json` (leaf) and the workspace root's
@@ -1105,8 +1116,15 @@ interface TsdoctorMetaOptions {
1105
1116
  /** Static images, path (bundle-relative) or url. Listed after a generated image. */
1106
1117
  readonly images?: ReadonlyArray<OpenGraphImage> | undefined;
1107
1118
  readonly themeColor?: string | undefined;
1108
- /** Render an image at build time; the bytes are written to `meta/og/<unscoped>.png` and listed first. */
1109
- readonly generate?: ((info: OgImageInfo) => Promise<Uint8Array>) | undefined;
1119
+ /** Render an image at build time; the bytes are written to `meta/og/<unscoped>.<png|jpg|webp>` and listed first. */
1120
+ readonly generate?: OgImageGenerator | undefined;
1121
+ /**
1122
+ * The cross-build image cache, keyed on the {@link OgImageInfo} plus this salt. A string
1123
+ * caches under it: change it whenever the generator's output changes for the same input,
1124
+ * since the cache cannot see code. `false` always regenerates. Omitted, the generator's own
1125
+ * {@link OgImageGenerator.cacheSalt} is used when it has one, otherwise it always regenerates.
1126
+ */
1127
+ readonly cacheSalt?: string | false | undefined;
1110
1128
  } | undefined;
1111
1129
  /** Registries; `false` disables the default derived from `targets.json`. */
1112
1130
  readonly registries?: ReadonlyArray<RegistryRef> | false | undefined;
@@ -1668,6 +1686,23 @@ export declare class OgGenerateError extends OgGenerateError_base<{
1668
1686
  }> {
1669
1687
  get message(): string;
1670
1688
  }
1689
+ /**
1690
+ * Where generated images persist across builds, and the salt naming the generator's identity.
1691
+ *
1692
+ * @public
1693
+ */
1694
+ interface OgImageCacheOptions {
1695
+ readonly directory: string;
1696
+ /** Change it whenever the generator's output changes for the same input; the cache cannot see code. */
1697
+ readonly salt: string;
1698
+ }
1699
+ /**
1700
+ * The salt the cross-build cache runs under, or `undefined` to always regenerate: an explicit
1701
+ * `cacheSalt` string wins, `false` disables, and otherwise the generator's own salt applies.
1702
+ *
1703
+ * @public
1704
+ */
1705
+ export declare function resolveOgCacheSalt(generate: OgImageGenerator, cacheSalt: string | false | undefined): string | undefined;
1671
1706
  /**
1672
1707
  * Options for {@link writeGeneratedOgImage}.
1673
1708
  *
@@ -1679,10 +1714,12 @@ interface WriteGeneratedOgImageOptions {
1679
1714
  /** The meta bundle dir; the image lands at `og/<unscopedName>.<ext>` beneath it. */
1680
1715
  readonly outMetaDir: string;
1681
1716
  readonly unscopedName: string;
1717
+ /** Reuse a previous build's image for the same info and salt; omitted, the generator always runs. */
1718
+ readonly cache?: OgImageCacheOptions | undefined;
1682
1719
  }
1683
1720
  /**
1684
- * Run the generator, size the bytes, and write `og/<unscoped>.<ext>` under the meta dir. Returns the
1685
- * manifest image entry (bundle-relative path, MIME type, dimensions).
1721
+ * Run the generator (or reuse a cached render), size the bytes, and write `og/<unscoped>.<ext>`
1722
+ * under the meta dir. Returns the manifest image entry (bundle-relative path, MIME type, dimensions).
1686
1723
  *
1687
1724
  * @public
1688
1725
  */
@@ -2114,5 +2151,5 @@ export declare function resolveTargets(options: {
2114
2151
  baseName: string;
2115
2152
  }): TargetResolution;
2116
2153
  //#endregion
2117
- export { type AmbientDtsEntry, type BuildEmittedManifestOptions, type BuildFormat, type BuildGroupSpec, type BuildIssues, type BuildPlatform, type BuildReport, BuildReport as BuildReportSchema, type BuildTargetGroupsOptions, CatalogAssemblyError, type ComposeManifestInput, type CopyAmbientDtsOptions, type CssOptions, DependencyResolutionError, type DeriveOptions, type DerivedTsdownOptions, type DiagnosticEntry, type DiagnosticInput, type DtsExportClass, type DualExports, type EmitManifestOptions, type EmittedFile, type EntryOverride, type Environment, type ExeBuild, type ExeConfig, type ExeRewrite, type ExeSeaConfig, type ExeTarget, type ExeTargetInput, type Executor, type ExtractAmbientOptions, type ExtractOptions, type ExtractResult, type Formatter, type FormatterContext, type GenerateMetaOptions, type Json, type JsxConfig, type LooseFileSpec, type LooseFiles, ManifestDecodeError, type ManifestLike, type ManifestRepository, type ManifestTarget, type MetaOptions, type MetaResult, type ModuleExportNames, type NextVersions, type NormalizedExe, type NormalizedLooseFile, type NormalizedMeta, type OgImageInfo, type OutputFormat, type PackageJsonEntriesOptions, type PackageJsonLike, type PassKind, type PassReport, type PkgOsCpu, type PlainDiagnostic, type PortableTsconfig, type PublishTargetObject, type PublishTargetValue, type PublishTargets, type ReexportBarrelAnalysis, type RenderReportOptions, type RenderedOutput, type ResolvedCompilerOptions, type ResolvedGroup, type ResolvedTarget, type ResolvedTsconfig, type ResolvedTsconfigOptions, type RunExeBuildOptions, type RunMetaPassOptions, type TargetGroupId, type TargetGroupRef, type TargetGroupReport, TargetGroupReport as TargetGroupReportSchema, type TargetResolution, type Timer, type TransformManifestOptions, type TsconfigJsx, type TsdocOptions, type TsdocTagDefinition, type TsdoctorMetaOptions, type TsdoctorSources, type TsdownBuild, type TsdownLogger, UnresolvedDependencyError, type ValidationInput, type WarningSuppressionRule, type WriteGeneratedOgImageOptions };
2154
+ export { type AmbientDtsEntry, type BuildEmittedManifestOptions, type BuildFormat, type BuildGroupSpec, type BuildIssues, type BuildPlatform, type BuildReport, BuildReport as BuildReportSchema, type BuildTargetGroupsOptions, CatalogAssemblyError, type ComposeManifestInput, type CopyAmbientDtsOptions, type CssOptions, DependencyResolutionError, type DeriveOptions, type DerivedTsdownOptions, type DiagnosticEntry, type DiagnosticInput, type DtsExportClass, type DualExports, type EmitManifestOptions, type EmittedFile, type EntryOverride, type Environment, type ExeBuild, type ExeConfig, type ExeRewrite, type ExeSeaConfig, type ExeTarget, type ExeTargetInput, type Executor, type ExtractAmbientOptions, type ExtractOptions, type ExtractResult, type Formatter, type FormatterContext, type GenerateMetaOptions, type Json, type JsxConfig, type LooseFileSpec, type LooseFiles, ManifestDecodeError, type ManifestLike, type ManifestRepository, type ManifestTarget, type MetaOptions, type MetaResult, type ModuleExportNames, type NextVersions, type NormalizedExe, type NormalizedLooseFile, type NormalizedMeta, type OgImageCacheOptions, type OgImageGenerator, type OgImageInfo, type OutputFormat, type PackageJsonEntriesOptions, type PackageJsonLike, type PassKind, type PassReport, type PkgOsCpu, type PlainDiagnostic, type PortableTsconfig, type PublishTargetObject, type PublishTargetValue, type PublishTargets, type ReexportBarrelAnalysis, type RenderReportOptions, type RenderedOutput, type ResolvedCompilerOptions, type ResolvedGroup, type ResolvedTarget, type ResolvedTsconfig, type ResolvedTsconfigOptions, type RunExeBuildOptions, type RunMetaPassOptions, type TargetGroupId, type TargetGroupRef, type TargetGroupReport, TargetGroupReport as TargetGroupReportSchema, type TargetResolution, type Timer, type TransformManifestOptions, type TsconfigJsx, type TsdocOptions, type TsdocTagDefinition, type TsdoctorMetaOptions, type TsdoctorSources, type TsdownBuild, type TsdownLogger, UnresolvedDependencyError, type ValidationInput, type WarningSuppressionRule, type WriteGeneratedOgImageOptions };
2118
2155
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { runExeBuild } from "./exe/build.js";
27
27
  import { computeExeFileName } from "./exe/filename.js";
28
28
  import { readTsconfigJsx, resolveJsxConfig } from "./jsx/config.js";
29
29
  import { normalizeMetaOptions } from "./meta/config.js";
30
- import { OgGenerateError, writeGeneratedOgImage } from "./meta/og-image.js";
30
+ import { OgGenerateError, resolveOgCacheSalt, writeGeneratedOgImage } from "./meta/og-image.js";
31
31
  import { resolvePortableTsconfig } from "./meta/tsconfig-resolver.js";
32
32
  import { composeTsdoctorManifest, githubOwnerRepo, ogImageInfoOf, registriesFromTargets } from "./meta/tsdoctor-manifest.js";
33
33
  import { generateMeta } from "./meta/generate.js";
@@ -50,4 +50,4 @@ import { ReportPipeline, renderReport } from "./report/pipeline.js";
50
50
  import { writeTargetsBinding } from "./targets/binding.js";
51
51
  import { CatalogAssemblyError, DependencyResolutionError, ManifestDecodeError, UnresolvedDependencyError } from "@effected/npm";
52
52
 
53
- export { BuildCollector, BuildCollectorTag, BuildReport as BuildReportSchema, CatalogAssemblyError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, DEFAULT_EXE_NODE_VERSION, DependencyResolutionError, EnvironmentDetector, ExecutorResolver, FormatSelector, JsonFormatter, ManifestDecodeError, MarkdownFormatter, MetaGenerationError, OgGenerateError, OutputRenderer, ReportPipeline, ReportTimings, SilentFormatter, TargetGroupReport as TargetGroupReportSchema, TerminalFormatter, TsdoctorEmitError, TsdoctorSourceError, UnresolvedDependencyError, ambientOutName, analyzeReexportBarrel, applySubdirMetaEntries, assertNoEntryCollisions, buildEmittedManifest, buildMetricsPlugin, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, classifyDtsExport, collectExportNames, composeTsdoctorManifest, computeExeFileName, copyAmbientDts, copyPublicDir, createEntryName, createTimer, createTsdownLogger, declarationExt, defaultManifestTransform, deriveExportPaths, deriveTargetGroupOptions, emitManifest, extractAmbientDts, extractEntries, findRelativeSpecifiers, flattenIssues, formatTime, generateMeta, githubOwnerRepo, isTargetObject, loadTsdoctorSources, mixedDtsExportError, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, ogImageInfoOf, packageJsonEntries, readTsconfigJsx, registriesFromTargets, removeDeclarationMaps, renderReexportStub, renderReport, resolveJsxConfig, resolveManifest, resolveNextVersions, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, runMetaPass, serializeIssues, transformBin, transformExports, transformManifest, writeDtsEmitTsconfig, writeGeneratedOgImage, writeIssuesArtifact, writeResolvedTsconfig, writeTargetsBinding };
53
+ export { BuildCollector, BuildCollectorTag, BuildReport as BuildReportSchema, CatalogAssemblyError, CiAnnotationsFormatter, ConfigValidationError, ConfigValidator, DEFAULT_EXE_NODE_VERSION, DependencyResolutionError, EnvironmentDetector, ExecutorResolver, FormatSelector, JsonFormatter, ManifestDecodeError, MarkdownFormatter, MetaGenerationError, OgGenerateError, OutputRenderer, ReportPipeline, ReportTimings, SilentFormatter, TargetGroupReport as TargetGroupReportSchema, TerminalFormatter, TsdoctorEmitError, TsdoctorSourceError, UnresolvedDependencyError, ambientOutName, analyzeReexportBarrel, applySubdirMetaEntries, assertNoEntryCollisions, buildEmittedManifest, buildMetricsPlugin, buildResolvedTsconfig, buildTargetGroups, cjsDefaultInterop, classifyDtsExport, collectExportNames, composeTsdoctorManifest, computeExeFileName, copyAmbientDts, copyPublicDir, createEntryName, createTimer, createTsdownLogger, declarationExt, defaultManifestTransform, deriveExportPaths, deriveTargetGroupOptions, emitManifest, extractAmbientDts, extractEntries, findRelativeSpecifiers, flattenIssues, formatTime, generateMeta, githubOwnerRepo, isTargetObject, loadTsdoctorSources, mixedDtsExportError, nodeBuiltinDefaultInterop, normalizeBinPaths, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, ogImageInfoOf, packageJsonEntries, readTsconfigJsx, registriesFromTargets, removeDeclarationMaps, renderReexportStub, renderReport, resolveJsxConfig, resolveManifest, resolveNextVersions, resolveOgCacheSalt, resolvePortableTsconfig, resolveTargets, rewriteMetaVersions, runExeBuild, runMetaPass, serializeIssues, transformBin, transformExports, transformManifest, writeDtsEmitTsconfig, writeGeneratedOgImage, writeIssuesArtifact, writeResolvedTsconfig, writeTargetsBinding };
package/meta/generate.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { TsdoctorEmitError } from "../errors.js";
2
2
  import { runApiExtractor } from "./api-extractor.js";
3
3
  import { mergeApiModels } from "./merge-models.js";
4
- import { writeGeneratedOgImage } from "./og-image.js";
4
+ import { resolveOgCacheSalt, writeGeneratedOgImage } from "./og-image.js";
5
5
  import { resolvePortableTsconfig } from "./tsconfig-resolver.js";
6
6
  import { writeTsdocConfig } from "./tsdoc-config.js";
7
7
  import { composeTsdoctorManifest, ogImageInfoOf } from "./tsdoctor-manifest.js";
@@ -152,6 +152,7 @@ async function generateMeta(options) {
152
152
  };
153
153
  const generate = options.tsdoctor.config?.openGraph?.generate;
154
154
  if (generate !== void 0) {
155
+ const cacheSalt = resolveOgCacheSalt(generate, options.tsdoctor.config?.openGraph?.cacheSalt);
155
156
  const generated = await writeGeneratedOgImage({
156
157
  generate,
157
158
  info: ogImageInfoOf({
@@ -159,7 +160,11 @@ async function generateMeta(options) {
159
160
  version: String(finalPkg.version ?? "0.0.0")
160
161
  }),
161
162
  outMetaDir,
162
- unscopedName: unscopedName(packageName)
163
+ unscopedName: unscopedName(packageName),
164
+ ...cacheSalt === void 0 ? {} : { cache: {
165
+ directory: join(cwd, "node_modules", ".cache", "tsdown-plugins", "og"),
166
+ salt: cacheSalt
167
+ } }
163
168
  });
164
169
  composeInput.generatedImage = generated;
165
170
  generatedImageRelative = generated.path;
package/meta/og-image.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { Data } from "effect";
4
- import { imageSize } from "image-size";
3
+ import { Data, Effect, Layer, Option, Result, Schema } from "effect";
4
+ import * as NodeServices from "@effect/platform-node/NodeServices";
5
+ import { ImageBackend, ImageCache, ImageCacheKey } from "@effected/images/cache";
5
6
 
6
7
  //#region src/meta/og-image.ts
7
8
  /**
@@ -17,63 +18,90 @@ var OgGenerateError = class extends Data.TaggedError("OgGenerateError") {
17
18
  }
18
19
  };
19
20
  /** The image types an Open Graph consumer can render; anything else fails rather than shipping a mislabeled file. */
20
- const MIME_BY_TYPE = {
21
- png: "image/png",
22
- jpg: "image/jpeg",
23
- webp: "image/webp"
24
- };
21
+ const ACCEPT = [
22
+ "png",
23
+ "jpeg",
24
+ "webp"
25
+ ];
26
+ /**
27
+ * The cache key's params: exactly what the generator receives, so any change to its input is a miss.
28
+ * `Schema.Struct` drops undeclared keys, so the `satisfies` clauses fail to compile when a field is
29
+ * added to {@link OgImageInfo} without being keyed here (or removed from it but kept here).
30
+ */
31
+ const OgImageInfoKey = Schema.Struct({
32
+ name: Schema.String,
33
+ packageName: Schema.String,
34
+ version: Schema.String,
35
+ tagline: Schema.optional(Schema.String),
36
+ description: Schema.optional(Schema.String),
37
+ project: Schema.optional(Schema.Struct({
38
+ name: Schema.optional(Schema.String),
39
+ tagline: Schema.optional(Schema.String)
40
+ }))
41
+ });
42
+ /**
43
+ * The cache is an optimization, never a reason to fail a build that rendered a valid image: an
44
+ * unreadable entry is a miss and a failed store is dropped. Generator and validation failures still
45
+ * surface, because those come from `ImageCache` itself, not the backend.
46
+ */
47
+ const tolerantDirectory = (directory) => Layer.effect(ImageBackend, Effect.gen(function* () {
48
+ const backend = yield* ImageBackend;
49
+ return {
50
+ get: (key) => backend.get(key).pipe(Effect.orElseSucceed(() => Option.none())),
51
+ set: (params) => backend.set(params).pipe(Effect.ignore)
52
+ };
53
+ })).pipe(Layer.provide(ImageBackend.layerDirectory({ directory })));
54
+ /**
55
+ * The salt the cross-build cache runs under, or `undefined` to always regenerate: an explicit
56
+ * `cacheSalt` string wins, `false` disables, and otherwise the generator's own salt applies.
57
+ *
58
+ * @public
59
+ */
60
+ function resolveOgCacheSalt(generate, cacheSalt) {
61
+ if (cacheSalt === false) return void 0;
62
+ return cacheSalt ?? generate.cacheSalt;
63
+ }
25
64
  /**
26
- * Run the generator, size the bytes, and write `og/<unscoped>.<ext>` under the meta dir. Returns the
27
- * manifest image entry (bundle-relative path, MIME type, dimensions).
65
+ * Run the generator (or reuse a cached render), size the bytes, and write `og/<unscoped>.<ext>`
66
+ * under the meta dir. Returns the manifest image entry (bundle-relative path, MIME type, dimensions).
28
67
  *
29
68
  * @public
30
69
  */
31
70
  async function writeGeneratedOgImage(options) {
32
- let bytes;
33
- try {
34
- bytes = await options.generate(options.info);
35
- } catch (cause) {
36
- throw new OgGenerateError({
37
- packageName: options.info.packageName,
38
- cause
39
- });
40
- }
41
- if (bytes.byteLength === 0) throw new OgGenerateError({
42
- packageName: options.info.packageName,
43
- cause: /* @__PURE__ */ new Error("generator returned no bytes")
44
- });
45
- let size;
46
- try {
47
- size = imageSize(bytes);
48
- } catch (cause) {
49
- throw new OgGenerateError({
50
- packageName: options.info.packageName,
51
- cause
71
+ const { packageName } = options.info;
72
+ const backend = options.cache === void 0 ? ImageBackend.layerNone : tolerantDirectory(options.cache.directory);
73
+ const result = await Effect.gen(function* () {
74
+ const key = yield* ImageCacheKey.fromParams(OgImageInfoKey, options.info, {
75
+ salt: options.cache?.salt ?? "",
76
+ namespace: "og"
52
77
  });
53
- }
54
- const ext = size.type ?? "png";
55
- const type = MIME_BY_TYPE[ext];
56
- if (type === void 0) throw new OgGenerateError({
57
- packageName: options.info.packageName,
58
- cause: /* @__PURE__ */ new Error(`generator returned a ${ext} image; Open Graph images must be png, jpg or webp`)
78
+ return yield* (yield* ImageCache).getOrGenerate(key, () => Effect.tryPromise({
79
+ try: () => options.generate(options.info),
80
+ catch: (cause) => cause
81
+ }), { accept: ACCEPT });
82
+ }).pipe(Effect.provide(ImageCache.layer.pipe(Layer.provide(backend), Layer.provideMerge(NodeServices.layer))), Effect.result, Effect.runPromise);
83
+ if (Result.isFailure(result)) throw new OgGenerateError({
84
+ packageName,
85
+ cause: result.failure
59
86
  });
60
- const relative = `og/${options.unscopedName}.${ext}`;
87
+ const { bytes, facts } = result.success;
88
+ const relative = `og/${options.unscopedName}.${facts.extension}`;
61
89
  try {
62
90
  mkdirSync(join(options.outMetaDir, "og"), { recursive: true });
63
91
  writeFileSync(join(options.outMetaDir, relative), bytes);
64
92
  } catch (cause) {
65
93
  throw new OgGenerateError({
66
- packageName: options.info.packageName,
94
+ packageName,
67
95
  cause
68
96
  });
69
97
  }
70
98
  return {
71
99
  path: relative,
72
- type,
73
- width: size.width,
74
- height: size.height
100
+ type: facts.mimeType,
101
+ width: facts.width,
102
+ height: facts.height
75
103
  };
76
104
  }
77
105
 
78
106
  //#endregion
79
- export { OgGenerateError, writeGeneratedOgImage };
107
+ export { OgGenerateError, resolveOgCacheSalt, writeGeneratedOgImage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/tsdown-plugins",
3
- "version": "2.8.21",
3
+ "version": "2.9.0",
4
4
  "private": false,
5
5
  "description": "Interface-only tsdown/rolldown plugin pack powering @savvy-web/bundler",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/tsdown-plugins",
@@ -32,6 +32,7 @@
32
32
  "@changesets/get-release-plan": "^5.0.2",
33
33
  "@effect/platform-node": "^4.0.2",
34
34
  "@effected/glob": "^0.10.0",
35
+ "@effected/images": "^0.1.0",
35
36
  "@effected/jsonc": "^0.15.1",
36
37
  "@effected/npm": "^0.20.0",
37
38
  "@effected/package-json": "^0.20.1",
@@ -42,9 +43,8 @@
42
43
  "@microsoft/api-extractor": "7.59.1",
43
44
  "@microsoft/tsdoc": "~0.16.0",
44
45
  "@microsoft/tsdoc-config": "0.18.1",
45
- "@tsdoctor/manifest": "^0.2.4",
46
+ "@tsdoctor/manifest": "^0.2.5",
46
47
  "effect": "^4.0.2",
47
- "image-size": "^2.0.4",
48
48
  "picocolors": "^1.1.1",
49
49
  "std-env": "^4.3.0",
50
50
  "tsdown": "^0.23.0",