@dbarrett24/tsdown-config 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +53 -16
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readWantedLockfile } from "@pnpm/lockfile-file";
2
2
  import { merge } from "es-toolkit";
3
+ import * as fs$1 from "fs";
3
4
  import fs from "fs";
4
5
  import path from "path";
5
6
  import { defineConfig } from "tsdown";
@@ -85,6 +86,50 @@ const writeEntryManifest = (packageName, entries, clientEntries = {}, targetDir
85
86
  fs.writeFileSync(path.join(targetDir, ENTRY_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 4)}\n`);
86
87
  };
87
88
  //#endregion
89
+ //#region json-file.ts
90
+ /**
91
+ * The one way this repo turns JSON text into a typed value.
92
+ *
93
+ * A bare `JSON.parse(...)` returns `any`, so `JSON.parse(text).name` compiles whether or not the
94
+ * file has a `name` — which is how the entry manifest once shipped `package: undefined` (PR #55).
95
+ * The caller states the shape it relies on as a type guard, and a mismatch throws an error naming
96
+ * the file and the failed expectation.
97
+ *
98
+ * This is the single generic core (`isJsonObject`, `parseJson`, `readJsonFile`). `scripts/lib/json-file.ts`
99
+ * imports it and adds only the guards scripts/ itself needs; do not copy it. Kept in its own
100
+ * dependency-free module (like `workspace-specifier.ts`) so it unit-tests without loading tsdown.
101
+ *
102
+ * `pnpm check-json-parse` fails on a bare `JSON.parse` anywhere in scope, and exempts this file
103
+ * because it is where the one permitted `JSON.parse` lives.
104
+ */
105
+ const isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
106
+ const isString = (value) => typeof value === "string";
107
+ const isOptional = (value, guard) => value === void 0 || guard(value);
108
+ const isStringRecord = (value) => isJsonObject(value) && Object.values(value).every(isString);
109
+ const isSourceManifest = (value) => isJsonObject(value) && isString(value.name) && isString(value.version) && isOptional(value.dependencies, isStringRecord) && isOptional(value.peerDependencies, isStringRecord) && isOptional(value.publishConfig, (v) => isJsonObject(v) && isOptional(v.access, isString)) && isOptional(value.bin, isStringRecord) && isOptional(value.scripts, (v) => isJsonObject(v) && isOptional(v.postinstall, isString));
110
+ /** A manifest that declares a string `version`, which is all a workspace dependency lookup needs. */
111
+ const hasStringVersion = (value) => isJsonObject(value) && isString(value.version);
112
+ /**
113
+ * Parse JSON text and check it against `guard`.
114
+ *
115
+ * @param source what the text came from (a path, or a description such as `WebSocket frame`); it
116
+ * leads the error message
117
+ * @param expectation what the guard checks, for the error message (e.g. `a package.json object`)
118
+ */
119
+ const parseJson = (text, source, guard, expectation = "the expected shape") => {
120
+ let parsed;
121
+ try {
122
+ parsed = JSON.parse(text);
123
+ } catch (error) {
124
+ const reason = error instanceof Error ? error.message : String(error);
125
+ throw new Error(`${source}: not valid JSON (${reason})`);
126
+ }
127
+ if (!guard(parsed)) throw new Error(`${source}: parsed JSON is not ${expectation}`);
128
+ return parsed;
129
+ };
130
+ /** Read `filePath` as UTF-8 JSON and check it against `guard`; errors name `filePath`. */
131
+ const readJsonFile = (filePath, guard, expectation = "the expected shape") => parseJson(fs$1.readFileSync(filePath, "utf-8"), filePath, guard, expectation);
132
+ //#endregion
88
133
  //#region workspace-specifier.ts
89
134
  /**
90
135
  * Resolution of pnpm workspace protocol specifiers for the emitted `dist/package.json`.
@@ -120,7 +165,7 @@ const makeConfig = (entries, clientEntries, optionOverrides) => {
120
165
  return defineConfig(makeConfigObject(entries, clientEntries, optionOverrides));
121
166
  };
122
167
  const copyPackageJson = async () => {
123
- const packageJSON = JSON.parse(fs.readFileSync("package.json").toString());
168
+ const packageJSON = readJsonFile("package.json", isSourceManifest, "a package.json manifest");
124
169
  const newPackageJSON = {};
125
170
  newPackageJSON.name = packageJSON.name;
126
171
  newPackageJSON.dependencies = packageJSON.dependencies;
@@ -177,21 +222,13 @@ const copyPackageJson = async () => {
177
222
  const packageName = process.cwd().split(path.sep).slice(-2).join("/");
178
223
  const lockfile_package_list = (await readWantedLockfile("../..", { ignoreIncompatible: true }))?.importers?.[packageName];
179
224
  const merged_deps = merge(lockfile_package_list?.dependencies ?? {}, lockfile_package_list?.devDependencies ?? {});
180
- for (const item of Object.entries(newPackageJSON.peerDependencies)) {
181
- const [dep, version] = item;
182
- if (isWorkspaceSpecifier(version)) {
183
- const package_path = merged_deps?.[dep].version.replace(/(?:link:|\/dist)/g, "") + "/package.json";
184
- const packageJSON = JSON.parse(fs.readFileSync(package_path).toString());
185
- newPackageJSON.peerDependencies[dep] = resolveWorkspaceSpecifier(version, packageJSON.version);
186
- }
225
+ for (const [dep, version] of Object.entries(packageJSON.peerDependencies ?? {})) if (isWorkspaceSpecifier(version)) {
226
+ const packageJSON = readJsonFile(merged_deps?.[dep].version.replace(/(?:link:|\/dist)/g, "") + "/package.json", hasStringVersion, "a package.json with a string \"version\"");
227
+ newPackageJSON.peerDependencies[dep] = resolveWorkspaceSpecifier(version, packageJSON.version);
187
228
  }
188
- for (const item of Object.entries(newPackageJSON.dependencies ?? {})) {
189
- const [dep, version] = item;
190
- if (isWorkspaceSpecifier(version)) {
191
- const package_path = merged_deps?.[dep].version.replace(/(?:link:|\/dist)/g, "") + "/package.json";
192
- const packageJSON = JSON.parse(fs.readFileSync(package_path).toString());
193
- newPackageJSON.dependencies[dep] = resolveWorkspaceSpecifier(version, packageJSON.version);
194
- }
229
+ for (const [dep, version] of Object.entries(packageJSON.dependencies ?? {})) if (isWorkspaceSpecifier(version)) {
230
+ const packageJSON = readJsonFile(merged_deps?.[dep].version.replace(/(?:link:|\/dist)/g, "") + "/package.json", hasStringVersion, "a package.json with a string \"version\"");
231
+ newPackageJSON.dependencies[dep] = resolveWorkspaceSpecifier(version, packageJSON.version);
195
232
  }
196
233
  fs.writeFileSync("dist/package.json", formatEmittedPackageJson(newPackageJSON));
197
234
  };
@@ -245,7 +282,7 @@ const makeConfigObject = (entries, clientEntries = {}, optionOverrides = {}) =>
245
282
  ...optionOverrides,
246
283
  onSuccess: async (config, signal) => {
247
284
  if (typeof optionOverrides.onSuccess === "function") await optionOverrides.onSuccess(config, signal);
248
- const packageName = JSON.parse(fs.readFileSync("package.json").toString()).name;
285
+ const packageName = readJsonFile("package.json", isJsonObject, "a package.json object").name;
249
286
  if (typeof packageName !== "string" || packageName.length === 0) throw new Error("tsdown-config: package.json has no \"name\" to write into the entry manifest");
250
287
  await copyPackageJson();
251
288
  copySources("src", "dist/src");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbarrett24/tsdown-config",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shared tsdown configuration for building component libraries (publish-from-dist with generated wildcard exports)",
5
5
  "type": "module",
6
6
  "module": "dist/index.js",
@@ -55,8 +55,8 @@
55
55
  "lint-fix": "pnpm lint-oxlint-fix && pnpm lint-oxfmt-fix",
56
56
  "lint-oxfmt": "oxfmt --config ../../.oxfmtrc.json --check \"*.ts\"",
57
57
  "lint-oxfmt-fix": "oxfmt --config ../../.oxfmtrc.json --write \"*.ts\"",
58
- "lint-oxlint": "oxlint index.ts tsdown.config.ts workspace-specifier.ts",
59
- "lint-oxlint-fix": "oxlint --fix index.ts tsdown.config.ts workspace-specifier.ts",
58
+ "lint-oxlint": "oxlint index.ts json-file.ts tsdown.config.ts workspace-specifier.ts",
59
+ "lint-oxlint-fix": "oxlint --fix index.ts json-file.ts tsdown.config.ts workspace-specifier.ts",
60
60
  "ts-check": "tsc --noEmit",
61
61
  "test": "jest --passWithNoTests"
62
62
  }