Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<br/>
<br/>
<br/>
Expand Down Expand Up @@ -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");
Comment thread
colinhacks marked this conversation as resolved.
}

export default hello;
Expand All @@ -696,7 +711,7 @@ export default hello;

```ts
function hello() {
console.log('hello');
console.log("hello");
}
exports.default = hello;
module.exports = exports.default;
Expand Down
77 changes: 52 additions & 25 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,6 +26,7 @@ interface RawConfig {
conditions?: Record<string, "esm" | "cjs" | "src">;
tsconfig?: string; // optional path to tsconfig.json file
noEdit?: boolean;
jsr?: boolean;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @pullfrog drop this. zshy should write to jsr.json if it exists. the existence of the file is the way to enable this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the jsr config option. Now zshy automatically detects if jsr.json exists and writes exports to it if present.

Pullfrog  | Triggered by Pullfrog | Using Claude Code | View workflow run | 𝕏

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Removed the jsr config option. Now zshy automatically detects and writes to jsr.json if it exists in the project.

Pullfrog  | Triggered by Pullfrog | Using Claude Code | View workflow run | 𝕏

}

interface NormalizedConfig {
Expand All @@ -33,6 +36,7 @@ interface NormalizedConfig {
cjs: boolean;
tsconfig: string;
noEdit: boolean;
jsr: boolean;
}

export async function main(): Promise<void> {
Expand Down Expand Up @@ -163,36 +167,15 @@ 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");
// console.log("📦 Extracting entry points from package.json exports...");
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);
Expand Down Expand Up @@ -285,11 +268,14 @@ Examples:

const config = { ...rawConfig } as NormalizedConfig;

// Normalize boolean options
config.noEdit ??= false;
config.jsr ??= false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving the config.jsr normalization after the config.cjs normalization and validation block (after line 290) to keep all boolean option normalizations together in one place, rather than splitting them.


// 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) {
Expand Down Expand Up @@ -1045,7 +1031,48 @@ 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");
}
}

//////////////////////////////////
/// 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");
}
}
Comment thread
DallasHoff marked this conversation as resolved.
Outdated

Expand Down
35 changes: 35 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as ts from "typescript";

Expand Down Expand Up @@ -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 {
Comment thread
DallasHoff marked this conversation as resolved.
Outdated
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;
}
104 changes: 104 additions & 0 deletions test/__snapshots__/zshy.test.ts.snap
Original file line number Diff line number Diff line change
@@ -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: <pm>
» Build will fail only on errors (default)
» Detected project root: <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": "<root>/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,
Expand Down
10 changes: 10 additions & 0 deletions test/jsr/dist/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use strict";
Comment thread
colinhacks marked this conversation as resolved.
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
1 change: 1 addition & 0 deletions test/jsr/dist/index.cjs.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/jsr/dist/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Main entry point for the test library
*/
export declare function hi(): void;
//# sourceMappingURL=index.d.ts.map
1 change: 1 addition & 0 deletions test/jsr/dist/index.d.cts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/jsr/dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Main entry point for the test library
*/
export declare function hi(): void;
//# sourceMappingURL=index.d.ts.map
1 change: 1 addition & 0 deletions test/jsr/dist/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/jsr/dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/jsr/dist/index.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions test/jsr/jsr.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading
Loading