Skip to content

Commit

Permalink
ci: reenable deno builds (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot authored Oct 22, 2023
1 parent 2dd005c commit a8c5d82
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 1 deletion.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ npm install --save openai
yarn add openai
```

You can import in Deno via:

```ts
import OpenAI from 'https://raw.githubusercontent.com/openai/openai-node/v4.12.4-deno/mod.ts';
```

## Usage

The full API of this library can be found in [api.md file](https://github.com/openai/openai-node/blob/master/api.md). The code below shows how to get started using the chat completions API.
Expand Down
25 changes: 25 additions & 0 deletions build-deno
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -exuo pipefail

rm -rf deno; mkdir deno
cp -rp src/* README.md deno
rm deno/_shims/auto/*-node.ts
for dir in deno/_shims deno/_shims/auto; do
rm "${dir}"/*.{d.ts,js,mjs}
for file in "${dir}"/*-deno.ts; do
mv -- "$file" "${file%-deno.ts}.ts"
done
done
for file in LICENSE CHANGELOG.md; do
if [ -e "${file}" ]; then cp "${file}" deno; fi
done
npm exec ts-node -- scripts/denoify.ts
deno fmt deno
deno check deno/mod.ts
if [ -e deno_tests ]; then
deno test deno_tests --allow-env
fi

# make sure that nothing crashes when we load the Deno module
(cd deno && deno run mod.ts)
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
"format": "prettier --write --cache --cache-strategy metadata . !dist",
"tsn": "ts-node -r tsconfig-paths/register",
"lint": "eslint --ext ts,js .",
"fix": "eslint --fix --ext ts,js ."
"fix": "eslint --fix --ext ts,js .",
"postpublish": "bash scripts/git-publish-deno.sh"
},
"dependencies": {
"@types/node": "^18.11.18",
Expand Down
229 changes: 229 additions & 0 deletions scripts/denoify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import path from 'path';
import * as tm from 'ts-morph';
import { name as pkgName } from '../package.json';
import fs from 'fs';

const rootDir = path.resolve(__dirname, '..');
const denoDir = path.join(rootDir, 'deno');
const tsConfigFilePath = path.join(rootDir, 'tsconfig.deno.json');

async function denoify() {
const project = new tm.Project({ tsConfigFilePath });

for (const file of project.getSourceFiles()) {
if (!file.getFilePath().startsWith(denoDir + '/')) continue;

let addedBuffer = false,
addedProcess = false;
file.forEachDescendant((node) => {
switch (node.getKind()) {
case tm.ts.SyntaxKind.ExportDeclaration: {
const decl: tm.ExportDeclaration = node as any;
if (decl.isTypeOnly()) return;
for (const named of decl.getNamedExports()) {
// Convert `export { Foo } from './foo.ts'`
// to `export { type Foo } from './foo.ts'`
// if `./foo.ts` only exports types for `Foo`
if (!named.isTypeOnly() && !hasValueDeclarations(named)) {
named.replaceWithText(`type ${named.getText()}`);
}
}
break;
}
case tm.ts.SyntaxKind.ImportEqualsDeclaration: {
const decl: tm.ImportEqualsDeclaration = node as any;
if (decl.isTypeOnly()) return;

const ref = decl.getModuleReference();
if (!hasValueDeclarations(ref)) {
const params = isBuiltinType(ref.getType()) ? [] : ref.getType().getTypeArguments();
if (params.length) {
const paramsStr = params.map((p: tm.TypeParameter) => p.getText()).join(', ');
const bindingsStr = params
.map((p: tm.TypeParameter) => p.getSymbol()?.getName() || p.getText())
.join(', ');
decl.replaceWithText(
`export type ${decl.getName()}<${paramsStr}> = ${ref.getText()}<${bindingsStr}>`,
);
} else {
decl.replaceWithText(`export type ${decl.getName()} = ${ref.getText()}`);
}
}
break;
}
case tm.ts.SyntaxKind.Identifier: {
const id = node as tm.Identifier;
if (!addedBuffer && id.getText() === 'Buffer') {
addedBuffer = true;
file?.addVariableStatement({
declarations: [
{
name: 'Buffer',
type: 'any',
},
],
hasDeclareKeyword: true,
});
file?.addTypeAlias({
name: 'Buffer',
type: 'any',
});
}
if (!addedProcess && id.getText() === 'process') {
addedProcess = true;
file?.addVariableStatement({
declarations: [
{
name: 'process',
type: 'any',
},
],
hasDeclareKeyword: true,
});
}
}
}
});
}

await project.save();

for (const file of project.getSourceFiles()) {
if (!file.getFilePath().startsWith(denoDir + '/')) continue;
for (const decl of [...file.getImportDeclarations(), ...file.getExportDeclarations()]) {
const moduleSpecifier = decl.getModuleSpecifier();
if (!moduleSpecifier) continue;
let specifier = moduleSpecifier.getLiteralValue().replace(/^node:/, '');
if (!specifier || specifier.startsWith('http')) continue;

if (nodeStdModules.has(specifier)) {
// convert node builtins to deno.land/std
specifier = `https://deno.land/std@0.177.0/node/${specifier}.ts`;
} else if (specifier.startsWith(pkgName + '/')) {
// convert self-referencing module specifiers to relative paths
specifier = file.getRelativePathAsModuleSpecifierTo(denoDir + specifier.substring(pkgName.length));
} else if (specifier === 'qs') {
decl.replaceWithText(`import { qs } from "https://deno.land/x/deno_qs@0.0.1/mod.ts"`);
continue;
} else if (!decl.isModuleSpecifierRelative()) {
specifier = `npm:${specifier}`;
}

