Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
65 changes: 51 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ jobs:
tag: ${{ steps.release_metadata.outputs.tag }}
dist_tag: ${{ steps.release_metadata.outputs.dist_tag }}
prerelease: ${{ steps.release_metadata.outputs.prerelease }}
tarball_name: ${{ steps.release_metadata.outputs.tarball_name }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
Expand Down Expand Up @@ -50,7 +49,6 @@ jobs:
"dist_tag=\(.distTag)",
"prerelease=\(.prerelease)",
"package_spec=\(.packageSpec)",
"tarball_name=\(.tarballName)",
"should_publish=\(.shouldPublish)"
' <<< "${release_metadata_json}" >> "${GITHUB_OUTPUT}"
jq -r '.releaseNotes' <<< "${release_metadata_json}" > ./release-notes.md
Expand All @@ -67,16 +65,23 @@ jobs:
if: steps.release_metadata.outputs.should_publish == 'true'
run: npm run build:npm

- name: Pack npmDist package
- name: Upload npmDist package
if: steps.release_metadata.outputs.should_publish == 'true'
run: npm pack ./npmDist --pack-destination . > /dev/null
uses: actions/upload-artifact@v4
with:
name: npmDist
path: ./npmDist

- name: Build Deno package
if: steps.release_metadata.outputs.should_publish == 'true'
run: npm run build:deno

- name: Upload npm package tarball
- name: Upload denoDist package
if: steps.release_metadata.outputs.should_publish == 'true'
uses: actions/upload-artifact@v4
with:
name: npmDist-tarball
path: ./${{ steps.release_metadata.outputs.tarball_name }}
name: denoDist
path: ./denoDist

- name: Upload release notes
if: steps.release_metadata.outputs.should_publish == 'true'
Expand All @@ -89,7 +94,7 @@ jobs:
name: Publish npm package
needs: check-publish
# Keep this guard on every job for defense-in-depth in case job dependencies are refactored.
if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-js' && github.ref_name == '17.x.x' && needs.check-publish.outputs.should_publish == 'true' && needs.check-publish.result == 'success' }}
if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-js' && github.ref_name == '17.x.x' && needs.check-publish.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
environment: release
permissions:
Expand All @@ -109,22 +114,54 @@ jobs:
- name: Download npmDist package
uses: actions/download-artifact@v4
with:
name: npmDist-tarball
path: ./artifacts
name: npmDist
path: ./npmDist

- name: Dry-run npm publish
- name: Publish to npm
working-directory: ./npmDist
run: |
if [ -n "${{ needs.check-publish.outputs.dist_tag }}" ]; then
npm publish --provenance --tag "${{ needs.check-publish.outputs.dist_tag }}" "./artifacts/${{ needs.check-publish.outputs.tarball_name }}"
npm publish --provenance --tag "${{ needs.check-publish.outputs.dist_tag }}"
else
npm publish --provenance "./artifacts/${{ needs.check-publish.outputs.tarball_name }}"
npm publish --provenance
fi

publish-jsr:
name: Publish JSR package
needs: check-publish
# Keep this guard on every job for defense-in-depth in case job dependencies are refactored.
if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-js' && github.ref_name == '17.x.x' && needs.check-publish.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
environment: release
permissions:
contents: read # for actions/checkout
id-token: write # for JSR trusted publishing via OIDC
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Download denoDist package
uses: actions/download-artifact@v4
with:
name: denoDist
path: ./denoDist

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Publish to JSR
working-directory: ./denoDist
run: deno publish

create-release:
name: Create release
needs: check-publish
# Keep this guard on every job for defense-in-depth in case job dependencies are refactored.
if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-js' && github.ref_name == '17.x.x' && needs.check-publish.outputs.should_publish == 'true' && needs.check-publish.result == 'success' }}
if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-js' && github.ref_name == '17.x.x' && needs.check-publish.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
environment: release
permissions:
Expand Down
6 changes: 6 additions & 0 deletions integrationTests/dev-deno-with-deno-build/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"imports": {
"graphql": "../graphql-deno-dist/__dev__/index.ts",
"graphql/": "../graphql-deno-dist/__dev__/"
}
}
7 changes: 7 additions & 0 deletions integrationTests/dev-deno-with-deno-build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"description": "graphql-js development mode should work with Deno with deno build",
"private": true,
"scripts": {
"test": "docker run --rm --volume \"$PWD/..\":/usr/src/app -w /usr/src/app/dev-deno-with-deno-build denoland/deno:alpine-\"$DENO_VERSION\" deno run test.js"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "graphql-js development mode should work with Deno",
"description": "graphql-js development mode should work with Deno with node build",
"private": true,
"scripts": {
"test": "docker run --rm --volume \"$PWD\":/usr/src/app -w /usr/src/app denoland/deno:alpine-\"$DENO_VERSION\" deno run --conditions=development test.js"
Expand Down
18 changes: 18 additions & 0 deletions integrationTests/dev-deno-with-node-build/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isObjectType } from 'graphql';

class GraphQLObjectType {
get [Symbol.toStringTag]() {
return 'GraphQLObjectType';
}
}

try {
isObjectType(new GraphQLObjectType());
throw new Error(
'Expected isObjectType to throw an error in Deno development mode.',
);
} catch (error) {
if (!error.message.includes('from another module or realm')) {
throw error;
}
}
6 changes: 6 additions & 0 deletions integrationTests/prod-deno-with-deno-build/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"imports": {
"graphql": "../graphql-deno-dist/index.ts",
"graphql/": "../graphql-deno-dist/"
}
}
7 changes: 7 additions & 0 deletions integrationTests/prod-deno-with-deno-build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"description": "graphql-js production mode should work with Deno with deno build",
"private": true,
"scripts": {
"test": "docker run --rm --volume \"$PWD/..\":/usr/src/app -w /usr/src/app/prod-deno-with-deno-build denoland/deno:alpine-\"$DENO_VERSION\" deno run test.js"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "graphql-js production mode should work with Deno",
"description": "graphql-js production mode should work with Deno with node build",
"private": true,
"scripts": {
"test": "docker run --rm --volume \"$PWD\":/usr/src/app -w /usr/src/app denoland/deno:alpine-\"$DENO_VERSION\" deno run test.js"
Expand Down
12 changes: 12 additions & 0 deletions integrationTests/prod-deno-with-node-build/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { isObjectType } from 'graphql';

class GraphQLObjectType {
get [Symbol.toStringTag]() {
return 'GraphQLObjectType';
}
}

const result = isObjectType(new GraphQLObjectType());
if (result !== false) {
throw new Error('isObjectType should return false in Deno production mode.');
}
146 changes: 120 additions & 26 deletions resources/build-deno.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';

Expand All @@ -6,43 +7,136 @@ import ts from 'typescript';
import { changeExtensionInImportPaths } from './change-extension-in-import-paths.js';
import { inlineInvariant } from './inline-invariant.js';
import {
buildESMDevModeStub,
prettify,
readPackageJSON,
readTSConfig,
showDirStats,
writeGeneratedFile,
} from './utils.js';

fs.rmSync('./denoDist', { recursive: true, force: true });
fs.mkdirSync('./denoDist');
console.log('\n./denoDist');
await buildPackage('./denoDist');
showDirStats('./denoDist');

async function buildPackage(outDir: string): Promise<void> {
const devDir = path.join(outDir, '__dev__');
fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(outDir);
fs.mkdirSync(devDir);

const emittedTSFiles = await emitTSFiles(outDir);
emitDevTSFiles(outDir, devDir, emittedTSFiles);
await writeJSRConfig(outDir, emittedTSFiles);

fs.copyFileSync('./LICENSE', path.join(outDir, 'LICENSE'));
fs.copyFileSync('./README.md', path.join(outDir, 'README.md'));
}

async function emitTSFiles(outDir: string): Promise<ReadonlyArray<string>> {
const emittedTSFiles = [];
const tsProgram = ts.createProgram(['src/index.ts'], readTSConfig());

for (const sourceFile of tsProgram.getSourceFiles()) {
if (
tsProgram.isSourceFileFromExternalLibrary(sourceFile) ||
tsProgram.isSourceFileDefaultLibrary(sourceFile)
) {
continue;
}

const tsProgram = ts.createProgram(['src/index.ts'], readTSConfig());
for (const sourceFile of tsProgram.getSourceFiles()) {
if (
tsProgram.isSourceFileFromExternalLibrary(sourceFile) ||
tsProgram.isSourceFileDefaultLibrary(sourceFile)
) {
continue;
const transformed = ts.transform(sourceFile, [
changeExtensionInImportPaths({ extension: '.ts' }),
inlineInvariant,
]);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const newContent = printer.printBundle(
ts.factory.createBundle(transformed.transformed),
);

transformed.dispose();

const filepath = path.relative('./src', sourceFile.fileName);
const destPath = path.join(outDir, filepath);
// eslint-disable-next-line no-await-in-loop
const prettified = await prettify(destPath, newContent);
writeGeneratedFile(destPath, prettified);
emittedTSFiles.push(filepath);
}

const transformed = ts.transform(sourceFile, [
changeExtensionInImportPaths({ extension: '.ts' }),
inlineInvariant,
]);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const newContent = printer.printBundle(
ts.factory.createBundle(transformed.transformed),
);
return emittedTSFiles.sort((a, b) => a.localeCompare(b));
}

function emitDevTSFiles(
outDir: string,
devDir: string,
emittedTSFiles: ReadonlyArray<string>,
): void {
for (const filepath of emittedTSFiles) {
const devPath = path.join(devDir, filepath);
const relativePathToOutDir = path.relative(path.dirname(devPath), outDir);

transformed.dispose();
writeGeneratedFile(
devPath,
buildESMDevModeStub(
`${relativePathToOutDir}/devMode.ts`,
`${relativePathToOutDir}/${filepath}`,
),
);
}
}

const filepath = path.relative('./src', sourceFile.fileName);
const destPath = path.join('./denoDist', filepath);
// eslint-disable-next-line no-await-in-loop
const prettified = await prettify(destPath, newContent);
writeGeneratedFile(destPath, prettified);
interface JSRConfig {
name: string;
version: string;
exports: { [entrypoint: string]: string };
}

fs.copyFileSync('./LICENSE', './denoDist/LICENSE');
fs.copyFileSync('./README.md', './denoDist/README.md');
async function writeJSRConfig(
outDir: string,
emittedTSFiles: ReadonlyArray<string>,
): Promise<void> {
const jsrConfigPath = path.join(outDir, 'jsr.json');

showDirStats('./denoDist');
const { version } = readPackageJSON();
const jsrExports: { [entrypoint: string]: string } = {};

for (const filepath of emittedTSFiles) {
const devEntrypointPath = `./__dev__/${filepath}`;
const devEntrypointKey = `./dev/${filepath}`;
setJSRExport(jsrExports, devEntrypointKey, devEntrypointPath);
}

for (const filepath of emittedTSFiles) {
const prodEntrypointPath = `./${filepath}`;
const prodEntrypointKey = `./${filepath}`;
setJSRExport(jsrExports, prodEntrypointKey, prodEntrypointPath);

if (filepath === 'index.ts') {
setJSRExport(jsrExports, '.', prodEntrypointPath);
setJSRExport(jsrExports, './mod.ts', prodEntrypointPath);
}
}

const jsrConfig: JSRConfig = {
name: '@graphql/graphql-js',
version,
exports: jsrExports,
};

const prettified = await prettify(jsrConfigPath, JSON.stringify(jsrConfig));
writeGeneratedFile(jsrConfigPath, prettified);
}

function setJSRExport(
jsrExports: { [entrypoint: string]: string },
entrypoint: string,
targetPath: string,
): void {
const existingPath = jsrExports[entrypoint];
assert(
existingPath === undefined || existingPath === targetPath,
`JSR export "${entrypoint}" cannot target both "${existingPath}" and "${targetPath}".`,
);
jsrExports[entrypoint] = targetPath;
}
Loading
Loading