From dfffcf7924c8f6ed7dd525e95cde8f4ee6ceadd1 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Wed, 19 Nov 2025 23:29:03 -0500 Subject: [PATCH 1/9] Split out some logic that will need to be reused as utils --- src/main.ts | 29 +++++------------------------ src/utils.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/main.ts b/src/main.ts index 0d97d65..a4df6c1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,8 @@ import { table } from "table"; import * as ts from "typescript"; import { type BuildContext, compileProject } from "./compile.js"; import { + detectConfigIndention, + findConfigPath, formatForLog, isSourceFile, isTestFile, @@ -163,22 +165,7 @@ Examples: /////////////////////////////////// // Find package.json by scanning up the file system - let packageJsonPath = "./package.json"; - let currentDir = process.cwd(); - - while (currentDir !== path.dirname(currentDir)) { - const candidatePath = path.join(currentDir, "package.json"); - if (fs.existsSync(candidatePath)) { - packageJsonPath = candidatePath; - break; - } - currentDir = path.dirname(currentDir); - } - - if (!fs.existsSync(packageJsonPath)) { - log.error("❌ package.json not found in current directory or any parent directories"); - process.exit(1); - } + const packageJsonPath = findConfigPath("package.json"); // read package.json and extract the "zshy" exports config const pkgJsonRaw = fs.readFileSync(packageJsonPath, "utf-8"); @@ -186,13 +173,7 @@ Examples: const pkgJson = JSON.parse(pkgJsonRaw); // Detect indentation from package.json to preserve it. - let indent: string | number = 2; // Default to 2 spaces - const indentMatch = pkgJsonRaw.match(/^([ \t]+)/m); - if (indentMatch?.[1]) { - indent = indentMatch[1]; - } else if (!pkgJsonRaw.includes("\n")) { - indent = 0; // minified - } + const pkgJsonIndent = detectConfigIndention(pkgJsonRaw); const pkgJsonDir = path.dirname(packageJsonPath); const pkgJsonRelPath = relativePosix(pkgJsonDir, packageJsonPath); @@ -1045,7 +1026,7 @@ Examples: /////////////////////////////// log.info("[dryrun] Skipping package.json modification"); } else { - fs.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, indent) + "\n"); + fs.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, pkgJsonIndent) + "\n"); } } diff --git a/src/utils.ts b/src/utils.ts index c043399..ca53456 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import * as fs from "node:fs"; import * as path from "node:path"; import * as ts from "typescript"; @@ -132,3 +133,37 @@ export function isTestFile(filePath: string): boolean { return false; } + +export function findConfigPath(fileName: string): string { + let resultPath = `./${fileName}`; + let currentDir = process.cwd(); + + while (currentDir !== path.dirname(currentDir)) { + const candidatePath = path.join(currentDir, fileName); + if (fs.existsSync(candidatePath)) { + resultPath = candidatePath; + break; + } + currentDir = path.dirname(currentDir); + } + + if (!fs.existsSync(resultPath)) { + log.error(`❌ ${fileName} not found in current directory or any parent directories`); + process.exit(1); + } + + return resultPath; +} + +export function detectConfigIndention(fileContents: string): string | number { + let indent: string | number = 2; // Default to 2 spaces + const indentMatch = fileContents.match(/^([ \t]+)/m); + + if (indentMatch?.[1]) { + indent = indentMatch[1]; + } else if (!fileContents.includes("\n")) { + indent = 0; // minified + } + + return indent; +} From fd1506160338c38d2c2e9d1df072f67de43aefe8 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Wed, 19 Nov 2025 23:50:11 -0500 Subject: [PATCH 2/9] Implement jsr option --- src/main.ts | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index a4df6c1..e59bcef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,6 +26,7 @@ interface RawConfig { conditions?: Record; tsconfig?: string; // optional path to tsconfig.json file noEdit?: boolean; + jsr?: boolean; } interface NormalizedConfig { @@ -35,6 +36,7 @@ interface NormalizedConfig { cjs: boolean; tsconfig: string; noEdit: boolean; + jsr: boolean; } export async function main(): Promise { @@ -266,11 +268,14 @@ Examples: const config = { ...rawConfig } as NormalizedConfig; + // Normalize boolean options + config.noEdit ??= false; + config.jsr ??= false; + // Normalize cjs property if (config.cjs === undefined) { config.cjs = true; // Default to true if not specified } - config.noEdit ??= false; // Validate that if cjs is disabled, no conditions are set to "cjs" if (config.cjs === false && config.conditions) { @@ -1030,6 +1035,47 @@ Examples: } } + ////////////////////////////////// + /// write jsr exports /// + ////////////////////////////////// + + if (config.jsr) { + if (!isSilent) { + log.info(`${prefix}Updating jsr.json...`); + } + + // Find jsr.json by scanning up the file system + const jsrJsonPath = findConfigPath("jsr.json"); + + // read jsr.json + const jsrJsonRaw = fs.readFileSync(jsrJsonPath, "utf-8"); + const jsrJson = JSON.parse(jsrJsonRaw); + + // Detect indentation from jsr.json to preserve it. + const jsrJsonIndent = detectConfigIndention(jsrJsonRaw); + + const jsrJsonDir = path.dirname(jsrJsonPath); + const jsrJsonRelPath = relativePosix(jsrJsonDir, jsrJsonPath); + + if (!isSilent) { + log.info(`Reading jsr.json from ./${jsrJsonRelPath}`); + } + + // Copy exports from zshy config to jsr.json exports + const jsrExports = config.exports; + jsrJson.exports = jsrExports; + if (isVerbose) { + log.info(`Setting "exports": ${formatForLog(jsrExports)}`); + } + + // Write jsr json + if (isDryRun) { + log.info("[dryrun] Skipping jsr.json modification"); + } else { + fs.writeFileSync(jsrJsonPath, JSON.stringify(jsrJson, null, jsrJsonIndent) + "\n"); + } + } + if (isAttw) { // run `@arethetypeswrong/cli --pack .` to check types From 30273938c71b62fcb6a415d659416e8641d9114f Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Thu, 20 Nov 2025 00:04:33 -0500 Subject: [PATCH 3/9] Update README --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b3af2c..32703d7 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,21 @@ With this addition, `zshy` will add the `"my-source"` condition to the generated } ``` +### JSR + +For packages that also publish to [JSR](https://jsr.io/), you can have `zshy` copy your configured exports to `jsr.json`, making your `zshy` configuration the single source of truth for exports: + +```jsonc +{ + "zshy": { + "exports": { ... }, + "jsr": true + } +} +``` + +This will copy over the paths of the source code entrypoints, not the paths to the transpiled code, since JSR supports and encourages publishing TypeScript source code rather than pairs of `.js` + `.d.ts` files. +


@@ -686,7 +701,7 @@ To learn more, read the ["Masquerading as CJS"](https://github.com/arethetypeswr ```ts function hello() { - console.log('hello'); + console.log("hello"); } export default hello; @@ -696,7 +711,7 @@ export default hello; ```ts function hello() { - console.log('hello'); + console.log("hello"); } exports.default = hello; module.exports = exports.default; From 5a0cf51a73de63ee29f8797d5ea718368fe27b81 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Thu, 20 Nov 2025 00:16:41 -0500 Subject: [PATCH 4/9] Add test --- test/__snapshots__/zshy.test.ts.snap | 104 +++++++++++++++++++++++++++ test/jsr/dist/index.cjs | 10 +++ test/jsr/dist/index.cjs.map | 1 + test/jsr/dist/index.d.cts | 5 ++ test/jsr/dist/index.d.cts.map | 1 + test/jsr/dist/index.d.ts | 5 ++ test/jsr/dist/index.d.ts.map | 1 + test/jsr/dist/index.js | 7 ++ test/jsr/dist/index.js.map | 1 + test/jsr/jsr.json | 8 +++ test/jsr/package.json | 31 ++++++++ test/jsr/src/index.ts | 6 ++ test/jsr/tsconfig.json | 8 +++ test/zshy.test.ts | 8 +++ 14 files changed, 196 insertions(+) create mode 100644 test/jsr/dist/index.cjs create mode 100644 test/jsr/dist/index.cjs.map create mode 100644 test/jsr/dist/index.d.cts create mode 100644 test/jsr/dist/index.d.cts.map create mode 100644 test/jsr/dist/index.d.ts create mode 100644 test/jsr/dist/index.d.ts.map create mode 100644 test/jsr/dist/index.js create mode 100644 test/jsr/dist/index.js.map create mode 100644 test/jsr/jsr.json create mode 100644 test/jsr/package.json create mode 100644 test/jsr/src/index.ts create mode 100644 test/jsr/tsconfig.json diff --git a/test/__snapshots__/zshy.test.ts.snap b/test/__snapshots__/zshy.test.ts.snap index 83901ee..e9ce809 100644 --- a/test/__snapshots__/zshy.test.ts.snap +++ b/test/__snapshots__/zshy.test.ts.snap @@ -1,5 +1,109 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`zshy with different tsconfig configurations > should copy exports to jsr.json when jsr is true 1`] = ` +{ + "exitCode": 0, + "stderr": "", + "stdout": "╔═══════════════════════════════════════════════╗ + ║ zshy » the bundler-free TypeScript build tool ║ + ╚═══════════════════════════════════════════════╝ +» Starting build... +» Verbose mode enabled +» Detected package manager: +» Build will fail only on errors (default) +» Detected project root: /test/jsr +» Reading package.json from ./package.json +» Parsed zshy config: { + "exports": { + ".": "./src/index.ts" + }, + "jsr": true + } +» Reading tsconfig from ./tsconfig.json +» Determining entrypoints... + ╔══════════╤════════════════╗ + ║ Subpath │ Entrypoint ║ + ╟──────────┼────────────────╢ + ║ "my-pkg" │ ./src/index.ts ║ + ╚══════════╧════════════════╝ +» Resolved build paths: + ╔══════════╤═══════════════╗ + ║ Location │ Resolved path ║ + ╟──────────┼───────────────╢ + ║ rootDir │ ./src ║ + ║ outDir │ ./dist ║ + ╚══════════╧═══════════════╝ +» Package is an ES module (package.json#/type is "module") +» Cleaning up outDir... +» Cleaning up declarationDir... +» Resolved entrypoints: [ + "./src/index.ts" + ] +» Resolved compilerOptions: { + "lib": [ + "lib.esnext.d.ts" + ], + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": 2, + "allowJs": true, + "declaration": true, + "jsx": 4, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "verbatimModuleSyntax": false, + "noEmit": false, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + "sourceMap": true, + "declarationMap": true, + "resolveJsonModule": true, + "noImplicitOverride": true, + "noImplicitThis": true, + "outDir": "/test/jsr/dist", + "emitDeclarationOnly": false, + "composite": false + } +» Building CJS... (rewriting .ts -> .cjs/.d.cts) +» Enabling CJS interop transform... +» Building ESM... +» Writing files (8 total)... + ./dist/index.cjs + ./dist/index.cjs.map + ./dist/index.d.cts + ./dist/index.d.cts.map + ./dist/index.d.ts + ./dist/index.d.ts.map + ./dist/index.js + ./dist/index.js.map +» Updating package.json... +» Setting "main": "./dist/index.cjs" +» Setting "module": "./dist/index.js" +» Setting "types": "./dist/index.d.cts" +» Setting "exports": { + ".": { + "types": "./dist/index.d.cts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + } +» Updating jsr.json... +» Reading jsr.json from ./jsr.json +» Setting "exports": { + ".": "./src/index.ts" + } +» Build complete!", +} +`; + exports[`zshy with different tsconfig configurations > should not edit package.json when noEdit is true 1`] = ` { "exitCode": 0, diff --git a/test/jsr/dist/index.cjs b/test/jsr/dist/index.cjs new file mode 100644 index 0000000..1f51044 --- /dev/null +++ b/test/jsr/dist/index.cjs @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.hi = hi; +/** + * Main entry point for the test library + */ +function hi() { + console.log("hi"); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/test/jsr/dist/index.cjs.map b/test/jsr/dist/index.cjs.map new file mode 100644 index 0000000..3e34d49 --- /dev/null +++ b/test/jsr/dist/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAGA,gBAEC;AALD;;GAEG;AACH,SAAgB,EAAE;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"} \ No newline at end of file diff --git a/test/jsr/dist/index.d.cts b/test/jsr/dist/index.d.cts new file mode 100644 index 0000000..24f2c4a --- /dev/null +++ b/test/jsr/dist/index.d.cts @@ -0,0 +1,5 @@ +/** + * Main entry point for the test library + */ +export declare function hi(): void; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/test/jsr/dist/index.d.cts.map b/test/jsr/dist/index.d.cts.map new file mode 100644 index 0000000..a9ea779 --- /dev/null +++ b/test/jsr/dist/index.d.cts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,EAAE,SAEjB"} \ No newline at end of file diff --git a/test/jsr/dist/index.d.ts b/test/jsr/dist/index.d.ts new file mode 100644 index 0000000..24f2c4a --- /dev/null +++ b/test/jsr/dist/index.d.ts @@ -0,0 +1,5 @@ +/** + * Main entry point for the test library + */ +export declare function hi(): void; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/test/jsr/dist/index.d.ts.map b/test/jsr/dist/index.d.ts.map new file mode 100644 index 0000000..a9ea779 --- /dev/null +++ b/test/jsr/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,EAAE,SAEjB"} \ No newline at end of file diff --git a/test/jsr/dist/index.js b/test/jsr/dist/index.js new file mode 100644 index 0000000..f635caf --- /dev/null +++ b/test/jsr/dist/index.js @@ -0,0 +1,7 @@ +/** + * Main entry point for the test library + */ +export function hi() { + console.log("hi"); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/test/jsr/dist/index.js.map b/test/jsr/dist/index.js.map new file mode 100644 index 0000000..a2a8c25 --- /dev/null +++ b/test/jsr/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,EAAE;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"} \ No newline at end of file diff --git a/test/jsr/jsr.json b/test/jsr/jsr.json new file mode 100644 index 0000000..330b91d --- /dev/null +++ b/test/jsr/jsr.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://jsr.io/schema/config-file.v1.json", + "name": "@jsr/my-pkg", + "version": "1.0.0", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/test/jsr/package.json b/test/jsr/package.json new file mode 100644 index 0000000..cfe4ee3 --- /dev/null +++ b/test/jsr/package.json @@ -0,0 +1,31 @@ +{ + "name": "my-pkg", + "version": "1.0.0", + "description": "Test fixture for zshy - represents a typical TypeScript library", + "type": "module", + "scripts": { + "build": "tsx ../../src/index.ts --project tsconfig.json" + }, + "devDependencies": { + "typescript": "^5.8.3" + }, + "zshy": { + "exports": { + ".": "./src/index.ts" + }, + "jsr": true + }, + "files": [ + "dist" + ], + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.cts", + "exports": { + ".": { + "types": "./dist/index.d.cts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + } +} diff --git a/test/jsr/src/index.ts b/test/jsr/src/index.ts new file mode 100644 index 0000000..422d0a8 --- /dev/null +++ b/test/jsr/src/index.ts @@ -0,0 +1,6 @@ +/** + * Main entry point for the test library + */ +export function hi() { + console.log("hi"); +} diff --git a/test/jsr/tsconfig.json b/test/jsr/tsconfig.json new file mode 100644 index 0000000..4e4f2c3 --- /dev/null +++ b/test/jsr/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.default.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/test/zshy.test.ts b/test/zshy.test.ts index abb2519..a7aec22 100644 --- a/test/zshy.test.ts +++ b/test/zshy.test.ts @@ -173,6 +173,14 @@ describe("zshy with different tsconfig configurations", () => { expect(snapshot).toMatchSnapshot(); }); + it("should copy exports to jsr.json when jsr is true", () => { + const snapshot = runZshyWithTsconfig("tsconfig.json", { + dryRun: false, + cwd: process.cwd() + "/test/jsr", + }); + expect(snapshot).toMatchSnapshot(); + }); + it("should support multiple bin entries", () => { const snapshot = runZshyWithTsconfig("tsconfig.json", { dryRun: false, From 8c955e6b9fa3afd8a5133cdebf1bd1a1671c9f18 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Tue, 25 Nov 2025 20:35:37 -0500 Subject: [PATCH 5/9] Address review comments --- src/main.ts | 62 ++++++++++++++++++++++++++++------------------------ src/utils.ts | 2 +- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/main.ts b/src/main.ts index e59bcef..d01b65f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,7 +6,7 @@ import { table } from "table"; import * as ts from "typescript"; import { type BuildContext, compileProject } from "./compile.js"; import { - detectConfigIndention, + detectConfigIndentation, findConfigPath, formatForLog, isSourceFile, @@ -175,7 +175,7 @@ Examples: const pkgJson = JSON.parse(pkgJsonRaw); // Detect indentation from package.json to preserve it. - const pkgJsonIndent = detectConfigIndention(pkgJsonRaw); + const pkgJsonIndent = detectConfigIndentation(pkgJsonRaw); const pkgJsonDir = path.dirname(packageJsonPath); const pkgJsonRelPath = relativePosix(pkgJsonDir, packageJsonPath); @@ -1040,39 +1040,45 @@ Examples: ////////////////////////////////// if (config.jsr) { - if (!isSilent) { - log.info(`${prefix}Updating jsr.json...`); - } + if (config.noEdit) { + if (!isSilent) { + log.info("[noedit] Skipping modification of jsr.json"); + } + } else { + if (!isSilent) { + log.info(`${prefix}Updating jsr.json...`); + } - // Find jsr.json by scanning up the file system - const jsrJsonPath = findConfigPath("jsr.json"); + // Find jsr.json by scanning up the file system + const jsrJsonPath = findConfigPath("jsr.json"); - // read jsr.json - const jsrJsonRaw = fs.readFileSync(jsrJsonPath, "utf-8"); - const jsrJson = JSON.parse(jsrJsonRaw); + // read jsr.json + const jsrJsonRaw = fs.readFileSync(jsrJsonPath, "utf-8"); + const jsrJson = JSON.parse(jsrJsonRaw); - // Detect indentation from jsr.json to preserve it. - const jsrJsonIndent = detectConfigIndention(jsrJsonRaw); + // Detect indentation from jsr.json to preserve it. + const jsrJsonIndent = detectConfigIndentation(jsrJsonRaw); - const jsrJsonDir = path.dirname(jsrJsonPath); - const jsrJsonRelPath = relativePosix(jsrJsonDir, jsrJsonPath); + const jsrJsonDir = path.dirname(jsrJsonPath); + const jsrJsonRelPath = relativePosix(jsrJsonDir, jsrJsonPath); - if (!isSilent) { - log.info(`Reading jsr.json from ./${jsrJsonRelPath}`); - } + if (!isSilent) { + log.info(`Reading jsr.json from ./${jsrJsonRelPath}`); + } - // Copy exports from zshy config to jsr.json exports - const jsrExports = config.exports; - jsrJson.exports = jsrExports; - if (isVerbose) { - log.info(`Setting "exports": ${formatForLog(jsrExports)}`); - } + // Copy exports from zshy config to jsr.json exports + const jsrExports = config.exports; + jsrJson.exports = jsrExports; + if (isVerbose) { + log.info(`Setting "exports": ${formatForLog(jsrExports)}`); + } - // Write jsr json - if (isDryRun) { - log.info("[dryrun] Skipping jsr.json modification"); - } else { - fs.writeFileSync(jsrJsonPath, JSON.stringify(jsrJson, null, jsrJsonIndent) + "\n"); + // Write jsr json + if (isDryRun) { + log.info("[dryrun] Skipping jsr.json modification"); + } else { + fs.writeFileSync(jsrJsonPath, JSON.stringify(jsrJson, null, jsrJsonIndent) + "\n"); + } } } diff --git a/src/utils.ts b/src/utils.ts index ca53456..68d83fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -155,7 +155,7 @@ export function findConfigPath(fileName: string): string { return resultPath; } -export function detectConfigIndention(fileContents: string): string | number { +export function detectConfigIndentation(fileContents: string): string | number { let indent: string | number = 2; // Default to 2 spaces const indentMatch = fileContents.match(/^([ \t]+)/m); From 2be37a1ea7566fc92ec71e442cec226e897c0bd9 Mon Sep 17 00:00:00 2001 From: pullfrog Date: Tue, 9 Dec 2025 01:04:02 +0000 Subject: [PATCH 6/9] Use jsr.json file existence instead of config flag Changed the implementation to automatically detect if jsr.json exists in the project rather than requiring a `jsr: true` config option. This simplifies the API - users just need to create a jsr.json file to enable JSR export writing. - Removed `jsr` property from RawConfig and NormalizedConfig interfaces - Updated jsr.json section to use try/catch with findConfigPath() - If jsr.json exists, zshy will automatically write exports to it --- src/main.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main.ts b/src/main.ts index d01b65f..5384ca1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,6 @@ interface RawConfig { conditions?: Record; tsconfig?: string; // optional path to tsconfig.json file noEdit?: boolean; - jsr?: boolean; } interface NormalizedConfig { @@ -36,7 +35,6 @@ interface NormalizedConfig { cjs: boolean; tsconfig: string; noEdit: boolean; - jsr: boolean; } export async function main(): Promise { @@ -270,7 +268,6 @@ Examples: // Normalize boolean options config.noEdit ??= false; - config.jsr ??= false; // Normalize cjs property if (config.cjs === undefined) { @@ -1039,7 +1036,15 @@ Examples: /// write jsr exports /// ////////////////////////////////// - if (config.jsr) { + // Check if jsr.json exists in the project + let jsrJsonPath: string | null = null; + try { + jsrJsonPath = findConfigPath("jsr.json"); + } catch { + // jsr.json doesn't exist, skip jsr export writing + } + + if (jsrJsonPath) { if (config.noEdit) { if (!isSilent) { log.info("[noedit] Skipping modification of jsr.json"); @@ -1049,9 +1054,6 @@ Examples: log.info(`${prefix}Updating jsr.json...`); } - // Find jsr.json by scanning up the file system - const jsrJsonPath = findConfigPath("jsr.json"); - // read jsr.json const jsrJsonRaw = fs.readFileSync(jsrJsonPath, "utf-8"); const jsrJson = JSON.parse(jsrJsonRaw); From fceaddcd3364675f31b53d4ff081f944f67ae77d Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Mon, 8 Dec 2025 22:19:03 -0500 Subject: [PATCH 7/9] Update README and test to account for pullfrog's changes --- README.md | 13 ++----------- test/jsr/package.json | 3 +-- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 32703d7..18b9140 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - 📦 **Bundler-free** — No bundler or bundler configs involved - 🟦 **No config file** — Reads from your `package.json` and `tsconfig.json` - 📝 **Declarative entrypoint map** — Specify your TypeScript entrypoints in `package.json#/zshy` -- 🤖 **Auto-generated `"exports"`** — Writes `"exports"` map directly into your `package.json` +- 🤖 **Auto-generated `"exports"`** — Writes `"exports"` map directly into your `package.json` and `jsr.json` - 🧱 **Dual-module builds** — Builds ESM and CJS outputs from a single TypeScript source file - 📂 **Unopinionated** — Use any file structure or import extension syntax you like - 📦 **Asset handling** — Non-JS assets are copied to the output directory @@ -463,16 +463,7 @@ With this addition, `zshy` will add the `"my-source"` condition to the generated ### JSR -For packages that also publish to [JSR](https://jsr.io/), you can have `zshy` copy your configured exports to `jsr.json`, making your `zshy` configuration the single source of truth for exports: - -```jsonc -{ - "zshy": { - "exports": { ... }, - "jsr": true - } -} -``` +For packages that also have a `jsr.json` file for publishing to [JSR](https://jsr.io/), `zshy` will copy your configured exports to `jsr.json/#exports`, making your `zshy` configuration the single source of truth for exports. This will copy over the paths of the source code entrypoints, not the paths to the transpiled code, since JSR supports and encourages publishing TypeScript source code rather than pairs of `.js` + `.d.ts` files. diff --git a/test/jsr/package.json b/test/jsr/package.json index cfe4ee3..2fa37a5 100644 --- a/test/jsr/package.json +++ b/test/jsr/package.json @@ -12,8 +12,7 @@ "zshy": { "exports": { ".": "./src/index.ts" - }, - "jsr": true + } }, "files": [ "dist" From 96c00e22cf331b8865a0ac32184cafb61009c3e2 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Mon, 8 Dec 2025 22:31:35 -0500 Subject: [PATCH 8/9] Fix breakage when jsr.json is not present --- README.md | 6 +++--- src/main.ts | 13 +++++++------ src/utils.ts | 9 ++------- test/__snapshots__/zshy.test.ts.snap | 3 +-- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 18b9140..8da1472 100644 --- a/README.md +++ b/README.md @@ -756,9 +756,9 @@ With this setup, your build outputs (`index.js`, etc) will be written to the pac
-### Can I prevent `zshy` from modifying my `package.json`? +### Can I prevent `zshy` from modifying my `package.json`/`jsr.json`? -Yes. If you prefer to manage your `package.json` fields manually, you can prevent `zshy` from making any changes by setting the `noEdit` option to `true` in your `package.json#/zshy` config. +Yes. If you prefer to manage your export fields manually, you can prevent `zshy` from making any changes by setting the `noEdit` option to `true` in your `package.json#/zshy` config. ```jsonc { @@ -769,7 +769,7 @@ Yes. If you prefer to manage your `package.json` fields manually, you can preven } ``` -When `noEdit` is enabled, `zshy` will build your files but will not write to `package.json`. You will be responsible for populating the `"exports"`, `"bin"`, `"main"`, `"module"`, and `"types"` fields yourself. +When `noEdit` is enabled, `zshy` will build your files but will not write to `package.json` or `jsr.json`. You will be responsible for populating the `"exports"`, `"bin"`, `"main"`, `"module"`, and `"types"` fields yourself.
diff --git a/src/main.ts b/src/main.ts index 5384ca1..36959e4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -160,6 +160,7 @@ Examples: log.info("Build will fail only on errors (default)"); } } + /////////////////////////////////// /// find and read pkg json /// /////////////////////////////////// @@ -167,6 +168,11 @@ Examples: // Find package.json by scanning up the file system const packageJsonPath = findConfigPath("package.json"); + if (!packageJsonPath) { + log.error(`❌ package.json not found in current directory or any parent directories`); + process.exit(1); + } + // read package.json and extract the "zshy" exports config const pkgJsonRaw = fs.readFileSync(packageJsonPath, "utf-8"); // console.log("📦 Extracting entry points from package.json exports..."); @@ -1037,12 +1043,7 @@ Examples: ////////////////////////////////// // Check if jsr.json exists in the project - let jsrJsonPath: string | null = null; - try { - jsrJsonPath = findConfigPath("jsr.json"); - } catch { - // jsr.json doesn't exist, skip jsr export writing - } + const jsrJsonPath = findConfigPath("jsr.json"); if (jsrJsonPath) { if (config.noEdit) { diff --git a/src/utils.ts b/src/utils.ts index 68d83fa..dea16f3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -134,7 +134,7 @@ export function isTestFile(filePath: string): boolean { return false; } -export function findConfigPath(fileName: string): string { +export function findConfigPath(fileName: string): string | null { let resultPath = `./${fileName}`; let currentDir = process.cwd(); @@ -147,12 +147,7 @@ export function findConfigPath(fileName: string): string { currentDir = path.dirname(currentDir); } - if (!fs.existsSync(resultPath)) { - log.error(`❌ ${fileName} not found in current directory or any parent directories`); - process.exit(1); - } - - return resultPath; + return fs.existsSync(resultPath) ? resultPath : null; } export function detectConfigIndentation(fileContents: string): string | number { diff --git a/test/__snapshots__/zshy.test.ts.snap b/test/__snapshots__/zshy.test.ts.snap index e9ce809..7b1928c 100644 --- a/test/__snapshots__/zshy.test.ts.snap +++ b/test/__snapshots__/zshy.test.ts.snap @@ -16,8 +16,7 @@ exports[`zshy with different tsconfig configurations > should copy exports to js » Parsed zshy config: { "exports": { ".": "./src/index.ts" - }, - "jsr": true + } } » Reading tsconfig from ./tsconfig.json » Determining entrypoints... From b65aead65094363d4921cd7a3a2828341b0f6150 Mon Sep 17 00:00:00 2001 From: Dallas Hoffman Date: Mon, 8 Dec 2025 22:49:48 -0500 Subject: [PATCH 9/9] Update test name --- src/main.ts | 2 +- test/__snapshots__/zshy.test.ts.snap | 2 +- test/zshy.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index 36959e4..b2e7f80 100644 --- a/src/main.ts +++ b/src/main.ts @@ -169,7 +169,7 @@ Examples: const packageJsonPath = findConfigPath("package.json"); if (!packageJsonPath) { - log.error(`❌ package.json not found in current directory or any parent directories`); + log.error("❌ package.json not found in current directory or any parent directories"); process.exit(1); } diff --git a/test/__snapshots__/zshy.test.ts.snap b/test/__snapshots__/zshy.test.ts.snap index 7b1928c..1d1def2 100644 --- a/test/__snapshots__/zshy.test.ts.snap +++ b/test/__snapshots__/zshy.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`zshy with different tsconfig configurations > should copy exports to jsr.json when jsr is true 1`] = ` +exports[`zshy with different tsconfig configurations > should copy exports to jsr.json when jsr.json exists 1`] = ` { "exitCode": 0, "stderr": "", diff --git a/test/zshy.test.ts b/test/zshy.test.ts index a7aec22..467cb38 100644 --- a/test/zshy.test.ts +++ b/test/zshy.test.ts @@ -173,7 +173,7 @@ describe("zshy with different tsconfig configurations", () => { expect(snapshot).toMatchSnapshot(); }); - it("should copy exports to jsr.json when jsr is true", () => { + it("should copy exports to jsr.json when jsr.json exists", () => { const snapshot = runZshyWithTsconfig("tsconfig.json", { dryRun: false, cwd: process.cwd() + "/test/jsr",