Skip to content

Commit d0e210d

Browse files
feat: improve docs for jsr README.md (#1208)
1 parent d40c61c commit d0e210d

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

scripts/build-deno

+2
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ done
2525
for file in README.md LICENSE CHANGELOG.md; do
2626
if [ -e "${file}" ]; then cp "${file}" dist-deno; fi
2727
done
28+
29+
node scripts/utils/convert-jsr-readme.cjs ./dist-deno/README.md

scripts/utils/convert-jsr-readme.cjs

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
const fs = require('fs');
2+
const { parse } = require('@typescript-eslint/parser');
3+
const { TSError } = require('@typescript-eslint/typescript-estree');
4+
5+
/**
6+
* Quick and dirty AST traversal
7+
*/
8+
function traverse(node, visitor) {
9+
if (!node || typeof node.type !== 'string') return;
10+
visitor.node?.(node);
11+
visitor[node.type]?.(node);
12+
for (const key in node) {
13+
const value = node[key];
14+
if (Array.isArray(value)) {
15+
for (const elem of value) traverse(elem, visitor);
16+
} else if (value instanceof Object) {
17+
traverse(value, visitor);
18+
}
19+
}
20+
}
21+
22+
/**
23+
* Helper method for replacing arbitrary ranges of text in input code.
24+
*/
25+
function replaceRanges(code, replacer) {
26+
const replacements = [];
27+
replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) });
28+
29+
if (!replacements.length) return code;
30+
replacements.sort((a, b) => a.range[0] - b.range[0]);
31+
const overlapIndex = replacements.findIndex(
32+
(r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0],
33+
);
34+
if (overlapIndex >= 0) {
35+
throw new Error(
36+
`replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify(
37+
replacements[overlapIndex],
38+
)}`,
39+
);
40+
}
41+
42+
const parts = [];
43+
let end = 0;
44+
for (const {
45+
range: [from, to],
46+
replacement,
47+
} of replacements) {
48+
if (from > end) parts.push(code.substring(end, from));
49+
parts.push(replacement);
50+
end = to;
51+
}
52+
if (end < code.length) parts.push(code.substring(end));
53+
return parts.join('');
54+
}
55+
56+
function replaceProcessEnv(content) {
57+
// Replace process.env['KEY'] and process.env.KEY with Deno.env.get('KEY')
58+
return content.replace(/process\.env(?:\.|\[['"])(.+?)(?:['"]\])/g, "Deno.env.get('$1')");
59+
}
60+
61+
function replaceProcessStdout(content) {
62+
return content.replace(/process\.stdout.write\(([^)]+)\)/g, 'Deno.stdout.writeSync($1)');
63+
}
64+
65+
function replaceInstallationDirections(content) {
66+
// Remove npm installation section
67+
return content.replace(/```sh\nnpm install.*?\n```.*### Installation from JSR\n\n/s, '');
68+
}
69+
70+
/**
71+
* Maps over module paths in imports and exports
72+
*/
73+
function replaceImports(code, config) {
74+
try {
75+
const ast = parse(code, { sourceType: 'module', range: true });
76+
return replaceRanges(code, ({ replace }) =>
77+
traverse(ast, {
78+
node(node) {
79+
switch (node.type) {
80+
case 'ImportDeclaration':
81+
case 'ExportNamedDeclaration':
82+
case 'ExportAllDeclaration':
83+
case 'ImportExpression':
84+
if (node.source) {
85+
const { range, value } = node.source;
86+
if (value.startsWith(config.npm)) {
87+
replace(range, JSON.stringify(value.replace(config.npm, config.jsr)));
88+
}
89+
}
90+
}
91+
},
92+
}),
93+
);
94+
} catch (e) {
95+
if (e instanceof TSError) {
96+
// This can error if the code block is not valid TS, in this case give up trying to transform the imports.
97+
console.warn(`Original codeblock could not be parsed, replace import skipped: ${e}\n\n${code}`);
98+
return code;
99+
}
100+
throw e;
101+
}
102+
}
103+
104+
function processReadme(config, file) {
105+
try {
106+
let readmeContent = fs.readFileSync(file, 'utf8');
107+
108+
// First replace installation directions
109+
readmeContent = replaceInstallationDirections(readmeContent);
110+
111+
// Replace content in all code blocks with a single regex
112+
readmeContent = readmeContent.replaceAll(
113+
/```(?:typescript|ts|javascript|js)\n([\s\S]*?)```/g,
114+
(match, codeBlock) => {
115+
try {
116+
let transformedCode = codeBlock.trim();
117+
transformedCode = replaceImports(transformedCode, config);
118+
transformedCode = replaceProcessEnv(transformedCode);
119+
transformedCode = replaceProcessStdout(transformedCode);
120+
return '```typescript\n' + transformedCode + '\n```';
121+
} catch (error) {
122+
console.warn(`Failed to transform code block: ${error}\n\n${codeBlock}`);
123+
return match; // Return original code block if transformation fails
124+
}
125+
},
126+
);
127+
128+
fs.writeFileSync(file, readmeContent);
129+
} catch (error) {
130+
console.error('Error processing README:', error);
131+
throw error;
132+
}
133+
}
134+
135+
const config = {
136+
npm: 'openai',
137+
jsr: '@openai/openai',
138+
};
139+
140+
processReadme(config, process.argv[2]);

0 commit comments

Comments
 (0)