if (specifier.startsWith('./') || specifier.startsWith('../')) {
// there may be CJS directory module specifiers that implicitly resolve
// to /index.ts. Add an explicit /index.ts to the end
const sourceFile = decl.getModuleSpecifierSourceFile();
if (sourceFile && /\/index\.ts$/.test(sourceFile.getFilePath()) && !/\/mod\.ts$/.test(specifier)) {
if (/\/index(\.ts)?$/.test(specifier)) {
specifier = specifier.replace(/\/index(\.ts)?$/, '/mod.ts');
} else {
specifier += '/mod.ts';
}
}
// add explicit .ts file extensions to relative module specifiers
specifier = specifier.replace(/(\.[^./]*)?$/, '.ts');
}
moduleSpecifier.replaceWithText(JSON.stringify(specifier));
}
}

await project.save();

await Promise.all(
project.getSourceFiles().map(async (f) => {
const filePath = f.getFilePath();
if (filePath.endsWith('index.ts')) {
const newPath = filePath.replace(/index\.ts$/, 'mod.ts');
await fs.promises.rename(filePath, newPath);
}
}),
);
}

const nodeStdModules = new Set([
'assert',
'assertion_error',
'async_hooks',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'diagnostics_channel',
'dns',
'domain',
'events',
'fs',
'global',
'http',
'http2',
'https',
'inspector',
'module_all',
'module_esm',
'module',
'net',
'os',
'path',
'perf_hooks',
'process',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'sys',
'timers',
'tls',
'tty',
'upstream_modules',
'url',
'util',
'v8',
'vm',
'wasi',
'worker_threads',
'zlib',
]);

const typeDeclarationKinds = new Set([
tm.ts.SyntaxKind.InterfaceDeclaration,
tm.ts.SyntaxKind.ModuleDeclaration,
tm.ts.SyntaxKind.TypeAliasDeclaration,
]);

const builtinTypeNames = new Set(['Array', 'Set', 'Map', 'Record', 'Promise']);

