summaryrefslogtreecommitdiffhomepage
path: root/packages/sdk/scripts
diff options
context:
space:
mode:
authoradamdotdevin <[email protected]>2025-07-22 11:50:51 -0500
committeradamdotdevin <[email protected]>2025-07-22 11:50:51 -0500
commit10c8b495907069461f5dc464f6285321290c8b14 (patch)
tree9fed07910dd7d99688c0261a364d810a500e21d3 /packages/sdk/scripts
parent500cea5ce7fa635a924cd9abea63aaf672f7645d (diff)
downloadopencode-10c8b495907069461f5dc464f6285321290c8b14.tar.gz
opencode-10c8b495907069461f5dc464f6285321290c8b14.zip
chore: generate sdk into packages/sdk
Diffstat (limited to 'packages/sdk/scripts')
-rwxr-xr-xpackages/sdk/scripts/bootstrap18
-rwxr-xr-xpackages/sdk/scripts/build51
-rwxr-xr-xpackages/sdk/scripts/format12
-rwxr-xr-xpackages/sdk/scripts/lint21
-rwxr-xr-xpackages/sdk/scripts/mock41
-rwxr-xr-xpackages/sdk/scripts/test56
-rw-r--r--packages/sdk/scripts/utils/attw-report.cjs24
-rwxr-xr-xpackages/sdk/scripts/utils/check-is-in-git-install.sh9
-rw-r--r--packages/sdk/scripts/utils/check-version.cjs20
-rw-r--r--packages/sdk/scripts/utils/fix-index-exports.cjs17
-rwxr-xr-xpackages/sdk/scripts/utils/git-swap.sh13
-rw-r--r--packages/sdk/scripts/utils/make-dist-package-json.cjs21
-rw-r--r--packages/sdk/scripts/utils/postprocess-files.cjs94
-rwxr-xr-xpackages/sdk/scripts/utils/upload-artifact.sh25
14 files changed, 422 insertions, 0 deletions
diff --git a/packages/sdk/scripts/bootstrap b/packages/sdk/scripts/bootstrap
new file mode 100755
index 000000000..0af58e251
--- /dev/null
+++ b/packages/sdk/scripts/bootstrap
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then
+ brew bundle check >/dev/null 2>&1 || {
+ echo "==> Installing Homebrew dependencies…"
+ brew bundle
+ }
+fi
+
+echo "==> Installing Node dependencies…"
+
+PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm")
+
+$PACKAGE_MANAGER install
diff --git a/packages/sdk/scripts/build b/packages/sdk/scripts/build
new file mode 100755
index 000000000..c65e0faec
--- /dev/null
+++ b/packages/sdk/scripts/build
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+set -exuo pipefail
+
+cd "$(dirname "$0")/.."
+
+node scripts/utils/check-version.cjs
+
+# Build into dist and will publish the package from there,
+# so that src/resources/foo.ts becomes <package root>/resources/foo.js
+# This way importing from `"@opencode-ai/sdk/resources/foo"` works
+# even with `"moduleResolution": "node"`
+
+rm -rf dist; mkdir dist
+# Copy src to dist/src and build from dist/src into dist, so that
+# the source map for index.js.map will refer to ./src/index.ts etc
+cp -rp src README.md dist
+for file in LICENSE CHANGELOG.md; do
+ if [ -e "${file}" ]; then cp "${file}" dist; fi
+done
+if [ -e "bin/cli" ]; then
+ mkdir -p dist/bin
+ cp -p "bin/cli" dist/bin/;
+fi
+if [ -e "bin/migration-config.json" ]; then
+ mkdir -p dist/bin
+ cp -p "bin/migration-config.json" dist/bin/;
+fi
+# this converts the export map paths for the dist directory
+# and does a few other minor things
+node scripts/utils/make-dist-package-json.cjs > dist/package.json
+
+# build to .js/.mjs/.d.ts files
+./node_modules/.bin/tsc-multi
+# we need to patch index.js so that `new module.exports()` works for cjs backwards
+# compat. No way to get that from index.ts because it would cause compile errors
+# when building .mjs
+node scripts/utils/fix-index-exports.cjs
+cp tsconfig.dist-src.json dist/src/tsconfig.json
+
+node scripts/utils/postprocess-files.cjs
+
+# make sure that nothing crashes when we require the output CJS or
+# import the output ESM
+(cd dist && node -e 'require("@opencode-ai/sdk")')
+(cd dist && node -e 'import("@opencode-ai/sdk")' --input-type=module)
+
+if [ -e ./scripts/build-deno ]
+then
+ ./scripts/build-deno
+fi
diff --git a/packages/sdk/scripts/format b/packages/sdk/scripts/format
new file mode 100755
index 000000000..7a7564019
--- /dev/null
+++ b/packages/sdk/scripts/format
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running eslint --fix"
+./node_modules/.bin/eslint --fix .
+
+echo "==> Running prettier --write"
+# format things eslint didn't
+./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs'
diff --git a/packages/sdk/scripts/lint b/packages/sdk/scripts/lint
new file mode 100755
index 000000000..3ffb78a64
--- /dev/null
+++ b/packages/sdk/scripts/lint
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+echo "==> Running eslint"
+./node_modules/.bin/eslint .
+
+echo "==> Building"
+./scripts/build
+
+echo "==> Checking types"
+./node_modules/typescript/bin/tsc
+
+echo "==> Running Are The Types Wrong?"
+./node_modules/.bin/attw --pack dist -f json >.attw.json || true
+node scripts/utils/attw-report.cjs
+
+echo "==> Running publint"
+./node_modules/.bin/publint dist
diff --git a/packages/sdk/scripts/mock b/packages/sdk/scripts/mock
new file mode 100755
index 000000000..d2814ae6a
--- /dev/null
+++ b/packages/sdk/scripts/mock
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [[ -n "$1" && "$1" != '--'* ]]; then
+ URL="$1"
+ shift
+else
+ URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)"
+fi
+
+# Check if the URL is empty
+if [ -z "$URL" ]; then
+ echo "Error: No OpenAPI spec path/url provided or found in .stats.yml"
+ exit 1
+fi
+
+echo "==> Starting mock server with URL ${URL}"
+
+# Run prism mock on the given spec
+if [ "$1" == "--daemon" ]; then
+ npm exec --package=@stainless-api/[email protected] -- prism mock "$URL" &> .prism.log &
+
+ # Wait for server to come online
+ echo -n "Waiting for server"
+ while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do
+ echo -n "."
+ sleep 0.1
+ done
+
+ if grep -q "✖ fatal" ".prism.log"; then
+ cat .prism.log
+ exit 1
+ fi
+
+ echo
+else
+ npm exec --package=@stainless-api/[email protected] -- prism mock "$URL"
+fi
diff --git a/packages/sdk/scripts/test b/packages/sdk/scripts/test
new file mode 100755
index 000000000..2049e31b0
--- /dev/null
+++ b/packages/sdk/scripts/test
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+NC='\033[0m' # No Color
+
+function prism_is_running() {
+ curl --silent "http://localhost:4010" >/dev/null 2>&1
+}
+
+kill_server_on_port() {
+ pids=$(lsof -t -i tcp:"$1" || echo "")
+ if [ "$pids" != "" ]; then
+ kill "$pids"
+ echo "Stopped $pids."
+ fi
+}
+
+function is_overriding_api_base_url() {
+ [ -n "$TEST_API_BASE_URL" ]
+}
+
+if ! is_overriding_api_base_url && ! prism_is_running ; then
+ # When we exit this script, make sure to kill the background mock server process
+ trap 'kill_server_on_port 4010' EXIT
+
+ # Start the dev server
+ ./scripts/mock --daemon
+fi
+
+if is_overriding_api_base_url ; then
+ echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}"
+ echo
+elif ! prism_is_running ; then
+ echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server"
+ echo -e "running against your OpenAPI spec."
+ echo
+ echo -e "To run the server, pass in the path or url of your OpenAPI"
+ echo -e "spec to the prism command:"
+ echo
+ echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
+ echo
+
+ exit 1
+else
+ echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}"
+ echo
+fi
+
+echo "==> Running tests"
+./node_modules/.bin/jest "$@"
diff --git a/packages/sdk/scripts/utils/attw-report.cjs b/packages/sdk/scripts/utils/attw-report.cjs
new file mode 100644
index 000000000..b3477c0ef
--- /dev/null
+++ b/packages/sdk/scripts/utils/attw-report.cjs
@@ -0,0 +1,24 @@
+const fs = require('fs');
+const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems)
+ .flat()
+ .filter(
+ (problem) =>
+ !(
+ // This is intentional, if the user specifies .mjs they get ESM.
+ (
+ (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) ||
+ // This is intentional for backwards compat reasons.
+ (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) ||
+ // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules
+ // folders to better support various runtimes without triggering automatic type acquisition.
+ (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules'))
+ )
+ ),
+ );
+fs.unlinkSync('.attw.json');
+if (problems.length) {
+ process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n');
+ process.exitCode = 1;
+} else {
+ process.stdout.write('Types ok!\n');
+}
diff --git a/packages/sdk/scripts/utils/check-is-in-git-install.sh b/packages/sdk/scripts/utils/check-is-in-git-install.sh
new file mode 100755
index 000000000..1354eb432
--- /dev/null
+++ b/packages/sdk/scripts/utils/check-is-in-git-install.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Check if you happen to call prepare for a repository that's already in node_modules.
+[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] ||
+# The name of the containing directory that 'npm` uses, which looks like
+# $HOME/.npm/_cacache/git-cloneXXXXXX
+[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] ||
+# The name of the containing directory that 'yarn` uses, which looks like
+# $(yarn cache dir)/.tmp/XXXXX
+[ "$(basename "$(dirname "$PWD")")" = '.tmp' ]
diff --git a/packages/sdk/scripts/utils/check-version.cjs b/packages/sdk/scripts/utils/check-version.cjs
new file mode 100644
index 000000000..86c56dfd3
--- /dev/null
+++ b/packages/sdk/scripts/utils/check-version.cjs
@@ -0,0 +1,20 @@
+const fs = require('fs');
+const path = require('path');
+
+const main = () => {
+ const pkg = require('../../package.json');
+ const version = pkg['version'];
+ if (!version) throw 'The version property is not set in the package.json file';
+ if (typeof version !== 'string') {
+ throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`;
+ }
+
+ const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts');
+ const contents = fs.readFileSync(versionFile, 'utf8');
+ const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`);
+ fs.writeFileSync(versionFile, output);
+};
+
+if (require.main === module) {
+ main();
+}
diff --git a/packages/sdk/scripts/utils/fix-index-exports.cjs b/packages/sdk/scripts/utils/fix-index-exports.cjs
new file mode 100644
index 000000000..e5e10b3e7
--- /dev/null
+++ b/packages/sdk/scripts/utils/fix-index-exports.cjs
@@ -0,0 +1,17 @@
+const fs = require('fs');
+const path = require('path');
+
+const indexJs =
+ process.env['DIST_PATH'] ?
+ path.resolve(process.env['DIST_PATH'], 'index.js')
+ : path.resolve(__dirname, '..', '..', 'dist', 'index.js');
+
+let before = fs.readFileSync(indexJs, 'utf8');
+let after = before.replace(
+ /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m,
+ `exports = module.exports = function (...args) {
+ return new exports.default(...args)
+ }
+ $1`.replace(/^ /gm, ''),
+);
+fs.writeFileSync(indexJs, after, 'utf8');
diff --git a/packages/sdk/scripts/utils/git-swap.sh b/packages/sdk/scripts/utils/git-swap.sh
new file mode 100755
index 000000000..79d1888eb
--- /dev/null
+++ b/packages/sdk/scripts/utils/git-swap.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -exuo pipefail
+# the package is published to NPM from ./dist
+# we want the final file structure for git installs to match the npm installs, so we
+
+# delete everything except ./dist and ./node_modules
+find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' +
+
+# move everything from ./dist to .
+mv dist/* .
+
+# delete the now-empty ./dist
+rmdir dist
diff --git a/packages/sdk/scripts/utils/make-dist-package-json.cjs b/packages/sdk/scripts/utils/make-dist-package-json.cjs
new file mode 100644
index 000000000..7c24f56e2
--- /dev/null
+++ b/packages/sdk/scripts/utils/make-dist-package-json.cjs
@@ -0,0 +1,21 @@
+const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json');
+
+function processExportMap(m) {
+ for (const key in m) {
+ const value = m[key];
+ if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './');
+ else processExportMap(value);
+ }
+}
+processExportMap(pkgJson.exports);
+
+for (const key of ['types', 'main', 'module']) {
+ if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './');
+}
+
+delete pkgJson.devDependencies;
+delete pkgJson.scripts.prepack;
+delete pkgJson.scripts.prepublishOnly;
+delete pkgJson.scripts.prepare;
+
+console.log(JSON.stringify(pkgJson, null, 2));
diff --git a/packages/sdk/scripts/utils/postprocess-files.cjs b/packages/sdk/scripts/utils/postprocess-files.cjs
new file mode 100644
index 000000000..deae575e3
--- /dev/null
+++ b/packages/sdk/scripts/utils/postprocess-files.cjs
@@ -0,0 +1,94 @@
+// @ts-check
+const fs = require('fs');
+const path = require('path');
+
+const distDir =
+ process.env['DIST_PATH'] ?
+ path.resolve(process.env['DIST_PATH'])
+ : path.resolve(__dirname, '..', '..', 'dist');
+
+async function* walk(dir) {
+ for await (const d of await fs.promises.opendir(dir)) {
+ const entry = path.join(dir, d.name);
+ if (d.isDirectory()) yield* walk(entry);
+ else if (d.isFile()) yield entry;
+ }
+}
+
+async function postprocess() {
+ for await (const file of walk(distDir)) {
+ if (!/(\.d)?[cm]?ts$/.test(file)) continue;
+
+ const code = await fs.promises.readFile(file, 'utf8');
+
+ // strip out lib="dom", types="node", and types="react" references; these
+ // are needed at build time, but would pollute the user's TS environment
+ const transformed = code.replace(
+ /^ *\/\/\/ *<reference +(lib="dom"|types="(node|react)").*?\n/gm,
+ // replace with same number of characters to avoid breaking source maps
+ (match) => ' '.repeat(match.length - 1) + '\n',
+ );
+
+ if (transformed !== code) {
+ console.error(`wrote ${path.relative(process.cwd(), file)}`);
+ await fs.promises.writeFile(file, transformed, 'utf8');
+ }
+ }
+
+ const newExports = {
+ '.': {
+ require: {
+ types: './index.d.ts',
+ default: './index.js',
+ },
+ types: './index.d.mts',
+ default: './index.mjs',
+ },
+ };
+
+ for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) {
+ if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') {
+ const subpath = './' + entry.name;
+ newExports[subpath + '/*.mjs'] = {
+ default: subpath + '/*.mjs',
+ };
+ newExports[subpath + '/*.js'] = {
+ default: subpath + '/*.js',
+ };
+ newExports[subpath + '/*'] = {
+ import: subpath + '/*.mjs',
+ require: subpath + '/*.js',
+ };
+ } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) {
+ const { name, ext } = path.parse(entry.name);
+ const subpathWithoutExt = './' + name;
+ const subpath = './' + entry.name;
+ newExports[subpathWithoutExt] ||= { import: undefined, require: undefined };
+ const isModule = ext[1] === 'm';
+ if (isModule) {
+ newExports[subpathWithoutExt].import = subpath;
+ } else {
+ newExports[subpathWithoutExt].require = subpath;
+ }
+ newExports[subpath] = {
+ default: subpath,
+ };
+ }
+ }
+ await fs.promises.writeFile(
+ 'dist/package.json',
+ JSON.stringify(
+ Object.assign(
+ /** @type {Record<String, unknown>} */ (
+ JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8'))
+ ),
+ {
+ exports: newExports,
+ },
+ ),
+ null,
+ 2,
+ ),
+ );
+}
+postprocess();
diff --git a/packages/sdk/scripts/utils/upload-artifact.sh b/packages/sdk/scripts/utils/upload-artifact.sh
new file mode 100755
index 000000000..ac113aac2
--- /dev/null
+++ b/packages/sdk/scripts/utils/upload-artifact.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -exuo pipefail
+
+RESPONSE=$(curl -X POST "$URL" \
+ -H "Authorization: Bearer $AUTH" \
+ -H "Content-Type: application/json")
+
+SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url')
+
+if [[ "$SIGNED_URL" == "null" ]]; then
+ echo -e "\033[31mFailed to get signed URL.\033[0m"
+ exit 1
+fi
+
+UPLOAD_RESPONSE=$(tar -cz dist | curl -v -X PUT \
+ -H "Content-Type: application/gzip" \
+ --data-binary @- "$SIGNED_URL" 2>&1)
+
+if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
+ echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
+ echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/opencode-typescript/$SHA'\033[0m"
+else
+ echo -e "\033[31mFailed to upload artifact.\033[0m"
+ exit 1
+fi