From b21ddc20b52ea81c6308b0f46c2ca5f82becf692 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:46:58 +0000 Subject: [PATCH] feat(sdk): added npm MCP server --- .github/workflows/publish-npm.yml | 11 +- .stats.yml | 2 +- eslint.config.mjs | 2 +- packages/mcp-server/README.md | 32 ++++++ packages/mcp-server/build | 45 ++++++++ packages/mcp-server/package.json | 74 +++++++++++++ .../scripts/postprocess-dist-package-json.cjs | 12 +++ packages/mcp-server/src/index.ts | 18 ++++ packages/mcp-server/src/server.ts | 86 +++++++++++++++ .../create-classifications-universal.ts | 71 ++++++++++++ packages/mcp-server/src/tools/index.ts | 20 ++++ .../src/tools/rerankings/create-rerankings.ts | 80 ++++++++++++++ packages/mcp-server/tsc-multi.json | 7 ++ packages/mcp-server/tsconfig.build.json | 18 ++++ packages/mcp-server/tsconfig.dist-src.json | 11 ++ packages/mcp-server/tsconfig.json | 37 +++++++ release-please-config.json | 8 +- scripts/build-all | 12 +++ scripts/publish-packages.ts | 102 ++++++++++++++++++ scripts/utils/make-dist-package-json.cjs | 8 ++ 20 files changed, 652 insertions(+), 4 deletions(-) create mode 100644 packages/mcp-server/README.md create mode 100644 packages/mcp-server/build create mode 100644 packages/mcp-server/package.json create mode 100644 packages/mcp-server/scripts/postprocess-dist-package-json.cjs create mode 100644 packages/mcp-server/src/index.ts create mode 100644 packages/mcp-server/src/server.ts create mode 100644 packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts create mode 100644 packages/mcp-server/src/tools/index.ts create mode 100644 packages/mcp-server/src/tools/rerankings/create-rerankings.ts create mode 100644 packages/mcp-server/tsc-multi.json create mode 100644 packages/mcp-server/tsconfig.build.json create mode 100644 packages/mcp-server/tsconfig.dist-src.json create mode 100644 packages/mcp-server/tsconfig.json create mode 100755 scripts/build-all create mode 100644 scripts/publish-packages.ts diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 039a7d0..fd165a1 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -4,6 +4,10 @@ name: Publish NPM on: workflow_dispatch: + inputs: + path: + description: The path to run the release in, e.g. '.' or 'packages/mcp-server' + required: true release: types: [published] @@ -27,6 +31,11 @@ jobs: - name: Publish to NPM run: | - bash ./bin/publish-npm + if [ -n "${{ github.event.inputs.path }}" ]; then + PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]' + else + PATHS_RELEASED='[\".\", \"packages/mcp-server\"]' + fi + yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }" env: NPM_TOKEN: ${{ secrets.ISAACUS_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.stats.yml b/.stats.yml index 713348a..467ad62 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 2 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-861e8a85f0fb73cf4b7fc6c2b27722072ff33109459e90c17be24af15dfcbd0c.yml openapi_spec_hash: 644a0383600633ee604bb1e5b9ca025d -config_hash: 2bc262108dc3b065c16da5bb85c1e282 +config_hash: 1d603d50b7183a492ad6df5f728a1863 diff --git a/eslint.config.mjs b/eslint.config.mjs index 3fec560..813fce5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -34,7 +34,7 @@ export default tseslint.config( }, }, { - files: ['tests/**', 'examples/**'], + files: ['tests/**', 'examples/**', 'packages/**'], rules: { 'no-restricted-imports': 'off', }, diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md new file mode 100644 index 0000000..0d58172 --- /dev/null +++ b/packages/mcp-server/README.md @@ -0,0 +1,32 @@ +# Isaacus TypeScript MCP Server + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + +### Via Claude Desktop + +See [the user guide](https://modelcontextprotocol.io/quickstart/user) for setup. + +Once it's set up, add your MCP server to your `claude_desktop_config.json` file to enable it. + +The configuration file should be at: + +- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +- Windows: `%APPDATA%\Claude\claude_desktop_config.json` + +Add the following value to your `mcpServers` section. Make sure to provide any necessary environment variables (like API keys) as well. + +```json +{ + "mcpServers": { + "isaacus_api": { + "command": "npx", + "args": ["-y", "isaacus-mcp"], + "env": { + "ISAACUS_API_KEY": "My API Key" + } + } + } +} +``` diff --git a/packages/mcp-server/build b/packages/mcp-server/build new file mode 100644 index 0000000..e88aeb3 --- /dev/null +++ b/packages/mcp-server/build @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -exuo pipefail + +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; do + if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi +done + +for file in CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done + +# this converts the export map paths for the dist directory +# and does a few other minor things +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json + +# updates the `isaacus` dependency to point to NPM +node scripts/postprocess-dist-package-json.cjs + +# build to .js/.mjs/.d.ts files +npm exec tsc-multi +# we need to add exports = module.exports = Anthropic TypeScript to index.js; +# No way to get that from index.ts because it would cause compile errors +# when building .mjs +DIST_PATH=./dist node ../../scripts/utils/fix-index-exports.cjs + +# with "moduleResolution": "nodenext", if ESM resolves to index.d.ts, +# it'll have TS errors on the default import. But if it resolves to +# index.d.mts the default import will work (even though both files have +# the same export default statement) +cp dist/index.d.ts dist/index.d.mts +cp tsconfig.dist-src.json dist/src/tsconfig.json + +# Add proper Node.js shebang to the top of the file +sed -i.bak '1s;^;#!/usr/bin/env node\n;' dist/index.js +rm dist/index.js.bak + +chmod +x dist/index.js + +DIST_PATH=./dist PKG_IMPORT_PATH=isaacus-mcp/ node ../../scripts/utils/postprocess-files.cjs diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json new file mode 100644 index 0000000..ad0180d --- /dev/null +++ b/packages/mcp-server/package.json @@ -0,0 +1,74 @@ +{ + "name": "isaacus-mcp", + "version": "0.3.1", + "description": "The official MCP Server for the Isaacus API", + "author": "Isaacus ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": "github:isaacus-dev/isaacus-typescript", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "private": false, + "scripts": { + "test": "echo 'no tests defined yet' && exit 1", + "build": "bash ./build", + "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "prettier --write --cache --cache-strategy metadata . !dist", + "prepare": "npm run build", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "eslint --ext ts,js .", + "fix": "eslint --fix --ext ts,js ." + }, + "dependencies": { + "isaacus": "file:../../dist/", + "@modelcontextprotocol/sdk": "^1.6.1" + }, + "bin": { + "mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "eslint": "^8.49.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "ts-jest": "^29.1.0", + "ts-morph": "^19.0.0", + "ts-node": "^10.5.0", + "tsc-multi": "^1.1.0", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.8.2" + }, + "imports": { + "isaacus-mcp": ".", + "isaacus-mcp/*": "./src/*" + }, + "exports": { + ".": { + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "./*.mjs": { + "types": "./dist/*.d.ts", + "default": "./dist/*.mjs" + }, + "./*.js": { + "types": "./dist/*.d.ts", + "default": "./dist/*.js" + }, + "./*": { + "types": "./dist/*.d.ts", + "require": "./dist/*.js", + "default": "./dist/*.mjs" + } + } +} diff --git a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs new file mode 100644 index 0000000..bbbf395 --- /dev/null +++ b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs @@ -0,0 +1,12 @@ +const fs = require('fs'); +const pkgJson = require('../dist/package.json'); +const parentPkgJson = require('../../../package.json'); + +for (const dep in pkgJson.dependencies) { + // ensure we point to NPM instead of a local directory + if (dep === 'isaacus') { + pkgJson.dependencies[dep] = '^' + parentPkgJson.version; + } +} + +fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2)); diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts new file mode 100644 index 0000000..038f6a8 --- /dev/null +++ b/packages/mcp-server/src/index.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { server, init } from './server'; + +async function main() { + init({ server }); + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP Server running on stdio'); +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error in main():', error); + process.exit(1); + }); +} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts new file mode 100644 index 0000000..6ae5f55 --- /dev/null +++ b/packages/mcp-server/src/server.ts @@ -0,0 +1,86 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { tools, handlers, HandlerFunction } from './tools'; +import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js'; +import Isaacus from 'isaacus'; +export { tools, handlers } from './tools'; + +// Create server instance +export const server = new McpServer( + { + name: 'isaacus_api', + version: '0.3.1', + }, + { + capabilities: { + tools: {}, + }, + }, +); + +/** + * Initializes the provided MCP Server with the given tools and handlers. + * If not provided, the default client, tools and handlers will be used. + */ +export function init(params: { + server: Server | McpServer; + client?: Isaacus; + tools?: Tool[]; + handlers?: Record; +}) { + const server = params.server instanceof McpServer ? params.server.server : params.server; + const providedTools = params.tools || tools; + const providedHandlers = params.handlers || handlers; + const client = params.client || new Isaacus({}); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: providedTools, + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + const handler = providedHandlers[name]; + if (!handler) { + throw new Error(`Unknown tool: ${name}`); + } + + return executeHandler(handler, client, args); + }); +} + +/** + * Runs the provided handler with the given client and arguments. + */ +export async function executeHandler( + handler: HandlerFunction, + client: Isaacus, + args: Record | undefined, +) { + const result = await handler(client, args || {}); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +} + +export const readEnv = (env: string): string => { + let envValue = undefined; + if (typeof (globalThis as any).process !== 'undefined') { + envValue = (globalThis as any).process.env?.[env]?.trim(); + } else if (typeof (globalThis as any).Deno !== 'undefined') { + envValue = (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + if (envValue === undefined) { + throw new Error(`Environment variable ${env} is not set`); + } + return envValue; +}; diff --git a/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts b/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts new file mode 100644 index 0000000..d92321d --- /dev/null +++ b/packages/mcp-server/src/tools/classifications/universal/create-classifications-universal.ts @@ -0,0 +1,71 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Isaacus from 'isaacus'; + +export const tool: Tool = { + name: 'create_classifications_universal', + description: + 'Classify the relevance of a legal document to a query with an Isaacus universal legal AI classifier.', + inputSchema: { + type: 'object', + properties: { + model: { + type: 'string', + description: + 'The ID of the [model](https://docs.isaacus.com/models#universal-classification) to use for universal classification.', + enum: ['kanon-universal-classifier', 'kanon-universal-classifier-mini'], + }, + query: { + type: 'string', + description: + 'The [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query or, if IQL is disabled, the statement, to evaluate the text against.\n\nThe query must contain at least one non-whitespace character.\n\nUnlike the text being classified, the query cannot be so long that it exceeds the maximum input length of the universal classifier.', + }, + text: { + type: 'string', + description: 'The text to classify.\n\nThe text must contain at least one non-whitespace character.', + }, + chunking_options: { + type: 'object', + title: 'Chunking options', + description: 'Options for how to split text into smaller chunks.', + properties: { + overlap_ratio: { + type: 'number', + title: 'Unit interval (closed, open)', + description: 'A number greater than or equal to 0 and less than 1.', + }, + overlap_tokens: { + type: 'integer', + title: 'Non-negative integer', + description: 'A whole number greater than -1.', + }, + size: { + type: 'integer', + title: 'Positive integer', + description: 'A whole number greater than or equal to 1.', + }, + }, + required: [], + }, + is_iql: { + type: 'boolean', + description: + 'Whether the query should be interpreted as an [IQL](https://docs.isaacus.com/iql) query or else as a statement.', + }, + scoring_method: { + type: 'string', + description: + "The method to use for producing an overall confidence score.\n\n`auto` is the default scoring method and is recommended for most use cases. Currently, it is equivalent to `chunk_max`. In the future, it will automatically select the best method based on the model and input.\n\n`chunk_max` uses the highest confidence score of all of the text's chunks.\n\n`chunk_avg` averages the confidence scores of all of the text's chunks.\n\n`chunk_min` uses the lowest confidence score of all of the text's chunks.", + enum: ['auto', 'chunk_max', 'chunk_avg', 'chunk_min'], + }, + }, + }, +}; + +export const handler = (client: Isaacus, args: any) => { + const { ...body } = args; + return client.classifications.universal.create(body); +}; + +export default { tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts new file mode 100644 index 0000000..50510b9 --- /dev/null +++ b/packages/mcp-server/src/tools/index.ts @@ -0,0 +1,20 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Isaacus from 'isaacus'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +import create_classifications_universal from './classifications/universal/create-classifications-universal'; +import create_rerankings from './rerankings/create-rerankings'; + +export const tools: Tool[] = []; + +export type HandlerFunction = (client: Isaacus, args: any) => Promise; +export const handlers: Record = {}; + +function addEndpoint(endpoint: { tool: Tool; handler: HandlerFunction }) { + tools.push(endpoint.tool); + handlers[endpoint.tool.name] = endpoint.handler; +} + +addEndpoint(create_classifications_universal); +addEndpoint(create_rerankings); diff --git a/packages/mcp-server/src/tools/rerankings/create-rerankings.ts b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts new file mode 100644 index 0000000..1ee7786 --- /dev/null +++ b/packages/mcp-server/src/tools/rerankings/create-rerankings.ts @@ -0,0 +1,80 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import Isaacus from 'isaacus'; + +export const tool: Tool = { + name: 'create_rerankings', + description: 'Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker.', + inputSchema: { + type: 'object', + properties: { + model: { + type: 'string', + description: 'The ID of the [model](https://docs.isaacus.com/models#reranking) to use for reranking.', + enum: ['kanon-universal-classifier', 'kanon-universal-classifier-mini'], + }, + query: { + type: 'string', + description: + 'The query to evaluate the relevance of the texts to.\n\nThe query must contain at least one non-whitespace character.\n\nUnlike the texts being reranked, the query cannot be so long that it exceeds the maximum input length of the reranker.', + }, + texts: { + type: 'array', + description: + 'The texts to rerank.\n\nThere must be at least one text.\n\nThe texts must contain at least one non-whitespace character.', + items: { + type: 'string', + title: 'Non-blank string', + description: 'A string with at least one non-whitespace character.', + }, + }, + chunking_options: { + type: 'object', + title: 'Chunking options', + description: 'Options for how to split text into smaller chunks.', + properties: { + overlap_ratio: { + type: 'number', + title: 'Unit interval (closed, open)', + description: 'A number greater than or equal to 0 and less than 1.', + }, + overlap_tokens: { + type: 'integer', + title: 'Non-negative integer', + description: 'A whole number greater than -1.', + }, + size: { + type: 'integer', + title: 'Positive integer', + description: 'A whole number greater than or equal to 1.', + }, + }, + required: [], + }, + is_iql: { + type: 'boolean', + description: + 'Whether the query should be interpreted as an [Isaacus Query Language (IQL)](https://docs.isaacus.com/iql) query, which is not the case by default.\n\nIf you allow untrusted users to construct their own queries, think carefully before enabling IQL since queries can be crafted to consume an excessively large amount of tokens.', + }, + scoring_method: { + type: 'string', + description: + "The method to use for producing an overall relevance score for a text.\n\n`auto` is the default scoring method and is recommended for most use cases. Currently, it is equivalent to `chunk_max`. In the future, it will automatically select the best method based on the model and inputs.\n\n`chunk_max` uses the highest relevance score of all of a text's chunks.\n\n`chunk_avg` averages the relevance scores of all of a text's chunks.\n\n`chunk_min` uses the lowest relevance score of all of a text's chunks.", + enum: ['auto', 'chunk_max', 'chunk_avg', 'chunk_min'], + }, + top_n: { + type: 'integer', + title: 'Positive integer', + description: 'A whole number greater than or equal to 1.', + }, + }, + }, +}; + +export const handler = (client: Isaacus, args: any) => { + const { ...body } = args; + return client.rerankings.create(body); +}; + +export default { tool, handler }; diff --git a/packages/mcp-server/tsc-multi.json b/packages/mcp-server/tsc-multi.json new file mode 100644 index 0000000..4facad5 --- /dev/null +++ b/packages/mcp-server/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + { "extname": ".js", "module": "commonjs" }, + { "extname": ".mjs", "module": "esnext" } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json new file mode 100644 index 0000000..f1828ab --- /dev/null +++ b/packages/mcp-server/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "isaacus-mcp/*": ["src/*"], + "isaacus-mcp": ["src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/packages/mcp-server/tsconfig.dist-src.json b/packages/mcp-server/tsconfig.dist-src.json new file mode 100644 index 0000000..e9f2d70 --- /dev/null +++ b/packages/mcp-server/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "es2015", + "lib": ["DOM"], + "moduleResolution": "node" + } +} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json new file mode 100644 index 0000000..1cf9ca3 --- /dev/null +++ b/packages/mcp-server/tsconfig.json @@ -0,0 +1,37 @@ +{ + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "isaacus-mcp/*": ["src/*"], + "isaacus-mcp": ["src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + + "skipLibCheck": true + } +} diff --git a/release-please-config.json b/release-please-config.json index 624ed99..b190980 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -62,6 +62,12 @@ "release-type": "node", "extra-files": [ "src/version.ts", - "README.md" + "README.md", + "packages/mcp-server/yarn.lock", + { + "type": "json", + "path": "packages/mcp-server/package.json", + "jsonpath": "$.version" + } ] } diff --git a/scripts/build-all b/scripts/build-all new file mode 100755 index 0000000..8ac03ea --- /dev/null +++ b/scripts/build-all @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -exuo pipefail + +# build the core SDK package and all sub-packages + +bash ./scripts/build + +for dir in packages/*; do + if [ -d "$dir" ]; then + (cd "$dir" && yarn install && yarn build) + fi +done diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 0000000..50e93fe --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,102 @@ +/** + * Called from the `create-releases.yml` workflow with the output + * of the release please action as the first argument. + * + * Example JSON input: + * + * ```json + { + "releases_created": "true", + "release_created": "true", + "id": "137967744", + "name": "sdk: v0.14.5", + "tag_name": "sdk-v0.14.5", + "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "body": "## 0.14.5 (2024-01-22)\n\n...", + "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5", + "draft": "false", + "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}", + "path": ".", + "version": "0.14.5", + "major": "0", + "minor": "14", + "patch": "5", + "packages/additional-sdk--release_created": "true", + "packages/additional-sdk--id": "137967756", + "packages/additional-sdk--name": "additional-sdk: v0.5.2", + "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2", + "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...", + "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2", + "packages/additional-sdk--draft": "false", + "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}", + "packages/additional-sdk--path": "packages/additional-sdk", + "packages/additional-sdk--version": "0.5.2", + "packages/additional-sdk--major": "0", + "packages/additional-sdk--minor": "5", + "packages/additional-sdk--patch": "2", + "paths_released": "[\".\",\"packages/additional-sdk\"]" + } + ``` + */ + +import { execSync } from 'child_process'; +import path from 'path'; + +function main() { + const data = process.argv[2] ?? process.env['DATA']; + if (!data) { + throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`); + } + + const rootDir = path.join(__dirname, '..'); + console.log('root dir', rootDir); + console.log(`publish-packages called with ${data}`); + + const outputs = JSON.parse(data); + + const rawPaths = outputs.paths_released; + + if (!rawPaths) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs to contain a truthy `paths_released` property'); + } + if (typeof rawPaths !== 'string') { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be a JSON string'); + } + + const paths = JSON.parse(rawPaths); + if (!Array.isArray(paths)) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be an array'); + } + if (!paths.length) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to contain at least one entry'); + } + + const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm'); + console.log('Using publish script at', publishScriptPath); + + console.log('Ensuring root package is built'); + console.log(`$ yarn build`); + execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' }); + + for (const relPackagePath of paths) { + console.log('\n'); + + const packagePath = path.join(rootDir, relPackagePath); + console.log(`Publishing in directory: ${packagePath}`); + + console.log(`$ yarn install`); + execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + + console.log(`$ bash ${publishScriptPath}`); + execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + } + + console.log('Finished publishing packages'); +} + +main(); diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs index 7c24f56..4d6634e 100644 --- a/scripts/utils/make-dist-package-json.cjs +++ b/scripts/utils/make-dist-package-json.cjs @@ -12,6 +12,14 @@ processExportMap(pkgJson.exports); for (const key of ['types', 'main', 'module']) { if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); } +// Fix bin paths if present +if (pkgJson.bin) { + for (const key in pkgJson.bin) { + if (typeof pkgJson.bin[key] === 'string') { + pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './'); + } + } +} delete pkgJson.devDependencies; delete pkgJson.scripts.prepack;