function isBuiltinType(type: tm.Type): boolean {
const symbol = type.getSymbol();
return (
symbol != null &&
builtinTypeNames.has(symbol.getName()) &&
symbol.getDeclarations().some((d) => d.getSourceFile().getFilePath().includes('node_modules/typescript'))
);
}

function hasValueDeclarations(nodes?: tm.Node): boolean;
function hasValueDeclarations(nodes?: tm.Node[]): boolean;
function hasValueDeclarations(nodes?: tm.Node | tm.Node[]): boolean {
if (nodes && !Array.isArray(nodes)) {
return (
!isBuiltinType(nodes.getType()) && hasValueDeclarations(nodes.getType().getSymbol()?.getDeclarations())
);
}
return nodes ?
nodes.some((n) => {
const parent = n.getParent();
return (
!typeDeclarationKinds.has(n.getKind()) &&
// sometimes the node will be the right hand side of a type alias
(!parent || !typeDeclarationKinds.has(parent.getKind()))
);
})
: false;
}

denoify();
63 changes: 63 additions & 0 deletions scripts/git-publish-deno.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash

# This script pushes the contents of the `deno`` directory to the `deno` branch,
# and creates a `vx.x.x-deno` tag, so that Deno users can
# import OpenAI from "https://raw.githubusercontent.com/openai/openai-node/vx.x.x-deno/mod.ts"

# It's also possible to publish to deno.land. You can do this by:
# - Creating a separate GitHub repo
# - Add the deno.land webhook to the repo as described at https://deno.com/add_module
# - Set the following environment variables when running this script:
# - DENO_PUSH_REMOTE_URL - the remote url of the separate GitHub repo
# - DENO_PUSH_BRANCH - the branch you want to push to in that repo (probably `main`)
# - DENO_PUSH_VERSION - defaults to version in package.json
# - DENO_PUSH_RELEASE_TAG - defaults to v$DENO_PUSH_VERSION-deno

die () {
echo >&2 "$@"
exit 1
}

set -exuo pipefail

# Allow caller to set the following environment variables, but provide defaults
# if unset
# : "${FOO:=bar}" sets FOO=bar unless it's set and non-empty
# https://stackoverflow.com/questions/307503/whats-a-concise-way-to-check-that-environment-variables-are-set-in-a-unix-shell
# https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

: "${DENO_PUSH_VERSION:=$(node -p 'require("./package.json").version')}"
: "${DENO_PUSH_BRANCH:=deno}"
: "${DENO_PUSH_REMOTE_URL:=$(git remote get-url origin)}"
: "${DENO_PUSH_RELEASE_TAG:="v$DENO_PUSH_VERSION-deno"}"

if [ ! -e deno ]; then ./build; fi

# We want to commit and push a branch where everything inside the deno
# directory is at root level in the branch.

# We can do this by temporarily creating a git repository inside deno,
# committing files to the branch, and pushing it to the remote.

cd deno
rm -rf .git
git init
git remote add origin "$DENO_PUSH_REMOTE_URL"
if git fetch origin "$DENO_PUSH_RELEASE_TAG"; then
die "Tag $DENO_PUSH_RELEASE_TAG already exists"
fi
if git fetch origin "$DENO_PUSH_BRANCH"; then
# the branch already exists on the remote; "check out" the branch without
# changing files in the working directory
git branch "$DENO_PUSH_BRANCH" -t origin/"$DENO_PUSH_BRANCH"
git symbolic-ref HEAD refs/heads/"$DENO_PUSH_BRANCH"
git reset
else
# the branch doesn't exist on the remote yet
git checkout -b "$DENO_PUSH_BRANCH"
fi
git add .
git commit -m "chore(deno): release $DENO_PUSH_VERSION"
git tag -a "$DENO_PUSH_RELEASE_TAG" -m "release $DENO_PUSH_VERSION"
git push --tags --set-upstream origin "$DENO_PUSH_BRANCH"
rm -rf .git

0 comments on commit a8c5d82

Please sign in to comment.