Update dependency @theguild/prettier-config to v1 (#676)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
This commit is contained in:
renovate[bot] 2022-11-24 10:00:41 +00:00 committed by GitHub
parent 85aa5ae783
commit 1afe0ec73a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
420 changed files with 5562 additions and 2863 deletions

View file

@ -1,8 +1,9 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool
that works with multi-package repos, or single-package repos to help you version and publish your
code. You can find the full documentation for it
[in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View file

@ -26,7 +26,10 @@ module.exports = {
rules: {
'no-process-env': 'error',
'no-restricted-globals': ['error', 'stop'],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', ignoreRestSiblings: true }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', ignoreRestSiblings: true },
],
'no-empty': ['error', { allowEmptyCatch: true }],
'import/no-absolute-path': 'error',

View file

@ -45,7 +45,9 @@ jobs:
run: pnpm build:libraries
- name: Schema Publish
run: ./packages/libraries/cli/bin/dev schema:publish "packages/services/api/src/modules/*/module.graphql.ts" --force --github
run:
./packages/libraries/cli/bin/dev schema:publish
"packages/services/api/src/modules/*/module.graphql.ts" --force --github
- name: Prepare NPM Credentials
run: |
@ -65,7 +67,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract published version
if: steps.changesets.outputs.published && contains(steps.changesets.outputs.publishedPackages, '"@graphql-hive/cli"')
if:
steps.changesets.outputs.published && contains(steps.changesets.outputs.publishedPackages,
'"@graphql-hive/cli"')
id: cli
run: |
echo '${{steps.changesets.outputs.publishedPackages}}' > cli-ver.json
@ -88,7 +92,9 @@ jobs:
working-directory: packages/libraries/cli
env:
VERSION: ${{ steps.cli.outputs.version }}
run: pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:7} --version $VERSION || pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:8} --version $VERSION
run:
pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:7} --version $VERSION || pnpm oclif
promote --no-xz --sha ${GITHUB_SHA:0:8} --version $VERSION
publish_rust:
name: Publish Rust
@ -316,7 +322,9 @@ jobs:
run: strip target/x86_64-unknown-linux-gnu/release/router
- name: Compress
run: ./target/x86_64-unknown-linux-gnu/release/compress ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz
run:
./target/x86_64-unknown-linux-gnu/release/compress
./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v3

View file

@ -17,10 +17,7 @@ jobs:
POSTGRES_USER: postgres
POSTGRES_DB: registry
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
outputs:
hive_token_present: ${{ steps.secrets_present.outputs.hive_token }}
@ -128,7 +125,10 @@ jobs:
id: pr-label-check
- name: Schema Check
run: ./packages/libraries/cli/bin/dev schema:check "packages/services/api/src/modules/*/module.graphql.ts" ${{ steps.pr-label-check.outputs.SAFE_FLAG }} --github
run:
./packages/libraries/cli/bin/dev schema:check
"packages/services/api/src/modules/*/module.graphql.ts" ${{
steps.pr-label-check.outputs.SAFE_FLAG }} --github
test:
name: Tests
@ -283,7 +283,9 @@ jobs:
run: strip target/x86_64-unknown-linux-gnu/release/router
- name: Compress
run: ./target/x86_64-unknown-linux-gnu/release/compress ./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz
run:
./target/x86_64-unknown-linux-gnu/release/compress
./target/x86_64-unknown-linux-gnu/release/router ./router.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v3

View file

@ -2,30 +2,43 @@
## 1. Purpose
A primary goal of GraphQL Hive is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
A primary goal of GraphQL Hive is to be inclusive to the largest number of contributors, with the
most varied and diverse backgrounds possible. As such, we are committed to providing a friendly,
safe and welcoming environment for all, regardless of gender, sexual orientation, ability,
ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
This code of conduct outlines our expectations for all those who participate in our community, as
well as the consequences for unacceptable behavior.
We invite all those who participate in GraphQL Hive to help us create safe and positive experiences for everyone.
We invite all those who participate in GraphQL Hive to help us create safe and positive experiences
for everyone.
## 2. Open [Source/Culture/Tech] Citizenship
A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by
encouraging participants to recognize and strengthen the relationships between our actions and their
effects on our community.
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
Communities mirror the societies in which they exist and positive action is essential to counteract
the many forms of inequality and abuses of power that exist in society.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and
encourages all participants to contribute to the fullest extent, we want to know.
## 3. Expected Behavior
The following behaviors are expected and requested of all community members:
- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
- Participate in an authentic and active way. In doing so, you contribute to the health and
longevity of this community.
- Exercise consideration and respect in your speech and actions.
- Attempt collaboration before conflict.
- Refrain from demeaning, discriminatory, or harassing behavior and speech.
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you
notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if
they seem inconsequential.
- Remember that community event venues may be shared with members of the public; please be
respectful to all patrons of these locations.
## 4. Unacceptable Behavior
@ -35,41 +48,62 @@ The following behaviors are considered harassment and are unacceptable within ou
- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
- Posting or displaying sexually explicit or violent material.
- Posting or threatening to post other people's personally identifying information ("doxing").
- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
- Personal insults, particularly those related to gender, sexual orientation, race, religion, or
disability.
- Inappropriate photography or recording.
- Inappropriate physical contact. You should have someone's consent before touching them.
- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching,
groping, and unwelcomed sexual advances.
- Deliberate intimidation, stalking or following (online or in person).
- Advocating for, or encouraging, any of the above behavior.
- Sustained disruption of community events, including talks and presentations.
## 5. Weapons Policy
No weapons will be allowed at GraphQL Hive events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter.
No weapons will be allowed at GraphQL Hive events, community spaces, or in other spaces covered by
the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives
(including fireworks), and large knives such as those used for hunting or display, as well as any
other item used for the purpose of causing injury or harm to others. Anyone seen in possession of
one of these items will be asked to leave immediately, and will only be allowed to return without
the weapon. Community members are further expected to comply with all state and local laws on this
matter.
## 6. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
Unacceptable behavior from any community member, including sponsors and those with decision-making
authority, will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
If a community member engages in unacceptable behavior, the community organizers may take any action
they deem appropriate, up to and including a temporary ban or permanent expulsion from the community
without warning (and without refund in the case of a paid event).
## 7. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. contact@the-guild.dev.
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a
community organizer as soon as possible. contact@the-guild.dev.
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
Additionally, community organizers are available to help community members engage with local law
enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context
of in-person events, organizers will also provide escorts as desired by the person experiencing
distress.
## 8. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. [Policy](https://graphql-hive.com/privacy-policy.pdf)
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should
notify with a concise description of your grievance. Your grievance will be handled in accordance
with our existing governing policies. [Policy](https://graphql-hive.com/privacy-policy.pdf)
## 9. Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business.
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests)
to abide by this Code of Conduct in all community venues--online and in-person--as well as in all
one-on-one communications pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
This code of conduct and its related procedures also applies to unacceptable behavior occurring
outside the scope of community activities when such behavior has the potential to adversely affect
the safety and well-being of community members.
## 10. Contact info
@ -77,9 +111,13 @@ contact@the-guild.dev
## 11. License and attribution
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org)
under a
[Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/)
and the
[Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
_Revision 2.3. Posted 6 March 2017._
@ -87,4 +125,5 @@ _Revision 2.2. Posted 4 February 2016._
_Revision 2.1. Posted 23 June 2014._
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10
January 2013. Posted 17 March 2013._

View file

@ -1,8 +1,10 @@
# GraphQL Hive
GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages, from standalone APIs to composed schemas (Federation, Stitching).
GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages,
from standalone APIs to composed schemas (Federation, Stitching).
- Visit [graphql-hive.com](https://graphql-hive.com) ([status page](https://status.graphql-hive.com/))
- Visit [graphql-hive.com](https://graphql-hive.com)
([status page](https://status.graphql-hive.com/))
- [Read the announcement blog post](https://www.the-guild.dev/blog/announcing-graphql-hive-public)
- [Read the docs](https://docs.graphql-hive.com)
@ -10,10 +12,12 @@ GraphQL Hive provides all the tools the get visibility of your GraphQL architect
GraphQL Hive has been built with 3 main objectives in mind:
- **Help GraphQL developers to get to know their GraphQL APIs** a little more with our Schema Registry, Performance Monitoring, Alerts, and Integrations.
- **Help GraphQL developers to get to know their GraphQL APIs** a little more with our Schema
Registry, Performance Monitoring, Alerts, and Integrations.
- **Support all kinds of GraphQL APIs**, from Federation, and Stitching, to standalone APIs.
- **Open Source at the heart**: 100% open-source and build in public with the community.
- **A plug and play SaaS solution**: to give access to Hive to most people with a generous free "Hobby plan"
- **A plug and play SaaS solution**: to give access to Hive to most people with a generous free
"Hobby plan"
## Features Overview
@ -21,18 +25,22 @@ GraphQL Hive has been built with 3 main objectives in mind:
GraphQL Hive offers 3 useful features to manage your GraphQL API:
- **Prevent breaking changes** - GraphQL Hive will run a set of checks and notify your team via Slack, GitHub, or within the application.
- **Prevent breaking changes** - GraphQL Hive will run a set of checks and notify your team via
Slack, GitHub, or within the application.
- **Data-driven** definition of a “breaking change” based on Operations Monitoring.
- **History of changes** - an access to the full history of changes, even on a complex composed schema (Federation, Stitching).
- **History of changes** - an access to the full history of changes, even on a complex composed
schema (Federation, Stitching).
- **High-availability and multi-zone CDN** service based on Cloudflare to access Schema Registry
### Monitoring
Once a Schema is deployed, **it is important to be aware of how it is used and what is the experience of its final users**.
Once a Schema is deployed, **it is important to be aware of how it is used and what is the
experience of its final users**.
## Self-hosted
GraphQL Hive is completely open-source under the MIT license, meaning that you are free to host on your own infrastructure.
GraphQL Hive is completely open-source under the MIT license, meaning that you are free to host on
your own infrastructure.
GraphQL Hive helps you get a global overview of the usage of your GraphQL API with:
@ -43,13 +51,16 @@ GraphQL Hive helps you get a global overview of the usage of your GraphQL API wi
### Integrations
GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running as smoothly as possible!
GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running
as smoothly as possible!
GraphQL Hive can notify your team when schema changes occur, either via Slack or a custom webhook.
Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub, BitBucket, Azure, and others). The same applies for schema publishing and operations checks.
Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub,
BitBucket, Azure, and others). The same applies for schema publishing and operations checks.
If you are using GitHub, you can directly benefit from the **GraphQL Hive app that will automatically add status checks to your PRs**!
If you are using GitHub, you can directly benefit from the **GraphQL Hive app that will
automatically add status checks to your PRs**!
### Join us in building the future of GraphQL Hive

View file

@ -21,9 +21,12 @@ generates:
DateTime: string
SafeInt: number
mappers:
SchemaChangeConnection: ../shared/mappers#SchemaChangeConnection as SchemaChangeConnectionMapper
SchemaErrorConnection: ../shared/mappers#SchemaErrorConnection as SchemaErrorConnectionMapper
OrganizationConnection: ../shared/mappers#OrganizationConnection as OrganizationConnectionMapper
SchemaChangeConnection:
../shared/mappers#SchemaChangeConnection as SchemaChangeConnectionMapper
SchemaErrorConnection:
../shared/mappers#SchemaErrorConnection as SchemaErrorConnectionMapper
OrganizationConnection:
../shared/mappers#OrganizationConnection as OrganizationConnectionMapper
UserConnection: ../shared/mappers#UserConnection as UserConnectionMapper
ActivityConnection: ../shared/mappers#ActivityConnection as ActivityConnectionMapper
MemberConnection: ../shared/mappers#MemberConnection as MemberConnectionMapper
@ -31,16 +34,20 @@ generates:
TargetConnection: ../shared/mappers#TargetConnection as TargetConnectionMapper
SchemaConnection: ../shared/mappers#SchemaConnection as SchemaConnectionMapper
TokenConnection: ../shared/mappers#TokenConnection as TokenConnectionMapper
OperationStatsConnection: ../shared/mappers#OperationStatsConnection as OperationStatsConnectionMapper
ClientStatsConnection: ../shared/mappers#ClientStatsConnection as ClientStatsConnectionMapper
OperationStatsConnection:
../shared/mappers#OperationStatsConnection as OperationStatsConnectionMapper
ClientStatsConnection:
../shared/mappers#ClientStatsConnection as ClientStatsConnectionMapper
OperationsStats: ../shared/mappers#OperationsStats as OperationsStatsMapper
DurationStats: ../shared/mappers#DurationStats as DurationStatsMapper
SchemaComparePayload: ../shared/mappers#SchemaComparePayload as SchemaComparePayloadMapper
SchemaCompareResult: ../shared/mappers#SchemaCompareResult as SchemaCompareResultMapper
SchemaVersionConnection: ../shared/mappers#SchemaVersionConnection as SchemaVersionConnectionMapper
SchemaVersionConnection:
../shared/mappers#SchemaVersionConnection as SchemaVersionConnectionMapper
SchemaVersion: ../shared/mappers#SchemaVersion as SchemaVersionMapper
Schema: ../shared/mappers#Schema as SchemaMapper
PersistedOperationConnection: ../shared/mappers#PersistedOperationConnection as PersistedOperationMapper
PersistedOperationConnection:
../shared/mappers#PersistedOperationConnection as PersistedOperationMapper
Organization: ../shared/entities#Organization as OrganizationMapper
Project: ../shared/entities#Project as ProjectMapper
Target: ../shared/entities#Target as TargetMapper
@ -55,13 +62,15 @@ generates:
AdminQuery: '{}'
AdminStats: '{ daysLimit?: number | null }'
AdminGeneralStats: '{ daysLimit?: number | null }'
AdminOrganizationStats: ../shared/entities#AdminOrganizationStats as AdminOrganizationStatsMapper
AdminOrganizationStats:
../shared/entities#AdminOrganizationStats as AdminOrganizationStatsMapper
UsageEstimation: '../shared/mappers#TargetsEstimationFilter'
UsageEstimationScope: '../shared/mappers#TargetsEstimationDateFilter'
BillingPaymentMethod: 'StripeTypes.PaymentMethod.Card'
BillingDetails: 'StripeTypes.PaymentMethod.BillingDetails'
BillingInvoice: 'StripeTypes.Invoice'
OrganizationGetStarted: ../shared/entities#OrganizationGetStarted as OrganizationGetStartedMapper
OrganizationGetStarted:
../shared/entities#OrganizationGetStarted as OrganizationGetStartedMapper
SchemaExplorer: ../shared/mappers#SchemaExplorerMapper
GraphQLObjectType: ../shared/mappers#GraphQLObjectTypeMapper
GraphQLInterfaceType: ../shared/mappers#GraphQLInterfaceTypeMapper
@ -74,7 +83,8 @@ generates:
GraphQLField: ../shared/mappers#GraphQLFieldMapper
GraphQLInputField: ../shared/mappers#GraphQLInputFieldMapper
GraphQLArgument: ../shared/mappers#GraphQLArgumentMapper
OrganizationInvitation: ../shared/entities#OrganizationInvitation as OrganizationInvitationMapper
OrganizationInvitation:
../shared/entities#OrganizationInvitation as OrganizationInvitationMapper
OIDCIntegration: '../shared/entities#OIDCIntegration as OIDCIntegrationMapper'
User: '../shared/entities#User as UserMapper'
plugins:

View file

@ -182,8 +182,14 @@ const schemaApi = deploySchema({
broker: cfBroker,
});
const supertokensApiKey = new random.RandomPassword('supertokens-api-key', { length: 31, special: false });
const auth0LegacyMigrationKey = new random.RandomPassword('auth0-legacy-migration-key', { length: 69, special: false });
const supertokensApiKey = new random.RandomPassword('supertokens-api-key', {
length: 31,
special: false,
});
const auth0LegacyMigrationKey = new random.RandomPassword('auth0-legacy-migration-key', {
length: 69,
special: false,
});
const oauthConfig = new pulumi.Config('oauth');

View file

@ -4,10 +4,6 @@
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/mime-types": "2.1.1",
"@types/node": "18.11.5"
},
"dependencies": {
"@manypkg/get-packages": "1.1.3",
"@pulumi/azure": "5.23.0",
@ -17,5 +13,9 @@
"@pulumi/pulumi": "3.44.1",
"@pulumi/random": "4.8.2",
"pg-connection-string": "2.5.0"
},
"devDependencies": {
"@types/mime-types": "2.1.1",
"@types/node": "18.11.5"
}
}

View file

@ -206,6 +206,6 @@ export function deployApp({
],
port: 3000,
},
[graphql.service, graphql.deployment, dbMigrations]
[graphql.service, graphql.deployment, dbMigrations],
).deploy();
}

View file

@ -29,7 +29,9 @@ export function deployStripeBilling({
dbMigrations: DbMigrations;
}) {
const rawConnectionString = apiConfig.requireSecret('postgresConnectionString');
const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString));
const connectionString = rawConnectionString.apply(rawConnectionString =>
parse(rawConnectionString),
);
return new RemoteArtifactAsServiceDeployment(
'stripe-billing',
@ -56,6 +58,6 @@ export function deployStripeBilling({
packageInfo: packageHelper.npmPack('@hive/stripe-billing'),
port: 4000,
},
[dbMigrations, usageEstimator.service, usageEstimator.deployment]
[dbMigrations, usageEstimator.service, usageEstimator.deployment],
).deploy();
}

View file

@ -7,11 +7,16 @@ function toExpressionList(items: string[]): string {
return items.map(v => `"${v}"`).join(' ');
}
export function deployCloudFlareSecurityTransform(options: { envName: string; ignoredPaths: string[] }) {
export function deployCloudFlareSecurityTransform(options: {
envName: string;
ignoredPaths: string[];
}) {
// We deploy it only once, because CloudFlare is not super friendly for multiple deployments of "http_response_headers_transform" rules
// The single rule, deployed to prod, covers all other envs, and infers the hostname dynamically.
if (options.envName !== 'prod') {
console.warn(`Skipped deploy security headers (see "cloudflare-security.ts") for env ${options.envName}`);
console.warn(
`Skipped deploy security headers (see "cloudflare-security.ts") for env ${options.envName}`,
);
return;
}

View file

@ -24,7 +24,9 @@ export function deployDbMigrations({
kafka: Kafka;
}) {
const rawConnectionString = apiConfig.requireSecret('postgresConnectionString');
const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString));
const connectionString = rawConnectionString.apply(rawConnectionString =>
parse(rawConnectionString),
);
const { job } = new RemoteArtifactAsServiceDeployment(
'db-migrations',
@ -50,7 +52,7 @@ export function deployDbMigrations({
packageInfo: packageHelper.npmPack('@hive/storage'),
},
[clickhouse.deployment, clickhouse.service],
clickhouse.service
clickhouse.service,
).deployAsJob();
return job;

View file

@ -55,7 +55,7 @@ export function deployEmails({
packageInfo: packageHelper.npmPack('@hive/emails'),
replicas: 1,
},
[redis.deployment, redis.service]
[redis.deployment, redis.service],
).deploy();
return { deployment, service, localEndpoint: serviceLocalEndpoint(service) };

View file

@ -72,7 +72,9 @@ export function deployGraphQL({
};
}) {
const rawConnectionString = apiConfig.requireSecret('postgresConnectionString');
const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString));
const connectionString = rawConnectionString.apply(rawConnectionString =>
parse(rawConnectionString),
);
return new RemoteArtifactAsServiceDeployment(
'graphql-api',
@ -148,6 +150,6 @@ export function deployGraphQL({
clickhouse.service,
rateLimit.deployment,
rateLimit.service,
]
],
).deploy();
}

View file

@ -10,7 +10,7 @@ export function deployCloudflarePolice({ envName, rootDns }: { envName: string;
cfCustomConfig.require('zoneId'),
cloudflareProviderConfig.require('accountId'),
cfCustomConfig.requireSecret('policeApiToken'),
rootDns
rootDns,
);
return police.deploy();

View file

@ -50,7 +50,7 @@ export function deployProxy({
path: '/',
service: docs.service,
},
]
],
)
.registerService({ record: appHostname }, [
{

View file

@ -32,7 +32,9 @@ export function deployRateLimit({
emails: Emails;
}) {
const rawConnectionString = apiConfig.requireSecret('postgresConnectionString');
const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString));
const connectionString = rawConnectionString.apply(rawConnectionString =>
parse(rawConnectionString),
);
return new RemoteArtifactAsServiceDeployment(
'rate-limiter',
@ -60,6 +62,6 @@ export function deployRateLimit({
packageInfo: packageHelper.npmPack('@hive/rate-limit'),
port: 4000,
},
[dbMigrations, usageEstimator.service, usageEstimator.deployment]
[dbMigrations, usageEstimator.service, usageEstimator.deployment],
).deploy();
}

View file

@ -48,6 +48,6 @@ export function deploySchema({
replicas: 2,
pdb: true,
},
[redis.deployment, redis.service]
[redis.deployment, redis.service],
).deploy();
}

View file

@ -26,7 +26,9 @@ export function deployTokens({
heartbeat?: string;
}) {
const rawConnectionString = apiConfig.requireSecret('postgresConnectionString');
const connectionString = rawConnectionString.apply(rawConnectionString => parse(rawConnectionString));
const connectionString = rawConnectionString.apply(rawConnectionString =>
parse(rawConnectionString),
);
return new RemoteArtifactAsServiceDeployment(
'tokens-service',
@ -50,6 +52,6 @@ export function deployTokens({
HEARTBEAT_ENDPOINT: heartbeat ?? '',
},
},
[dbMigrations]
[dbMigrations],
).deploy();
}

View file

@ -46,6 +46,6 @@ export function deployUsageEstimation({
packageInfo: packageHelper.npmPack('@hive/usage-estimator'),
port: 4000,
},
[dbMigrations]
[dbMigrations],
).deploy();
}

View file

@ -87,6 +87,6 @@ export function deployUsageIngestor({
maxReplicas: maxReplicas,
},
},
[clickhouse.deployment, clickhouse.service, dbMigrations]
[clickhouse.deployment, clickhouse.service, dbMigrations],
).deploy();
}

View file

@ -73,6 +73,6 @@ export function deployUsage({
maxReplicas: maxReplicas,
},
},
[dbMigrations, tokens.deployment, tokens.service, rateLimit.deployment, rateLimit.service]
[dbMigrations, tokens.deployment, tokens.service, rateLimit.deployment, rateLimit.service],
).deploy();
}

View file

@ -50,6 +50,6 @@ export function deployWebhooks({
packageInfo: packageHelper.npmPack('@hive/webhooks'),
replicas: 1,
},
[redis.deployment, redis.service]
[redis.deployment, redis.service],
).deploy();
}

View file

@ -80,7 +80,7 @@ export class BotKube {
},
{
dependsOn: [ns],
}
},
);
}
}

View file

@ -37,7 +37,7 @@ export class CertManager {
},
{
dependsOn: [certManager],
}
},
);
return {

View file

@ -8,7 +8,7 @@ export class Clickhouse {
protected options: {
env?: kx.types.Container['env'];
sentryDsn: string;
}
},
) {}
deploy() {
@ -79,7 +79,7 @@ export class Clickhouse {
},
{
annotations: metadata.annotations,
}
},
),
});
const service = deployment.createService({});

View file

@ -12,7 +12,7 @@ export class CloudflareCDN {
authPrivateKey: pulumi.Output<string>;
sentryDsn: string;
release: string;
}
},
) {}
deploy() {
@ -21,7 +21,10 @@ export class CloudflareCDN {
});
const script = new cf.WorkerScript('hive-ha-worker', {
content: readFileSync(resolve(__dirname, '../../packages/services/cdn-worker/dist/worker.js'), 'utf-8'),
content: readFileSync(
resolve(__dirname, '../../packages/services/cdn-worker/dist/worker.js'),
'utf-8',
),
name: `hive-storage-cdn-${this.config.envName}`,
kvNamespaceBindings: [
{
@ -78,12 +81,15 @@ export class CloudflareBroker {
secretSignature: pulumi.Output<string>;
sentryDsn: string;
release: string;
}
},
) {}
deploy() {
const script = new cf.WorkerScript('hive-broker-worker', {
content: readFileSync(resolve(__dirname, '../../packages/services/broker-worker/dist/worker.js'), 'utf-8'),
content: readFileSync(
resolve(__dirname, '../../packages/services/broker-worker/dist/worker.js'),
'utf-8',
),
name: `hive-broker-${this.config.envName}`,
secretTextBindings: [
{

View file

@ -5,7 +5,9 @@ export function isProduction(deploymentEnv: DeploymentEnvironment | string): boo
}
export function isStaging(deploymentEnv: DeploymentEnvironment | string): boolean {
return isDeploymentEnvironment(deploymentEnv) ? deploymentEnv.ENVIRONMENT === 'staging' : deploymentEnv === 'staging';
return isDeploymentEnvironment(deploymentEnv)
? deploymentEnv.ENVIRONMENT === 'staging'
: deploymentEnv === 'staging';
}
export function isDeploymentEnvironment(value: any): value is DeploymentEnvironment {

View file

@ -6,7 +6,9 @@ export function serviceLocalEndpoint(service: k8s.types.input.core.v1.Service) {
const defaultPort = (spec?.ports || [])[0];
const portText = defaultPort ? `:${defaultPort.port}` : '';
return `http://${metadata?.name}.${metadata?.namespace || 'default'}.svc.cluster.local${portText}`;
return `http://${metadata?.name}.${
metadata?.namespace || 'default'
}.svc.cluster.local${portText}`;
});
}

View file

@ -210,7 +210,10 @@ export class Observability {
regex: '(.+)',
},
{
source_labels: ['__address__', '__meta_kubernetes_pod_annotation_prometheus_io_port'],
source_labels: [
'__address__',
'__meta_kubernetes_pod_annotation_prometheus_io_port',
],
action: 'replace',
regex: '([^:]+)(?::d+)?;(d+)',
replacement: '$1:$2',
@ -358,7 +361,7 @@ export class Observability {
},
{
dependsOn: [ns],
}
},
);
}
}

View file

@ -14,7 +14,7 @@ export function normalizeEnv(env: kx.types.Container['env']): any[] {
export class PodBuilder extends kx.PodBuilder {
public asExtendedDeploymentSpec(
args?: kx.types.PodBuilderDeploymentSpec,
metadata?: k8s.types.input.meta.v1.ObjectMeta
metadata?: k8s.types.input.meta.v1.ObjectMeta,
): pulumi.Output<k8s.types.input.apps.v1.DeploymentSpec> {
const podName = this.podSpec.containers.apply((containers: any) => {
return pulumi.output(containers[0].name);

View file

@ -9,7 +9,7 @@ export class HivePolice {
private zoneId: string,
private accountId: string,
private cfToken: pulumi.Output<string>,
private rootDns: string
private rootDns: string,
) {}
deploy() {
@ -18,7 +18,10 @@ export class HivePolice {
});
const script = new cf.WorkerScript('hive-police-worker', {
content: readFileSync(resolve(__dirname, '../../packages/services/police-worker/dist/worker.js'), 'utf-8'),
content: readFileSync(
resolve(__dirname, '../../packages/services/police-worker/dist/worker.js'),
'utf-8',
),
name: `hive-police-${this.envName}`,
kvNamespaceBindings: [
{

View file

@ -10,7 +10,7 @@ export class Redis {
protected options: {
env?: kx.types.Container['env'];
password: string;
}
},
) {}
deploy({ limits }: { limits: k8s.types.input.core.v1.ResourceRequirements['limits'] }) {
@ -105,7 +105,7 @@ fi
},
{
annotations: metadata.annotations,
}
},
),
});

View file

@ -38,7 +38,7 @@ export class RemoteArtifactAsServiceDeployment {
};
},
protected dependencies?: Array<pulumi.Resource | undefined | null>,
protected parent?: pulumi.Resource | null
protected parent?: pulumi.Resource | null,
) {}
deployAsJob() {
@ -50,7 +50,7 @@ export class RemoteArtifactAsServiceDeployment {
{
spec: pb.asJobSpec(),
},
{ dependsOn: this.dependencies?.filter(isDefined) }
{ dependsOn: this.dependencies?.filter(isDefined) },
);
return { job };
@ -214,13 +214,13 @@ export class RemoteArtifactAsServiceDeployment {
},
{
annotations: metadata.annotations,
}
},
),
},
{
dependsOn: this.dependencies?.filter(isDefined),
parent: this.parent ?? undefined,
}
},
);
if (this.options.pdb) {
@ -265,7 +265,7 @@ export class RemoteArtifactAsServiceDeployment {
},
{
dependsOn: [deployment, service],
}
},
);
}

View file

@ -18,7 +18,7 @@ export class Proxy {
virtualHost?: Output<string>;
httpsUpstream?: boolean;
withWwwDomain?: boolean;
}[]
}[],
) {
const cert = new k8s.apiextensions.CustomResource(`cert-${dns.record}`, {
apiVersion: 'cert-manager.io/v1',
@ -104,7 +104,7 @@ export class Proxy {
},
{
dependsOn: [cert, this.lbService!],
}
},
);
return this;
@ -183,7 +183,10 @@ export class Proxy {
this.lbService = proxyController.getResource('v1/Service', 'contour/contour-proxy-envoy');
const contourDeployment = proxyController.getResource('apps/v1/Deployment', 'contour/contour-proxy-contour');
const contourDeployment = proxyController.getResource(
'apps/v1/Deployment',
'contour/contour-proxy-contour',
);
new k8s.policy.v1.PodDisruptionBudget('contour-pdb', {
spec: {
minAvailable: 1,
@ -191,7 +194,10 @@ export class Proxy {
},
});
const envoyDaemonset = proxyController.getResource('apps/v1/ReplicaSet', 'contour/contour-proxy-envoy');
const envoyDaemonset = proxyController.getResource(
'apps/v1/ReplicaSet',
'contour/contour-proxy-envoy',
);
new k8s.policy.v1.PodDisruptionBudget('envoy-pdb', {
spec: {
minAvailable: 1,
@ -219,7 +225,7 @@ export class Proxy {
},
{
dependsOn: [this.lbService],
}
},
);
return this;

View file

@ -65,7 +65,18 @@ services:
soft: 20000
hard: 40000
healthcheck:
test: ['CMD', 'cub', 'kafka-ready', '1', '5', '-b', '127.0.0.1:9092', '-c', '/etc/kafka/kafka.properties']
test:
[
'CMD',
'cub',
'kafka-ready',
'1',
'5',
'-b',
'127.0.0.1:9092',
'-c',
'/etc/kafka/kafka.properties',
]
interval: 15s
timeout: 10s
retries: 6

View file

@ -4,4 +4,5 @@
![Design Diagram](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/kamilkisiela/graphql-hive/main/docs/architecture.puml)
Note: The relationships in this diagram is based on [this docker-compose](https://github.com/kamilkisiela/graphql-hive/blob/main/docker-compose.community.yml)
Note: The relationships in this diagram is based on
[this docker-compose](https://github.com/kamilkisiela/graphql-hive/blob/main/docker-compose.community.yml)

View file

@ -1,15 +1,23 @@
## Deployment
Deployment is based on NPM packages. That means we are bundling (as much as possible) each service or package, and publish it to the private GitHub Packages artifactory.
Deployment is based on NPM packages. That means we are bundling (as much as possible) each service
or package, and publish it to the private GitHub Packages artifactory.
Doing that allows us to have a simple, super fast deployments, because we don't need to deal with Docker images (which are heavy).
Doing that allows us to have a simple, super fast deployments, because we don't need to deal with
Docker images (which are heavy).
We create an executable package (with `bin` entrypoint) and then use `npx PACKAGE_NAME@PACKAGE_VERSION` as command for a base Docker image of NodeJS. So instead of building a Docker image for each change, we build NPM package, and the Docker image we are using in prod is the same.
We create an executable package (with `bin` entrypoint) and then use
`npx PACKAGE_NAME@PACKAGE_VERSION` as command for a base Docker image of NodeJS. So instead of
building a Docker image for each change, we build NPM package, and the Docker image we are using in
prod is the same.
Think of it as Lambda (bundled JS, runtime is predefined) without all the crap (weird cache, weird pricing, cold start and so on).
Think of it as Lambda (bundled JS, runtime is predefined) without all the crap (weird cache, weird
pricing, cold start and so on).
### How to deploy?
We are using Pulumi (infrastructure as code) to describe and run our deployment. It's managed as GitHub Actions that runs on every bump release by Changesets.
We are using Pulumi (infrastructure as code) to describe and run our deployment. It's managed as
GitHub Actions that runs on every bump release by Changesets.
So changes are aggregated in a Changesets PR, and when merge, it updated the deployment manifest `package.json`, leading to a deployment of only the updated packages to production.
So changes are aggregated in a Changesets PR, and when merge, it updated the deployment manifest
`package.json`, leading to a deployment of only the updated packages to production.

View file

@ -16,14 +16,18 @@ Developing Hive locally requires you to have the following software installed lo
- In the root of the repo, run `nvm use` to use the same version of node as mentioned
- Run `pnpm i` at the root to install all the dependencies and run the hooks
- Run `pnpm setup` to create and apply migrations on the PostgreSQL database
- Run `pnpm generate` to generate the typings from the graphql files (use `pnpm graphql:generate` if you only need to run GraphQL Codegen)
- Run `pnpm generate` to generate the typings from the graphql files (use `pnpm graphql:generate` if
you only need to run GraphQL Codegen)
- Run `pnpm build` to build all services
- Click on `Start Hive` in the bottom bar of VSCode
- If you are not added to the list of guest users, request access from The Guild maintainers
- Alternatively, [configure hive to use your own Auth0 Application](#setting-up-auth0-app-for-developing)
- Alternatively,
[configure hive to use your own Auth0 Application](#setting-up-auth0-app-for-developing)
- Open the UI (`http://localhost:3000` by default) and Sign in with any of the identity provider
- Once this is done, you should be able to login and use the project
- Once you generate the token against your organization/personal account in hive, the same can be added locally to `hive.json` within `packages/libraries/cli` which can be used to interact via the hive cli with the registry
- Once you generate the token against your organization/personal account in hive, the same can be
added locally to `hive.json` within `packages/libraries/cli` which can be used to interact via the
hive cli with the registry
## Development Seed
@ -33,11 +37,15 @@ We have a script to feed your local instance of Hive.
2. Make sure `usage` and `usage-ingestor` are running as well (with `pnpm dev`)
3. Open Hive app, create a project and a target, then create a token
4. Run the seed script: `TOKEN="MY_TOKEN_HERE" pnpm seed`
5. This should report a dummy schema and some dummy usage data to your local instance of Hive, allowing you to test features e2e
5. This should report a dummy schema and some dummy usage data to your local instance of Hive,
allowing you to test features e2e
> Note: You can set `STAGING=1` in order to target staging env and seed a target there.
> To send more operations and test heavy load on Hive instance, you can also set `OPERATIONS` (amount of operations in each interval round, default is `1`) and `INTERVAL` (frequency of sending operations, default: `1000`ms). For example, using `INTERVAL=1000 OPERATIONS=1000` will send 1000 requests per second.
> To send more operations and test heavy load on Hive instance, you can also set `OPERATIONS`
> (amount of operations in each interval round, default is `1`) and `INTERVAL` (frequency of sending
> operations, default: `1000`ms). For example, using `INTERVAL=1000 OPERATIONS=1000` will send 1000
> requests per second.
## Publish your first schema (manually)
@ -45,14 +53,18 @@ We have a script to feed your local instance of Hive.
2. Create a project and a target
3. Create a token from that target
4. Go to `packages/libraries/cli` and run `pnpm build`
5. Inside `packages/libraries/cli`, run: `pnpm start schema:publish --token "YOUR_TOKEN_HERE" --registry "http://localhost:4000/graphql" examples/single.graphql`
5. Inside `packages/libraries/cli`, run:
`pnpm start schema:publish --token "YOUR_TOKEN_HERE" --registry "http://localhost:4000/graphql" examples/single.graphql`
### Setting up Slack App for developing
1. [Download](https://loophole.cloud/download) Loophole CLI (same as ngrok but supports non-random urls)
1. [Download](https://loophole.cloud/download) Loophole CLI (same as ngrok but supports non-random
urls)
2. Log in to Loophole `$ loophole account login`
3. Start the proxy by running `$ loophole http 3000 --hostname hive-<your-name>` (@kamilkisiela I use `hive-kamil`). It creates `https://hive-<your-name>.loophole.site` endpoint.
4. Message @kamilkisiela and send him the url (He will update the list of accepted redirect urls in both Auth0 and Slack App).
3. Start the proxy by running `$ loophole http 3000 --hostname hive-<your-name>` (@kamilkisiela I
use `hive-kamil`). It creates `https://hive-<your-name>.loophole.site` endpoint.
4. Message @kamilkisiela and send him the url (He will update the list of accepted redirect urls in
both Auth0 and Slack App).
5. Update `APP_BASE_URL` and `AUTH0_BASE_URL` in [`packages/web/app/.env`](./packages/web/app/.env)
6. Run `packages/web/app` and open `https://hive-<your-name>.loophole.site`.
@ -61,25 +73,28 @@ We have a script to feed your local instance of Hive.
### Setting up GitHub App for developing
1. Follow the steps above for Slack App
2. Update `Setup URL` in [GraphQL Hive Development](https://github.com/organizations/the-guild-org/settings/apps/graphql-hive-development) app and set it to `https://hive-<your-name>.loophole.site/api/github/setup-callback`
2. Update `Setup URL` in
[GraphQL Hive Development](https://github.com/organizations/the-guild-org/settings/apps/graphql-hive-development)
app and set it to `https://hive-<your-name>.loophole.site/api/github/setup-callback`
### Run Hive
1. Click on Start Hive in the bottom bar of VSCode
2. Open the UI (`http://localhost:3000` by default) and register any email and
password
3. Sending e-mails is mocked out during local development, so in order to
verify the account find the verification link by visiting the email server's
`/_history` endpoint - `http://localhost:6260/_history` by default.
2. Open the UI (`http://localhost:3000` by default) and register any email and password
3. Sending e-mails is mocked out during local development, so in order to verify the account find
the verification link by visiting the email server's `/_history` endpoint -
`http://localhost:6260/_history` by default.
- Searching for `token` should help you find the link.
### Legacy Auth0 Integration
**Note:** If you are not working at The Guild, you can safely ignore this section.
Since we migrated from Auth0 to SuperTokens there is a compatibility layer for importing/migrating accounts from Auth0 to SuperTokens.
Since we migrated from Auth0 to SuperTokens there is a compatibility layer for importing/migrating
accounts from Auth0 to SuperTokens.
By default you don't need to set this up and can just use SuperTokens locally. However, if you need to test some stuff or fix the Auth0 -> SuperTokens migration flow you have to set up some stuff.
By default you don't need to set this up and can just use SuperTokens locally. However, if you need
to test some stuff or fix the Auth0 -> SuperTokens migration flow you have to set up some stuff.
1. Create your own Auth0 application
1. If you haven't already, create an account on [manage.auth0.com](https://manage.auth0.com)
@ -108,9 +123,11 @@ By default you don't need to set this up and can just use SuperTokens locally. H
return callback(null, user, context);
}
```
2. Update the `.env` secrets used by your local hive instance that are found when viewing your new application on Auth0:
2. Update the `.env` secrets used by your local hive instance that are found when viewing your new
application on Auth0:
- `AUTH_LEGACY_AUTH0` (set this to `1` for enabling the migration.)
- `AUTH_LEGACY_AUTH0_CLIENT_ID` (e.g. `rGSrExtM9sfilpF8kbMULkMNYI2SgXro`)
- `AUTH_LEGACY_AUTH0_CLIENT_SECRET` (e.g. `gJjNQJsCaOC0nCKTgqWv2wvrh1XXXb-iqzVdn8pi2nSPq2TxxxJ9FIUYbNjheXxx`)
- `AUTH_LEGACY_AUTH0_CLIENT_SECRET` (e.g.
`gJjNQJsCaOC0nCKTgqWv2wvrh1XXXb-iqzVdn8pi2nSPq2TxxxJ9FIUYbNjheXxx`)
- `AUTH_LEGACY_AUTH0_ISSUER_BASE_URL`(e.g. `https://foo-bars.us.auth0.com`)
- `AUTH_LEGACY_AUTH0_AUDIENCE` (e.g. `https://foo-bars.us.auth0.com/api/v2/`)

View file

@ -15,7 +15,8 @@ We are using Dockest to test the following concerns:
To run integration tests locally, follow:
1. Make sure you have Docker installed. If you are having issues, try to run `docker system prune` to clean the Docker caches.
1. Make sure you have Docker installed. If you are having issues, try to run `docker system prune`
to clean the Docker caches.
2. Install all deps: `pnpm i`
3. Generate types: `pnpm graphql:generate`
4. Build and pack all services: `pnpm --filter integration-tests build-and-pack`

View file

@ -70,7 +70,18 @@ services:
soft: 20000
hard: 40000
healthcheck:
test: ['CMD', 'cub', 'kafka-ready', '1', '5', '-b', '127.0.0.1:9092', '-c', '/etc/kafka/kafka.properties']
test:
[
'CMD',
'cub',
'kafka-ready',
'1',
'5',
'-b',
'127.0.0.1:9092',
'-c',
'/etc/kafka/kafka.properties',
]
interval: 15s
timeout: 10s
retries: 6

View file

@ -16,5 +16,5 @@ beforeEach(() =>
host: redisAddress.replace(':6379', ''),
port: 6379,
password: 'test',
})
}),
);

View file

@ -1,35 +1,35 @@
{
"name": "integration-tests",
"version": "0.0.0",
"type": "module",
"private": true,
"version": "0.0.0",
"scripts": {
"build-and-pack": "(cd ../ && pnpm build:services && pnpm build:libraries && pnpm build:local) && node ./scripts/pack.mjs",
"build:local": "pnpm build-and-pack && (cd ../ && pnpm docker:build)",
"dockest": "tsup-node dockest.ts --format esm --target node16 && PWD=$PWD/.. node dist/dockest.js"
},
"dependencies": {
"@app/gql": "link:./testkit/gql",
"@graphql-hive/core": "0.2.3",
"@graphql-typed-document-node/core": "3.1.1",
"@n1ru4l/dockest": "2.1.0-rc.6",
"@trpc/client": "9.23.2",
"@whatwg-node/fetch": "0.4.7",
"auth0": "2.36.2",
"axios": "0.27.2",
"dotenv": "10.0.0",
"date-fns": "2.25.0",
"dependency-graph": "0.11.0",
"@n1ru4l/dockest": "2.1.0-rc.6",
"dotenv": "10.0.0",
"ioredis": "4.28.5",
"rxjs": "^6.5.4",
"slonik": "30.1.2",
"tsup": "6.5.0",
"yaml": "2.1.0",
"@whatwg-node/fetch": "0.4.7",
"zod": "3.15.1",
"@trpc/client": "9.23.2"
"zod": "3.15.1"
},
"devDependencies": {
"@hive/server": "workspace:*",
"@types/ioredis": "4.28.10",
"tslib": "2.4.1"
},
"scripts": {
"build-and-pack": "(cd ../ && pnpm build:services && pnpm build:libraries && pnpm build:local) && node ./scripts/pack.mjs",
"build:local": "pnpm build-and-pack && (cd ../ && pnpm docker:build)",
"dockest": "tsup-node dockest.ts --format esm --target node16 && PWD=$PWD/.. node dist/dockest.js"
}
}

View file

@ -25,7 +25,9 @@ async function main() {
fsExtra.mkdirSync(tarballDir, { recursive: true });
function isBackendPackage(manifestPath) {
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).buildOptions?.tags.includes('backend');
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')).buildOptions?.tags.includes(
'backend',
);
}
function listBackendPackages() {
@ -35,11 +37,15 @@ async function main() {
ignore: ['**/node_modules/**', '**/dist/**'],
});
return manifestPathCollection.filter(isBackendPackage).map(filepath => path.relative(cwd, path.dirname(filepath)));
return manifestPathCollection
.filter(isBackendPackage)
.map(filepath => path.relative(cwd, path.dirname(filepath)));
}
async function pack(location) {
const { version, name } = JSON.parse(await fsExtra.readFile(path.join(cwd, location, 'package.json'), 'utf-8'));
const { version, name } = JSON.parse(
await fsExtra.readFile(path.join(cwd, location, 'package.json'), 'utf-8'),
);
const stdout = await new Promise((resolve, reject) => {
exec(
`npm pack ${path.join(cwd, location, 'dist')}`,
@ -54,7 +60,7 @@ async function main() {
} else {
resolve(stdout);
}
}
},
);
});
@ -86,7 +92,7 @@ async function main() {
console.error('[pack] Maybe you forgot to build the packages first?');
process.exit(1);
}
})
}),
);
}

View file

@ -23,7 +23,7 @@ const SignUpSignInUserResponseModel = z.object({
const signUpUserViaEmail = async (
email: string,
password: string
password: string,
): Promise<z.TypeOf<typeof SignUpSignInUserResponseModel>> => {
const response = await fetch(`${ensureEnv('SUPERTOKENS_CONNECTION_URI')}/recipe/signup`, {
method: 'POST',
@ -63,7 +63,11 @@ const CreateSessionModel = z.object({
}),
});
const createSession = async (superTokensUserId: string, email: string, oidcIntegrationId: string | null) => {
const createSession = async (
superTokensUserId: string,
email: string,
oidcIntegrationId: string | null,
) => {
await internalApi.mutation('ensureUser', {
superTokensUserId,
email,
@ -111,18 +115,27 @@ export const userEmails: Record<UserID, string> = {
admin: 'admin@localhost.localhost',
};
const tokenResponsePromise: Record<UserID, Promise<z.TypeOf<typeof SignUpSignInUserResponseModel>> | null> = {
const tokenResponsePromise: Record<
UserID,
Promise<z.TypeOf<typeof SignUpSignInUserResponseModel>> | null
> = {
main: null,
extra: null,
admin: null,
};
export function authenticate(userId: UserID, oidcIntegrationId?: string): Promise<{ access_token: string }> {
export function authenticate(
userId: UserID,
oidcIntegrationId?: string,
): Promise<{ access_token: string }> {
if (!tokenResponsePromise[userId]) {
tokenResponsePromise[userId] = signUpUserViaEmail(userEmails[userId] ?? `${userId}@localhost.localhost`, password);
tokenResponsePromise[userId] = signUpUserViaEmail(
userEmails[userId] ?? `${userId}@localhost.localhost`,
password,
);
}
return tokenResponsePromise[userId]!.then(data =>
createSession(data.user.id, data.user.email, oidcIntegrationId ?? null)
createSession(data.user.id, data.user.email, oidcIntegrationId ?? null),
);
}

View file

@ -16,9 +16,13 @@ async function exec(cmd: string) {
}
export async function schemaPublish(args: string[]) {
return exec(['schema:publish', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '));
return exec(
['schema:publish', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '),
);
}
export async function schemaCheck(args: string[]) {
return exec(['schema:check', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '));
return exec(
['schema:check', `--registry`, `http://${registryAddress}/graphql`, ...args].join(' '),
);
}

View file

@ -9,14 +9,16 @@ export const resetDb = async (conn: DatabasePoolConnection) => {
WHERE "schemaname" = 'public';
`);
const tablenames = result.map(({ tablename }) => tablename).filter(tablename => !migrationTables.includes(tablename));
const tablenames = result
.map(({ tablename }) => tablename)
.filter(tablename => !migrationTables.includes(tablename));
if (tablenames.length) {
await conn.query(sql`
TRUNCATE TABLE
${sql.join(
tablenames.map(name => sql.identifier([name])),
sql`,`
sql`,`,
)}
RESTART IDENTITY
;

View file

@ -14,7 +14,7 @@ export const startReadinessCheck: ReadinessCheck = ({ runner }) => {
return runner.dockerEventStream$.pipe(
filter(ev => ev.action === 'start'),
mapTo(undefined),
take(1)
take(1),
);
};
@ -82,7 +82,9 @@ export function createServices() {
}
export function cleanDockerContainers() {
const output = execa(`docker ps --all --filter "name=integration-tests" --format={{.ID}}:{{.Status}}`);
const output = execa(
`docker ps --all --filter "name=integration-tests" --format={{.ID}}:{{.Status}}`,
);
if (output.stdout.length) {
const runningContainers = output.stdout.split('\n');

View file

@ -8,7 +8,7 @@ export function invariant(
condition: any,
// Can provide a string, or a function that returns a string for cases where
// the message takes a fair amount of effort to compute
message?: string | (() => string)
message?: string | (() => string),
): asserts condition {
if (condition) {
return;
@ -32,6 +32,7 @@ export function ensureEnv(key: string, valueType: 'string'): string;
export function ensureEnv(key: string, valueType: 'number'): number;
export function ensureEnv(key: string, valueType: 'boolean'): boolean;
export function ensureEnv(key: string, valueType?: ValueType) {
// eslint-disable-next-line no-process-env
let value = process.env[key];
if (value === '<sync>') {

View file

@ -393,7 +393,11 @@ export function updateMemberAccess(input: OrganizationMemberAccessInput, authTok
});
}
export function publishSchema(input: SchemaPublishInput, token: string, authHeader?: 'x-api-token' | 'authorization') {
export function publishSchema(
input: SchemaPublishInput,
token: string,
authHeader?: 'x-api-token' | 'authorization',
) {
return execute({
document: gql(/* GraphQL */ `
mutation schemaPublish($input: SchemaPublishInput!) {
@ -491,7 +495,7 @@ export function setTargetValidation(
}
| {
authToken: string;
}
},
) {
return execute({
document: gql(/* GraphQL */ `
@ -519,7 +523,7 @@ export function updateTargetValidationSettings(
}
| {
authToken: string;
}
},
) {
return execute({
document: gql(/* GraphQL */ `
@ -838,11 +842,14 @@ export async function fetchMetadataFromCDN(selector: TargetSelectorInput, token:
export async function updateOrgRateLimit(
selector: OrganizationSelectorInput,
monthlyLimits: RateLimitInput,
authToken: string
authToken: string,
) {
return execute({
document: gql(/* GraphQL */ `
mutation updateOrgRateLimit($selector: OrganizationSelectorInput!, $monthlyLimits: RateLimitInput!) {
mutation updateOrgRateLimit(
$selector: OrganizationSelectorInput!
$monthlyLimits: RateLimitInput!
) {
updateOrgRateLimit(selector: $selector, monthlyLimits: $monthlyLimits) {
id
}
@ -856,7 +863,10 @@ export async function updateOrgRateLimit(
});
}
export async function enableExternalSchemaComposition(input: EnableExternalSchemaCompositionInput, token: string) {
export async function enableExternalSchemaComposition(
input: EnableExternalSchemaCompositionInput,
token: string,
) {
return execute({
document: gql(/* GraphQL */ `
mutation enableExternalSchemaComposition($input: EnableExternalSchemaCompositionInput!) {

View file

@ -17,7 +17,9 @@ export async function execute<TResult, TVariables>(
authToken?: string;
token?: string;
legacyAuthorizationMode?: boolean;
} & (TVariables extends Record<string, never> ? { variables?: never } : { variables: TVariables })
} & (TVariables extends Record<string, never>
? { variables?: never }
: { variables: TVariables }),
) {
const response = await fetch(`http://${registryAddress}/graphql`, {
method: 'POST',

View file

@ -18,7 +18,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -29,7 +29,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -45,7 +45,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -59,7 +59,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa
sdl: `type Query { ping: String }`,
},
token,
'x-api-token'
'x-api-token',
);
expect(result.body.errors).not.toBeDefined();
@ -98,7 +98,7 @@ test('X-API-Token header should work when calling GraphQL API and collecting usa
to,
},
},
token
token,
);
expect(operationStatsResult.body.errors).not.toBeDefined();

View file

@ -47,10 +47,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -139,10 +140,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -180,10 +182,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -221,10 +224,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -262,10 +266,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -303,10 +308,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const result = await execute({
document: CreateOIDCIntegrationMutation,
@ -344,10 +350,11 @@ describe('create', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
let result = await execute({
document: CreateOIDCIntegrationMutation,
@ -429,10 +436,11 @@ describe('delete', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const createResult = await execute({
document: CreateOIDCIntegrationMutation,
@ -448,7 +456,8 @@ describe('delete', () => {
});
expect(createResult.body.errors).toBeUndefined();
const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const oidcIntegrationId =
createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
let refetchedOrg = await execute({
document: OrganizationWithOIDCIntegration,
@ -522,10 +531,11 @@ describe('delete', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const createResult = await execute({
document: CreateOIDCIntegrationMutation,
@ -541,7 +551,8 @@ describe('delete', () => {
});
expect(createResult.body.errors).toBeUndefined();
const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const oidcIntegrationId =
createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const { access_token: accessTokenExtra } = await authenticate('extra');
@ -580,10 +591,11 @@ describe('delete', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const createResult = await execute({
document: CreateOIDCIntegrationMutation,
@ -599,7 +611,8 @@ describe('delete', () => {
});
expect(createResult.body.errors).toBeUndefined();
const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const oidcIntegrationId =
createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const MeQuery = gql(/* GraphQL */ `
query Me {
@ -610,7 +623,10 @@ describe('delete', () => {
`);
// create new member that belongs to oidc integration
const { access_token: memberAccessToken } = await authenticate('oidc_member', oidcIntegrationId);
const { access_token: memberAccessToken } = await authenticate(
'oidc_member',
oidcIntegrationId,
);
let meResult = await execute({
document: MeQuery,
authToken: memberAccessToken,
@ -694,10 +710,11 @@ describe('update', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const createResult = await execute({
document: CreateOIDCIntegrationMutation,
@ -713,7 +730,8 @@ describe('update', () => {
});
expect(createResult.body.errors).toBeUndefined();
const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const oidcIntegrationId =
createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const updateResult = await execute({
document: UpdateOIDCIntegrationMutation,
@ -749,10 +767,11 @@ describe('update', () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const createResult = await execute({
document: CreateOIDCIntegrationMutation,
@ -768,7 +787,8 @@ describe('update', () => {
});
expect(createResult.body.errors).toBeUndefined();
const oidcIntegrationId = createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const oidcIntegrationId =
createResult.body.data!.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const { access_token: accessTokenExtra } = await authenticate('extra');

View file

@ -7,7 +7,7 @@ test('renaming an organization should result changing its cleanId', async () =>
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -16,19 +16,22 @@ test('renaming an organization should result changing its cleanId', async () =>
organization: org.cleanId,
name: 'bar',
},
access_token
access_token,
);
expect(renamedOrganizationResult.body.errors).not.toBeDefined();
expect(renamedOrganizationResult.body.data?.updateOrganizationName.error).toBeNull();
expect(
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.organization.name
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload
.organization.name,
).toBe('bar');
expect(
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.organization.cleanId
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload
.organization.cleanId,
).toBe('bar');
expect(
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload.selector.organization
renamedOrganizationResult.body.data?.updateOrganizationName.ok?.updatedOrganizationPayload
.selector.organization,
).toBe('bar');
});

View file

@ -12,7 +12,12 @@ import {
} from '../../../testkit/flow';
import { authenticate } from '../../../testkit/auth';
import { collect } from '../../../testkit/usage';
import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql';
import {
TargetAccessScope,
ProjectType,
ProjectAccessScope,
OrganizationAccessScope,
} from '@app/gql/graphql';
async function getSteps({ organization, token }: { organization: string; token: string }) {
const result = await getOrganization(organization, token);
@ -28,7 +33,7 @@ test('freshly created organization has Get Started progress at 0%', async () =>
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -51,7 +56,7 @@ test('completing each step should result in updated Get Started progress', async
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -63,7 +68,7 @@ test('completing each step should result in updated Get Started progress', async
name: 'foo',
type: ProjectType.Single,
},
access_token
access_token,
);
let steps = await getSteps({
@ -80,7 +85,9 @@ test('completing each step should result in updated Get Started progress', async
expect(projectResult.body.errors).not.toBeDefined();
const target = projectResult.body.data?.createProject.ok?.createdTargets.find(t => t.name === 'production');
const target = projectResult.body.data?.createProject.ok?.createdTargets.find(
t => t.name === 'production',
);
const project = projectResult.body.data?.createProject.ok?.createdProject;
if (!target || !project) {
@ -102,7 +109,7 @@ test('completing each step should result in updated Get Started progress', async
TargetAccessScope.Settings,
],
},
access_token
access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -117,7 +124,7 @@ test('completing each step should result in updated Get Started progress', async
commit: 'test',
sdl: 'type Query { foo: String }',
},
token
token,
);
steps = await getSteps({
@ -138,7 +145,7 @@ test('completing each step should result in updated Get Started progress', async
{
sdl: 'type Query { foo: String bar: String }',
},
token
token,
);
steps = await getSteps({
@ -160,7 +167,7 @@ test('completing each step should result in updated Get Started progress', async
email: 'some@email.com',
organization: org.cleanId,
},
access_token
access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -224,7 +231,7 @@ test('completing each step should result in updated Get Started progress', async
},
{
token,
}
},
);
steps = await getSteps({

View file

@ -1,5 +1,10 @@
import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '@app/gql/graphql';
import { createOrganization, inviteToOrganization, joinOrganization, updateMemberAccess } from '../../../testkit/flow';
import {
createOrganization,
inviteToOrganization,
joinOrganization,
updateMemberAccess,
} from '../../../testkit/flow';
import { authenticate } from '../../../testkit/auth';
import { history } from '../../../testkit/emails';
@ -9,12 +14,13 @@ test('owner of an organization should have all scopes', async () => {
{
name: 'foo',
},
access_token
access_token,
);
expect(result.body.errors).not.toBeDefined();
const owner = result.body.data!.createOrganization.ok!.createdOrganizationPayload.organization.owner;
const owner =
result.body.data!.createOrganization.ok!.createdOrganizationPayload.organization.owner;
Object.values(OrganizationAccessScope).forEach(scope => {
expect(owner.organizationAccessScopes).toContain(scope);
@ -35,7 +41,7 @@ test('regular member of an organization should have basic scopes', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -46,7 +52,7 @@ test('regular member of an organization should have basic scopes', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -89,7 +95,7 @@ test('cannot grant an access scope to another user if user has no access to that
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -100,7 +106,7 @@ test('cannot grant an access scope to another user if user has no access to that
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -125,7 +131,7 @@ test('cannot grant an access scope to another user if user has no access to that
targetScopes: [],
user: member.id,
},
owner_access_token
owner_access_token,
);
// Grant access to target:tokens:write
@ -137,7 +143,7 @@ test('cannot grant an access scope to another user if user has no access to that
targetScopes: [TargetAccessScope.TokensWrite],
user: member.id,
},
member_access_token
member_access_token,
);
expect(accessResult.body.errors).toHaveLength(1);
@ -150,7 +156,7 @@ test('granting no scopes is equal to setting read-only for org, project and targ
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -161,7 +167,7 @@ test('granting no scopes is equal to setting read-only for org, project and targ
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -186,14 +192,15 @@ test('granting no scopes is equal to setting read-only for org, project and targ
targetScopes: [],
user: member.id,
},
owner_access_token
owner_access_token,
);
expect(accessResult.body.errors).not.toBeDefined();
const memberWithAccess = accessResult.body.data?.updateOrganizationMemberAccess.organization.members.nodes.find(
m => m.id === member.id
);
const memberWithAccess =
accessResult.body.data?.updateOrganizationMemberAccess.organization.members.nodes.find(
m => m.id === member.id,
);
expect(memberWithAccess?.organizationAccessScopes).toHaveLength(1);
expect(memberWithAccess?.organizationAccessScopes).toContainEqual(OrganizationAccessScope.Read);
expect(memberWithAccess?.projectAccessScopes).toHaveLength(1);
@ -208,9 +215,10 @@ test('email invitation', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
// Invite
const email = 'invited@invited.com';
@ -219,7 +227,7 @@ test('email invitation', async () => {
email,
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -235,9 +243,10 @@ test('cannot join organization twice using the same invitation code', async () =
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const org =
createOrgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
// Invite
const invitationResult = await inviteToOrganization(
@ -245,7 +254,7 @@ test('cannot join organization twice using the same invitation code', async () =
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -264,5 +273,7 @@ test('cannot join organization twice using the same invitation code', async () =
const { access_token: another_access_token } = await authenticate('admin');
const secondJoinResult = await joinOrganization(inviteCode!, another_access_token);
expect(secondJoinResult.body.data?.joinOrganization.__typename).toBe('OrganizationInvitationError');
expect(secondJoinResult.body.data?.joinOrganization.__typename).toBe(
'OrganizationInvitationError',
);
});

View file

@ -1,5 +1,10 @@
import { ProjectType, ProjectAccessScope } from '@app/gql/graphql';
import { createOrganization, publishPersistedOperations, createProject, createToken } from '../../../testkit/flow';
import {
createOrganization,
publishPersistedOperations,
createProject,
createToken,
} from '../../../testkit/flow';
import { authenticate } from '../../../testkit/auth';
test('can publish persisted operations only with project:operations-store:write', async () => {
@ -8,7 +13,7 @@ test('can publish persisted operations only with project:operations-store:write'
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -18,7 +23,7 @@ test('can publish persisted operations only with project:operations-store:write'
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -35,7 +40,7 @@ test('can publish persisted operations only with project:operations-store:write'
projectScopes: [],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(noAccessTokenResult.body.errors).not.toBeDefined();
@ -50,7 +55,7 @@ test('can publish persisted operations only with project:operations-store:write'
projectScopes: [ProjectAccessScope.OperationsStoreRead],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(readTokenResult.body.errors).not.toBeDefined();
@ -62,10 +67,13 @@ test('can publish persisted operations only with project:operations-store:write'
project: project.cleanId,
target: target.cleanId,
organizationScopes: [],
projectScopes: [ProjectAccessScope.OperationsStoreRead, ProjectAccessScope.OperationsStoreWrite],
projectScopes: [
ProjectAccessScope.OperationsStoreRead,
ProjectAccessScope.OperationsStoreWrite,
],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
@ -113,7 +121,7 @@ test('should skip on already persisted operations', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -123,7 +131,7 @@ test('should skip on already persisted operations', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -137,10 +145,13 @@ test('should skip on already persisted operations', async () => {
project: project.cleanId,
target: target.cleanId,
organizationScopes: [],
projectScopes: [ProjectAccessScope.OperationsStoreRead, ProjectAccessScope.OperationsStoreWrite],
projectScopes: [
ProjectAccessScope.OperationsStoreRead,
ProjectAccessScope.OperationsStoreWrite,
],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
@ -181,8 +192,12 @@ test('should skip on already persisted operations', async () => {
expect(persisted.summary.unchanged).toEqual(1);
expect(persisted.operations).toHaveLength(2);
const meOperation = persisted.operations.find(op => op.operationHash === operations[0].operationHash);
const userOperation = persisted.operations.find(op => op.operationHash === operations[1].operationHash);
const meOperation = persisted.operations.find(
op => op.operationHash === operations[0].operationHash,
);
const userOperation = persisted.operations.find(
op => op.operationHash === operations[1].operationHash,
);
expect(meOperation?.operationHash).toEqual(operations[0].operationHash);
expect(userOperation?.operationHash).toEqual(operations[1].operationHash);

View file

@ -8,7 +8,7 @@ test('creating a project should result in creating the development, staging and
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -18,7 +18,7 @@ test('creating a project should result in creating the development, staging and
type: ProjectType.Single,
name: 'foo',
},
access_token
access_token,
);
const targets = projectResult.body.data!.createProject.ok!.createdTargets;
@ -28,19 +28,19 @@ test('creating a project should result in creating the development, staging and
expect.objectContaining({
cleanId: 'development',
name: 'development',
})
}),
);
expect(targets).toContainEqual(
expect.objectContaining({
cleanId: 'staging',
name: 'staging',
})
}),
);
expect(targets).toContainEqual(
expect.objectContaining({
cleanId: 'production',
name: 'production',
})
}),
);
});
@ -50,7 +50,7 @@ test('renaming a project should result changing its cleanId', async () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -60,7 +60,7 @@ test('renaming a project should result changing its cleanId', async () => {
type: ProjectType.Single,
name: 'foo',
},
access_token
access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -71,7 +71,7 @@ test('renaming a project should result changing its cleanId', async () => {
project: project.cleanId,
name: 'bar',
},
access_token
access_token,
);
expect(renamedProjectResult.body.errors).not.toBeDefined();

View file

@ -1,5 +1,16 @@
import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql';
import { createOrganization, createProject, createToken, updateOrgRateLimit, waitFor } from '../../../testkit/flow';
import {
TargetAccessScope,
ProjectType,
ProjectAccessScope,
OrganizationAccessScope,
} from '@app/gql/graphql';
import {
createOrganization,
createProject,
createToken,
updateOrgRateLimit,
waitFor,
} from '../../../testkit/flow';
import * as emails from '../../../testkit/emails';
import { authenticate, userEmails } from '../../../testkit/auth';
import { collect } from '../../../testkit/usage';
@ -24,7 +35,7 @@ test('rate limit approaching and reached for organization', async () => {
{
name: generateUnique(),
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -34,11 +45,13 @@ test('rate limit approaching and reached for organization', async () => {
type: ProjectType.Single,
name: 'bar',
},
access_token
access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
const target = projectResult.body.data!.createProject.ok!.createdTargets.find(t => t.name === 'production')!;
const target = projectResult.body.data!.createProject.ok!.createdTargets.find(
t => t.name === 'production',
)!;
await updateOrgRateLimit(
{
@ -47,7 +60,7 @@ test('rate limit approaching and reached for organization', async () => {
{
operations: 11,
},
access_token
access_token,
);
const tokenResult = await createToken(
@ -58,9 +71,13 @@ test('rate limit approaching and reached for organization', async () => {
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
access_token
access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();

View file

@ -16,7 +16,7 @@ test('can check a schema with target:registry:read access', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -25,7 +25,7 @@ test('can check a schema with target:registry:read access', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -41,7 +41,7 @@ test('can check a schema with target:registry:read access', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -58,7 +58,7 @@ test('can check a schema with target:registry:read access', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -70,7 +70,7 @@ test('can check a schema with target:registry:read access', async () => {
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -88,7 +88,7 @@ test('can check a schema with target:registry:read access', async () => {
projectScopes: [],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(noAccessTokenResult.body.errors).not.toBeDefined();
@ -103,7 +103,7 @@ test('can check a schema with target:registry:read access', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead],
},
owner_access_token
owner_access_token,
);
expect(readTokenResult.body.errors).not.toBeDefined();
@ -115,7 +115,7 @@ test('can check a schema with target:registry:read access', async () => {
{
sdl: `type Query { ping: String foo: String }`,
},
noAccessToken
noAccessToken,
);
expect(checkResult.body.errors).toHaveLength(1);
expect(checkResult.body.errors![0].message).toMatch('target:registry:read');
@ -125,7 +125,7 @@ test('can check a schema with target:registry:read access', async () => {
{
sdl: `type Query { ping: String foo: String }`,
},
readToken
readToken,
);
expect(checkResult.body.errors).not.toBeDefined();
expect(checkResult.body.data!.schemaCheck.__typename).toBe('SchemaCheckSuccess');
@ -137,7 +137,7 @@ test('should match indentation of previous description', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -146,7 +146,7 @@ test('should match indentation of previous description', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -162,7 +162,7 @@ test('should match indentation of previous description', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -179,7 +179,7 @@ test('should match indentation of previous description', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -198,7 +198,7 @@ test('should match indentation of previous description', async () => {
}
`,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -216,7 +216,7 @@ test('should match indentation of previous description', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead],
},
owner_access_token
owner_access_token,
);
expect(readTokenResult.body.errors).not.toBeDefined();
@ -236,7 +236,7 @@ test('should match indentation of previous description', async () => {
}
`,
},
readToken
readToken,
);
expect(checkResult.body.errors).not.toBeDefined();

View file

@ -15,7 +15,7 @@ test('call an external service to compose and validate services', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -25,7 +25,7 @@ test('call an external service to compose and validate services', async () => {
type: ProjectType.Federation,
name: 'bar',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -42,7 +42,7 @@ test('call an external service to compose and validate services', async () => {
projectScopes: [ProjectAccessScope.Settings, ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -56,7 +56,7 @@ test('call an external service to compose and validate services', async () => {
sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`,
service: usersServiceName,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -70,15 +70,16 @@ test('call an external service to compose and validate services', async () => {
const externalCompositionResult = await enableExternalSchemaComposition(
{
endpoint: `http://${dockerAddress}/compose`,
// eslint-disable-next-line no-process-env
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
project: project.cleanId,
organization: org.cleanId,
},
writeToken
writeToken,
);
expect(externalCompositionResult.body.errors).not.toBeDefined();
expect(externalCompositionResult.body.data!.enableExternalSchemaComposition.ok?.endpoint).toBe(
`http://${dockerAddress}/compose`
`http://${dockerAddress}/compose`,
);
const productsServiceName = Math.random().toString(16).substring(2);
@ -90,7 +91,7 @@ test('call an external service to compose and validate services', async () => {
sdl: `type Query { products: [Product] } type Product @key(fields: "id") { id: ID! name: String }`,
service: productsServiceName,
},
writeToken
writeToken,
);
// Schema publish should be successful

View file

@ -25,7 +25,7 @@ test('cannot publish a schema without target:registry:write access', async () =>
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -37,7 +37,7 @@ test('cannot publish a schema without target:registry:write access', async () =>
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -51,7 +51,7 @@ test('cannot publish a schema without target:registry:write access', async () =>
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -67,7 +67,7 @@ test('cannot publish a schema without target:registry:write access', async () =>
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -78,7 +78,7 @@ test('cannot publish a schema without target:registry:write access', async () =>
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(result.body.errors).toHaveLength(1);
@ -91,7 +91,7 @@ test('can publish a schema with target:registry:write access', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -103,7 +103,7 @@ test('can publish a schema with target:registry:write access', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -117,7 +117,7 @@ test('can publish a schema with target:registry:write access', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -133,7 +133,7 @@ test('can publish a schema with target:registry:write access', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -146,7 +146,7 @@ test('can publish a schema with target:registry:write access', async () => {
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -158,7 +158,7 @@ test('can publish a schema with target:registry:write access', async () => {
commit: 'abc123',
sdl: `type Query { ping: String pong: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -171,7 +171,7 @@ test('can publish a schema with target:registry:write access', async () => {
target: target.cleanId,
},
3,
token
token,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -184,7 +184,7 @@ test('base schema should not affect the output schema persisted in db', async ()
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -194,7 +194,7 @@ test('base schema should not affect the output schema persisted in db', async ()
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -211,7 +211,7 @@ test('base schema should not affect the output schema persisted in db', async ()
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -223,7 +223,7 @@ test('base schema should not affect the output schema persisted in db', async ()
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -239,7 +239,7 @@ test('base schema should not affect the output schema persisted in db', async ()
project: project.cleanId,
target: target.cleanId,
},
writeToken
writeToken,
);
expect(updateBaseResult.body.errors).not.toBeDefined();
@ -250,7 +250,7 @@ test('base schema should not affect the output schema persisted in db', async ()
author: 'Kamil',
commit: 'abc234',
},
writeToken
writeToken,
);
expect(publishResult.body.errors).not.toBeDefined();
expect(publishResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess');
@ -262,7 +262,7 @@ test('base schema should not affect the output schema persisted in db', async ()
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -273,10 +273,12 @@ test('base schema should not affect the output schema persisted in db', async ()
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc234');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
'type Query { ping: String @auth pong: String }'
'type Query { ping: String @auth pong: String }',
);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).not.toMatch('directive');
expect(latestResult.body.data!.latestVersion.baseSchema).toMatch('directive @auth on OBJECT | FIELD_DEFINITION');
expect(latestResult.body.data!.latestVersion.baseSchema).toMatch(
'directive @auth on OBJECT | FIELD_DEFINITION',
);
});
test('directives should not be removed (federation)', async () => {
@ -285,7 +287,7 @@ test('directives should not be removed (federation)', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -295,7 +297,7 @@ test('directives should not be removed (federation)', async () => {
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -312,7 +314,7 @@ test('directives should not be removed (federation)', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -326,7 +328,7 @@ test('directives should not be removed (federation)', async () => {
service: 'users',
sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -340,7 +342,7 @@ test('directives should not be removed (federation)', async () => {
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -351,7 +353,7 @@ test('directives should not be removed (federation)', async () => {
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`,
);
});
@ -361,7 +363,7 @@ test('should allow to update the URL of a Federated service without changing the
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -371,7 +373,7 @@ test('should allow to update the URL of a Federated service without changing the
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -388,7 +390,7 @@ test('should allow to update the URL of a Federated service without changing the
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -414,7 +416,7 @@ test('should allow to update the URL of a Federated service without changing the
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -427,22 +429,24 @@ test('should allow to update the URL of a Federated service without changing the
url: `http://localhost:3000/test/graphql`,
commit: 'abc1234',
},
writeToken
writeToken,
);
expect(updateResult.body.errors).not.toBeDefined();
expect(updateResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess');
expect((updateResult.body.data!.schemaPublish as any).message).toBe(
'Updated: New service url: http://localhost:3000/test/graphql (previously: https://api.com/users)'
'Updated: New service url: http://localhost:3000/test/graphql (previously: https://api.com/users)',
);
const latestResult = await fetchLatestSchema(writeToken);
expect(latestResult.body.errors).not.toBeDefined();
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc1234');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].url).toBe('http://localhost:3000/test/graphql');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].url).toBe(
'http://localhost:3000/test/graphql',
);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`,
);
});
@ -452,7 +456,7 @@ test('should allow to update the URL of a Federated service while also changing
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -462,7 +466,7 @@ test('should allow to update the URL of a Federated service while also changing
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -479,7 +483,7 @@ test('should allow to update the URL of a Federated service while also changing
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -505,7 +509,7 @@ test('should allow to update the URL of a Federated service while also changing
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -516,7 +520,7 @@ test('should allow to update the URL of a Federated service while also changing
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`
`type Query { me: User } type User @key(fields: "id") { id: ID! name: String }`,
);
// try to update the schema again, with force and url set
@ -528,7 +532,7 @@ test('should allow to update the URL of a Federated service while also changing
// here, we also add something minor to the schema, just to trigger the publish flow and not just the URL update flow
sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! name: String age: Int }`,
},
writeToken
writeToken,
);
expect(updateResult.body.errors).not.toBeDefined();
@ -541,7 +545,7 @@ test('directives should not be removed (stitching)', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -551,7 +555,7 @@ test('directives should not be removed (stitching)', async () => {
type: ProjectType.Stitching,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -568,7 +572,7 @@ test('directives should not be removed (stitching)', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -582,7 +586,7 @@ test('directives should not be removed (stitching)', async () => {
service: 'test',
url: 'https://api.com/users',
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -596,7 +600,7 @@ test('directives should not be removed (stitching)', async () => {
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -607,7 +611,7 @@ test('directives should not be removed (stitching)', async () => {
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
`type Query { me: User } type User @key(selectionSet: "{ id }") { id: ID! name: String }`
`type Query { me: User } type User @key(selectionSet: "{ id }") { id: ID! name: String }`,
);
});
@ -617,7 +621,7 @@ test('directives should not be removed (single)', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -627,7 +631,7 @@ test('directives should not be removed (single)', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -644,7 +648,7 @@ test('directives should not be removed (single)', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -658,7 +662,7 @@ test('directives should not be removed (single)', async () => {
service: 'test',
url: 'https://api.com/users',
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -672,7 +676,7 @@ test('directives should not be removed (single)', async () => {
target: target.cleanId,
},
5,
writeToken
writeToken,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -683,7 +687,7 @@ test('directives should not be removed (single)', async () => {
expect(latestResult.body.data!.latestVersion.schemas.total).toBe(1);
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].commit).toBe('abc123');
expect(latestResult.body.data!.latestVersion.schemas.nodes[0].source).toMatch(
`directive @auth on FIELD_DEFINITION type Query { me: User @auth } type User { id: ID! name: String }`
`directive @auth on FIELD_DEFINITION type Query { me: User @auth } type User { id: ID! name: String }`,
);
});
@ -693,7 +697,7 @@ test('share publication of schema using redis', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -703,7 +707,7 @@ test('share publication of schema using redis', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -720,7 +724,7 @@ test('share publication of schema using redis', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -732,7 +736,7 @@ test('share publication of schema using redis', async () => {
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
writeToken
writeToken,
);
// Schema publish should be successful
@ -746,7 +750,7 @@ test('share publication of schema using redis', async () => {
author: 'Kamil',
commit: 'abc234',
},
writeToken
writeToken,
),
publishSchema(
{
@ -754,7 +758,7 @@ test('share publication of schema using redis', async () => {
author: 'Kamil',
commit: 'abc234',
},
writeToken
writeToken,
),
]);
expect(publishResult1.body.errors).not.toBeDefined();
@ -769,7 +773,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const projectResult = await createProject(
@ -778,7 +782,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
@ -792,7 +796,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -802,7 +806,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
writeToken
writeToken,
);
const createTargetResult = await createTarget(
{
@ -810,7 +814,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
project: project.cleanId,
name: 'target2',
},
owner_access_token
owner_access_token,
);
const target2 = createTargetResult.body!.data!.createTarget.ok!.createdTarget;
const writeTokenResult2 = await createToken(
@ -823,7 +827,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
const writeToken2 = writeTokenResult2.body.data!.createToken.ok!.secret;
const publishResult2 = await publishSchema(
@ -832,7 +836,7 @@ test("Two targets with the same commit id shouldn't return an error", async () =
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
writeToken2
writeToken2,
);
// Schema publish should be successful
expect(publishResult.body.errors).not.toBeDefined();
@ -847,7 +851,7 @@ test('marking versions as valid', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -859,7 +863,7 @@ test('marking versions as valid', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -873,7 +877,7 @@ test('marking versions as valid', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -889,7 +893,7 @@ test('marking versions as valid', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -903,7 +907,7 @@ test('marking versions as valid', async () => {
commit: 'c0',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -918,7 +922,7 @@ test('marking versions as valid', async () => {
force: true,
metadata: JSON.stringify({ c1: true }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -932,7 +936,7 @@ test('marking versions as valid', async () => {
force: true,
metadata: JSON.stringify({ c2: true }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -944,7 +948,7 @@ test('marking versions as valid', async () => {
target: target.cleanId,
},
3,
token
token,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -954,7 +958,9 @@ test('marking versions as valid', async () => {
let latestValidSchemaResult = await fetchLatestValidSchema(token);
expect(latestValidSchemaResult.body.errors).not.toBeDefined();
expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.total).toEqual(1);
expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.nodes[0].commit).toEqual('c0');
expect(latestValidSchemaResult.body.data!.latestValidVersion.schemas.nodes[0].commit).toEqual(
'c0',
);
const versionId = (commit: string) =>
versionsResult.body.data!.schemaVersions.nodes.find(node => node.commit.commit === commit)!.id;
@ -968,11 +974,13 @@ test('marking versions as valid', async () => {
valid: true,
version: versionId('c2'),
},
token
token,
);
expect(versionStatusUpdateResult.body.errors).not.toBeDefined();
expect(versionStatusUpdateResult.body.data!.updateSchemaVersionStatus.id).toEqual(versionId('c2'));
expect(versionStatusUpdateResult.body.data!.updateSchemaVersionStatus.id).toEqual(
versionId('c2'),
);
latestValidSchemaResult = await fetchLatestValidSchema(token);
expect(latestValidSchemaResult.body.errors).not.toBeDefined();
@ -987,7 +995,7 @@ test('marking versions as valid', async () => {
valid: true,
version: versionId('c1'),
},
token
token,
);
expect(versionStatusUpdateResult.body.errors).not.toBeDefined();
@ -1002,7 +1010,7 @@ test('marking only the most recent version as valid result in an update of CDN',
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -1014,7 +1022,7 @@ test('marking only the most recent version as valid result in an update of CDN',
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -1028,7 +1036,7 @@ test('marking only the most recent version as valid result in an update of CDN',
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1044,7 +1052,7 @@ test('marking only the most recent version as valid result in an update of CDN',
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1059,7 +1067,7 @@ test('marking only the most recent version as valid result in an update of CDN',
sdl: `type Query { ping: String }`,
metadata: JSON.stringify({ c0: 1 }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1074,7 +1082,7 @@ test('marking only the most recent version as valid result in an update of CDN',
force: true,
metadata: JSON.stringify({ c1: 1 }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1088,7 +1096,7 @@ test('marking only the most recent version as valid result in an update of CDN',
force: true,
metadata: JSON.stringify({ c2: 1 }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1121,7 +1129,7 @@ test('marking only the most recent version as valid result in an update of CDN',
valid: true,
version: versionId('c2'),
},
token
token,
);
cdnResult = await fetchSchemaFromCDN(targetSelector, token);
@ -1141,7 +1149,7 @@ test('marking only the most recent version as valid result in an update of CDN',
valid: true,
version: versionId('c1'),
},
token
token,
);
// console.log(JSON.stringify(updateSchemaVersionStatusResult));
@ -1159,7 +1167,7 @@ test('CDN data can not be fetched with an invalid access token', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -1171,7 +1179,7 @@ test('CDN data can not be fetched with an invalid access token', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -1185,7 +1193,7 @@ test('CDN data can not be fetched with an invalid access token', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1201,7 +1209,7 @@ test('CDN data can not be fetched with an invalid access token', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1216,7 +1224,7 @@ test('CDN data can not be fetched with an invalid access token', async () => {
sdl: `type Query { ping: String }`,
metadata: JSON.stringify({ c0: 1 }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1253,7 +1261,7 @@ test('CDN data can be fetched with an valid access token', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -1265,7 +1273,7 @@ test('CDN data can be fetched with an valid access token', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -1279,7 +1287,7 @@ test('CDN data can be fetched with an valid access token', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1295,7 +1303,7 @@ test('CDN data can be fetched with an valid access token', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1310,7 +1318,7 @@ test('CDN data can be fetched with an valid access token', async () => {
sdl: `type Query { ping: String }`,
metadata: JSON.stringify({ c0: 1 }),
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1347,7 +1355,7 @@ test('linkToWebsite should be available when publishing initial schema', async (
{
name: 'bar',
},
owner_access_token
owner_access_token,
);
// Join
@ -1359,7 +1367,7 @@ test('linkToWebsite should be available when publishing initial schema', async (
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -1373,7 +1381,7 @@ test('linkToWebsite should be available when publishing initial schema', async (
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1390,7 +1398,7 @@ test('linkToWebsite should be available when publishing initial schema', async (
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1403,7 +1411,7 @@ test('linkToWebsite should be available when publishing initial schema', async (
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1423,7 +1431,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
{
name: 'bar',
},
owner_access_token
owner_access_token,
);
// Join
@ -1435,7 +1443,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -1449,7 +1457,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1466,7 +1474,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1479,7 +1487,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
commit: 'abc123',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1491,7 +1499,7 @@ test('linkToWebsite should be available when publishing non-initial schema', asy
commit: 'abc123',
sdl: `type Query { ping: String pong: String }`,
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1513,7 +1521,7 @@ test('cannot do API request with invalid access token', async () => {
sdl: 'type Query { smokeBangBang: String }',
author: 'Kamil',
},
'foobars'
'foobars',
);
expect(orgResult).toEqual({
body: {
@ -1541,7 +1549,7 @@ test('publish new schema when a field is moved from one service to another (stit
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1552,7 +1560,7 @@ test('publish new schema when a field is moved from one service to another (stit
type: ProjectType.Stitching,
name: 'foo',
},
access_token
access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1568,7 +1576,7 @@ test('publish new schema when a field is moved from one service to another (stit
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
access_token
access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1587,7 +1595,7 @@ test('publish new schema when a field is moved from one service to another (stit
`,
service: 'cats',
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1606,7 +1614,7 @@ test('publish new schema when a field is moved from one service to another (stit
`,
service: 'dogs',
},
token
token,
);
expect(result.body.errors).not.toBeDefined();
@ -1625,7 +1633,7 @@ test('publish new schema when a field is moved from one service to another (stit
`,
service: 'cats',
},
token
token,
);
// We expect to have a new version, even tough the schema (merged) is the same
@ -1640,7 +1648,7 @@ test('publish new schema when a field is moved from one service to another (stit
target: target.cleanId,
},
3,
token
token,
);
expect(versionsResult.body.errors).not.toBeDefined();
@ -1653,7 +1661,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1663,7 +1671,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1680,7 +1688,7 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -1710,11 +1718,15 @@ test('(experimental_acceptBreakingChanges) accept breaking changes if schema is
// We also removed the `name` field (breaking)
sdl: `type Query { me: User } type User @key(fields: "id") { id: ID! }`,
},
writeToken
writeToken,
);
const latestValid = await fetchLatestValidSchema(writeToken);
expect(composableButBreakingResult.body.data!.schemaPublish.__typename).toBe('SchemaPublishSuccess');
expect(latestValid.body.data?.latestValidVersion.schemas.nodes[0].commit).toBe('composable-but-breaking');
expect(composableButBreakingResult.body.data!.schemaPublish.__typename).toBe(
'SchemaPublishSuccess',
);
expect(latestValid.body.data?.latestValidVersion.schemas.nodes[0].commit).toBe(
'composable-but-breaking',
);
});

View file

@ -15,7 +15,7 @@ test('marking only the most recent version as valid result in an update of CDN',
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -26,7 +26,7 @@ test('marking only the most recent version as valid result in an update of CDN',
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -42,7 +42,7 @@ test('marking only the most recent version as valid result in an update of CDN',
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -56,7 +56,7 @@ test('marking only the most recent version as valid result in an update of CDN',
commit: 'c0',
sdl: `type Query { ping: String }`,
},
token
token,
);
expect(publishResult.body.errors).not.toBeDefined();
@ -79,7 +79,7 @@ test('marking only the most recent version as valid result in an update of CDN',
project: project.cleanId,
target: target.cleanId,
},
token
token,
);
expect(syncResult.body.errors).not.toBeDefined();

View file

@ -8,7 +8,7 @@ test('renaming a target should result changing its cleanId', async () => {
{
name: 'foo',
},
access_token
access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -18,11 +18,13 @@ test('renaming a target should result changing its cleanId', async () => {
type: ProjectType.Single,
name: 'foo',
},
access_token
access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
const target = projectResult.body.data?.createProject.ok?.createdTargets.find(t => t.name === 'production');
const target = projectResult.body.data?.createProject.ok?.createdTargets.find(
t => t.name === 'production',
);
expect(target).toBeDefined();
@ -33,7 +35,7 @@ test('renaming a target should result changing its cleanId', async () => {
target: target!.cleanId,
name: 'bar',
},
access_token
access_token,
);
expect(renamedTargetResult.body.errors).not.toBeDefined();

View file

@ -16,7 +16,7 @@ test('setting no scopes equals to readonly for organization, project, target', a
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -27,7 +27,7 @@ test('setting no scopes equals to readonly for organization, project, target', a
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -44,7 +44,7 @@ test('setting no scopes equals to readonly for organization, project, target', a
projectScopes: [],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -88,7 +88,7 @@ test('cannot set a scope on a token if user has no access to that scope', async
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
// Join
@ -100,7 +100,7 @@ test('cannot set a scope on a token if user has no access to that scope', async
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -114,7 +114,7 @@ test('cannot set a scope on a token if user has no access to that scope', async
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
if (joinResult.body.data!.joinOrganization.__typename !== 'OrganizationPayload') {
@ -139,7 +139,7 @@ test('cannot set a scope on a token if user has no access to that scope', async
],
user: member.id,
},
owner_access_token
owner_access_token,
);
// member should not have access to target:registry:write
@ -153,7 +153,7 @@ test('cannot set a scope on a token if user has no access to that scope', async
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryWrite],
},
member_access_token
member_access_token,
);
expect(tokenResult.body.errors).toHaveLength(1);

View file

@ -1,4 +1,9 @@
import { TargetAccessScope, ProjectType, ProjectAccessScope, OrganizationAccessScope } from '@app/gql/graphql';
import {
TargetAccessScope,
ProjectType,
ProjectAccessScope,
OrganizationAccessScope,
} from '@app/gql/graphql';
import formatISO from 'date-fns/formatISO';
import subHours from 'date-fns/subHours';
import {
@ -16,7 +21,7 @@ import {
import { authenticate } from '../../../testkit/auth';
import { collect, CollectedOperation } from '../../../testkit/usage';
import { clickHouseQuery } from '../../../testkit/clickhouse';
// eslint-disable-next-line hive/enforce-deps-in-dev, import/no-extraneous-dependencies
// eslint-disable-next-line hive/enforce-deps-in-dev
import { normalizeOperation } from '@graphql-hive/core';
// eslint-disable-next-line import/no-extraneous-dependencies
import { parse, print } from 'graphql';
@ -49,7 +54,7 @@ test('collect operation', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -60,7 +65,7 @@ test('collect operation', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -76,7 +81,7 @@ test('collect operation', async () => {
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.Settings],
},
owner_access_token
owner_access_token,
);
const tokenResult = await createToken(
@ -87,9 +92,13 @@ test('collect operation', async () => {
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(settingsTokenResult.body.errors).not.toBeDefined();
@ -104,7 +113,7 @@ test('collect operation', async () => {
commit: 'abc123',
sdl: `type Query { ping: String me: String }`,
},
token
token,
);
expect(schemaPublishResult.body.errors).not.toBeDefined();
@ -119,7 +128,7 @@ test('collect operation', async () => {
},
{
token: tokenForSettings,
}
},
);
expect(targetValidationResult.body.errors).not.toBeDefined();
@ -132,7 +141,7 @@ test('collect operation', async () => {
{
sdl: `type Query { me: String }`,
},
token
token,
);
expect(unusedCheckResult.body.errors).not.toBeDefined();
expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckSuccess');
@ -162,11 +171,13 @@ test('collect operation', async () => {
{
sdl: `type Query { me: String }`,
},
token
token,
);
if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, got ${usedCheckResult.body.data!.schemaCheck.__typename}`);
throw new Error(
`Expected SchemaCheckError, got ${usedCheckResult.body.data!.schemaCheck.__typename}`,
);
}
expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(false);
@ -183,7 +194,7 @@ test('collect operation', async () => {
to,
},
},
token
token,
);
expect(operationStatsResult.body.errors).not.toBeDefined();
@ -212,7 +223,7 @@ test('normalize and collect operation without breaking its syntax', async () =>
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -223,7 +234,7 @@ test('normalize and collect operation without breaking its syntax', async () =>
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -239,7 +250,7 @@ test('normalize and collect operation without breaking its syntax', async () =>
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.Settings],
},
owner_access_token
owner_access_token,
);
const tokenResult = await createToken(
@ -250,9 +261,13 @@ test('normalize and collect operation without breaking its syntax', async () =>
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(settingsTokenResult.body.errors).not.toBeDefined();
@ -357,7 +372,7 @@ test('normalize and collect operation without breaking its syntax', async () =>
to,
},
},
token
token,
);
expect(operationStatsResult.body.errors).not.toBeDefined();
@ -389,7 +404,7 @@ test('number of produced and collected operations should match (no errors)', asy
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -400,7 +415,7 @@ test('number of produced and collected operations should match (no errors)', asy
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -414,9 +429,13 @@ test('number of produced and collected operations should match (no errors)', asy
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -439,7 +458,7 @@ test('number of produced and collected operations should match (no errors)', asy
errorsTotal: 0,
},
},
token
token,
);
}
@ -457,7 +476,7 @@ test('number of produced and collected operations should match (no errors)', asy
to,
},
},
token
token,
);
expect(operationStatsResult.body.errors).not.toBeDefined();
@ -487,7 +506,7 @@ test('check usage from two selected targets', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -498,7 +517,7 @@ test('check usage from two selected targets', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -510,7 +529,7 @@ test('check usage from two selected targets', async () => {
organization: org.cleanId,
project: project.cleanId,
},
owner_access_token
owner_access_token,
);
const production = productionTargetResult.body.data!.createTarget.ok!.createdTarget;
@ -523,9 +542,13 @@ test('check usage from two selected targets', async () => {
target: staging.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
const productionTokenResult = await createToken(
@ -536,9 +559,13 @@ test('check usage from two selected targets', async () => {
target: production.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(stagingTokenResult.body.errors).not.toBeDefined();
@ -553,7 +580,7 @@ test('check usage from two selected targets', async () => {
commit: 'usage-check-2',
sdl: `type Query { ping: String me: String }`,
},
tokenForStaging
tokenForStaging,
);
expect(schemaPublishResult.body.errors).not.toBeDefined();
@ -568,7 +595,7 @@ test('check usage from two selected targets', async () => {
},
{
authToken: owner_access_token,
}
},
);
expect(targetValidationResult.body.errors).not.toBeDefined();
@ -625,7 +652,7 @@ test('check usage from two selected targets', async () => {
{
sdl: `type Query { me: String }`, // ping is used but on production
},
tokenForStaging
tokenForStaging,
);
expect(unusedCheckResult.body.errors).not.toBeDefined();
expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckSuccess');
@ -643,19 +670,22 @@ test('check usage from two selected targets', async () => {
},
{
authToken: owner_access_token,
}
},
);
expect(updateValidationResult.body.errors).not.toBeDefined();
expect(updateValidationResult.body.data!.updateTargetValidationSettings.error).toBeNull();
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.percentage
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.percentage,
).toEqual(50);
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.period
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.period,
).toEqual(2);
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.targets
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.targets,
).toHaveLength(2);
// should be non-breaking because the field is used in production and we are checking staging and production now
@ -664,11 +694,13 @@ test('check usage from two selected targets', async () => {
{
sdl: `type Query { me: String }`, // ping is used on production and we do check production now
},
tokenForStaging
tokenForStaging,
);
if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckSuccess') {
throw new Error(`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`);
throw new Error(
`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`,
);
}
expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(true);
@ -681,7 +713,7 @@ test('check usage not from excluded client names', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -692,11 +724,13 @@ test('check usage not from excluded client names', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
const production = projectResult.body.data!.createProject.ok!.createdTargets.find(t => t.name === 'production');
const production = projectResult.body.data!.createProject.ok!.createdTargets.find(
t => t.name === 'production',
);
if (!production) {
throw new Error('No production target');
@ -710,9 +744,13 @@ test('check usage not from excluded client names', async () => {
target: production.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(productionTokenResult.body.errors).not.toBeDefined();
@ -725,7 +763,7 @@ test('check usage not from excluded client names', async () => {
commit: 'usage-check-2',
sdl: `type Query { ping: String me: String }`,
},
tokenForProduction
tokenForProduction,
);
expect(schemaPublishResult.body.errors).not.toBeDefined();
@ -740,7 +778,7 @@ test('check usage not from excluded client names', async () => {
},
{
authToken: owner_access_token,
}
},
);
expect(targetValidationResult.body.errors).not.toBeDefined();
@ -814,7 +852,7 @@ test('check usage not from excluded client names', async () => {
{
sdl: `type Query { ping: String }`, // Query.me is used
},
tokenForProduction
tokenForProduction,
);
expect(unusedCheckResult.body.errors).not.toBeDefined();
expect(unusedCheckResult.body.data!.schemaCheck.__typename).toEqual('SchemaCheckError');
@ -832,19 +870,22 @@ test('check usage not from excluded client names', async () => {
},
{
authToken: owner_access_token,
}
},
);
expect(updateValidationResult.body.errors).not.toBeDefined();
expect(updateValidationResult.body.data!.updateTargetValidationSettings.error).toBeNull();
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.enabled
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.enabled,
).toBe(true);
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.excludedClients
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.excludedClients,
).toHaveLength(1);
expect(
updateValidationResult.body.data!.updateTargetValidationSettings.ok!.updatedTargetValidationSettings.excludedClients
updateValidationResult.body.data!.updateTargetValidationSettings.ok!
.updatedTargetValidationSettings.excludedClients,
).toContainEqual('app');
// should be safe because the field was not used by the non-excluded clients (cli never requested `Query.me`, but app did)
@ -852,11 +893,13 @@ test('check usage not from excluded client names', async () => {
{
sdl: `type Query { ping: String }`,
},
tokenForProduction
tokenForProduction,
);
if (usedCheckResult.body.data!.schemaCheck.__typename !== 'SchemaCheckSuccess') {
throw new Error(`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`);
throw new Error(
`Expected SchemaCheckSuccess, got ${usedCheckResult.body.data!.schemaCheck.__typename}`,
);
}
expect(usedCheckResult.body.data!.schemaCheck.valid).toEqual(true);
@ -869,7 +912,7 @@ test('number of produced and collected operations should match', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -880,7 +923,7 @@ test('number of produced and collected operations should match', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -894,9 +937,13 @@ test('number of produced and collected operations should match', async () => {
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -935,7 +982,7 @@ test('number of produced and collected operations should match', async () => {
},
},
},
token
token,
);
}
@ -963,7 +1010,7 @@ test('number of produced and collected operations should match', async () => {
client_name: 'web',
hash: expect.any(String),
total: expect.stringMatching('5000'),
})
}),
);
expect(result.data).toContainEqual(
expect.objectContaining({
@ -971,7 +1018,7 @@ test('number of produced and collected operations should match', async () => {
client_name: '',
hash: expect.any(String),
total: expect.stringMatching('5000'),
})
}),
);
});
@ -981,7 +1028,7 @@ test('different order of schema coordinates should not result in different hash'
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -992,7 +1039,7 @@ test('different order of schema coordinates should not result in different hash'
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1006,9 +1053,13 @@ test('different order of schema coordinates should not result in different hash'
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1064,7 +1115,7 @@ test('different order of schema coordinates should not result in different hash'
}>(
FF_CLICKHOUSE_V2_TABLES
? `SELECT hash FROM operation_collection GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`,
);
expect(operationCollectionResult.rows).toEqual(1);
@ -1076,7 +1127,7 @@ test('same operation but with different schema coordinates should result in diff
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1087,7 +1138,7 @@ test('same operation but with different schema coordinates should result in diff
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1101,9 +1152,13 @@ test('same operation but with different schema coordinates should result in diff
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1154,7 +1209,7 @@ test('same operation but with different schema coordinates should result in diff
}>(
FF_CLICKHOUSE_V2_TABLES
? `SELECT hash FROM operation_collection GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`,
);
expect(operationCollectionResult.rows).toEqual(2);
@ -1167,7 +1222,7 @@ test('same operation but with different schema coordinates should result in diff
}>(
FF_CLICKHOUSE_V2_TABLES
? `SELECT hash FROM operation_collection GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`,
);
expect(operationsResult.rows).toEqual(2);
@ -1179,7 +1234,7 @@ test('operations with the same schema coordinates and body but with different na
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1190,7 +1245,7 @@ test('operations with the same schema coordinates and body but with different na
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1204,9 +1259,13 @@ test('operations with the same schema coordinates and body but with different na
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1262,7 +1321,7 @@ test('operations with the same schema coordinates and body but with different na
}>(
FF_CLICKHOUSE_V2_TABLES
? `SELECT hash FROM operation_collection GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`,
);
expect(operationsResult.rows).toEqual(2);
@ -1274,7 +1333,7 @@ test('ignore operations with syntax errors', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1285,7 +1344,7 @@ test('ignore operations with syntax errors', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1299,9 +1358,13 @@ test('ignore operations with syntax errors', async () => {
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1339,7 +1402,7 @@ test('ignore operations with syntax errors', async () => {
expect.objectContaining({
rejected: 1,
accepted: 1,
})
}),
);
await waitFor(5_000);
@ -1365,7 +1428,7 @@ test('ignore operations with syntax errors', async () => {
}>(
FF_CLICKHOUSE_V2_TABLES
? `SELECT hash FROM operation_collection GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`
: `SELECT hash FROM operations_registry FINAL GROUP BY hash`,
);
expect(operationsResult.rows).toEqual(1);
@ -1377,7 +1440,7 @@ test('ensure correct data', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -1388,7 +1451,7 @@ test('ensure correct data', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -1402,9 +1465,13 @@ test('ensure correct data', async () => {
target: target.cleanId,
organizationScopes: [OrganizationAccessScope.Read],
projectScopes: [ProjectAccessScope.Read],
targetScopes: [TargetAccessScope.Read, TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
targetScopes: [
TargetAccessScope.Read,
TargetAccessScope.RegistryRead,
TargetAccessScope.RegistryWrite,
],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -1637,7 +1704,9 @@ test('ensure correct data', async () => {
expect(dailyAggOfKnownClient.hash).toHaveLength(32);
expect(dailyAggOfKnownClient.target).toEqual(target.id);
const dailyAggOfUnknownClient = clientsDailyResult.data.find(c => c.client_name !== 'test-name')!;
const dailyAggOfUnknownClient = clientsDailyResult.data.find(
c => c.client_name !== 'test-name',
)!;
expect(dailyAggOfUnknownClient).toBeDefined();
expect(ensureNumber(dailyAggOfUnknownClient.total)).toEqual(1);
expect(dailyAggOfUnknownClient.client_version).toHaveLength(0);
@ -1799,7 +1868,9 @@ test('ensure correct data', async () => {
expect(dailyAggOfKnownClient.hash).toHaveLength(32);
expect(dailyAggOfKnownClient.target).toEqual(target.id);
const dailyAggOfUnknownClient = clientsDailyResult.data.find(c => c.client_name !== 'test-name')!;
const dailyAggOfUnknownClient = clientsDailyResult.data.find(
c => c.client_name !== 'test-name',
)!;
expect(dailyAggOfUnknownClient).toBeDefined();
expect(ensureNumber(dailyAggOfUnknownClient.total)).toEqual(1);
expect(dailyAggOfUnknownClient.hash).toHaveLength(32);

View file

@ -1,5 +1,11 @@
import { ProjectType } from '@app/gql/graphql';
import { createOrganization, createProject, createToken, readTokenInfo, deleteTokens } from '../../testkit/flow';
import {
createOrganization,
createProject,
createToken,
readTokenInfo,
deleteTokens,
} from '../../testkit/flow';
import { authenticate } from '../../testkit/auth';
test('deleting a token should clear the cache', async () => {
@ -8,7 +14,7 @@ test('deleting a token should clear the cache', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -19,7 +25,7 @@ test('deleting a token should clear the cache', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -36,7 +42,7 @@ test('deleting a token should clear the cache', async () => {
projectScopes: [],
targetScopes: [],
},
owner_access_token
owner_access_token,
);
expect(tokenResult.body.errors).not.toBeDefined();
@ -83,7 +89,7 @@ test('deleting a token should clear the cache', async () => {
target: target.cleanId,
tokens: [createdToken.id],
},
owner_access_token
owner_access_token,
);
tokenInfoResult = await readTokenInfo(secret!);

View file

@ -17,7 +17,7 @@ test('can publish and check a schema with target:registry:read access', async ()
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -26,7 +26,7 @@ test('can publish and check a schema with target:registry:read access', async ()
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -42,7 +42,7 @@ test('can publish and check a schema with target:registry:read access', async ()
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -59,7 +59,7 @@ test('can publish and check a schema with target:registry:read access', async ()
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -76,9 +76,9 @@ test('can publish and check a schema with target:registry:read access', async ()
await schemaCheck(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']);
await expect(schemaCheck(['--token', writeToken, 'fixtures/breaking-schema.graphql'])).rejects.toThrowError(
/breaking/
);
await expect(
schemaCheck(['--token', writeToken, 'fixtures/breaking-schema.graphql']),
).rejects.toThrowError(/breaking/);
});
test('publishing a breaking change results in invalid state', async () => {
@ -87,7 +87,7 @@ test('publishing a breaking change results in invalid state', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -96,7 +96,7 @@ test('publishing a breaking change results in invalid state', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -112,7 +112,7 @@ test('publishing a breaking change results in invalid state', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -129,7 +129,7 @@ test('publishing a breaking change results in invalid state', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -144,9 +144,9 @@ test('publishing a breaking change results in invalid state', async () => {
'fixtures/init-schema.graphql',
]);
await expect(schemaPublish(['--token', writeToken, 'fixtures/breaking-schema.graphql'])).rejects.toThrowError(
/breaking/
);
await expect(
schemaPublish(['--token', writeToken, 'fixtures/breaking-schema.graphql']),
).rejects.toThrowError(/breaking/);
});
test('publishing invalid schema SDL provides meaningful feedback for the user.', async () => {
@ -155,7 +155,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.',
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -164,7 +164,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.',
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -180,7 +180,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.',
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -197,7 +197,7 @@ test('publishing invalid schema SDL provides meaningful feedback for the user.',
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -229,7 +229,7 @@ test('service url should be available in supergraph', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -238,7 +238,7 @@ test('service url should be available in supergraph', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -254,7 +254,7 @@ test('service url should be available in supergraph', async () => {
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -271,7 +271,7 @@ test('service url should be available in supergraph', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -296,7 +296,7 @@ test('service url should be available in supergraph', async () => {
project: project.cleanId,
target: target.cleanId,
},
writeToken
writeToken,
);
expect(supergraph.body).toMatch('(name: "users" url: "https://api.com/users-subgraph")');
@ -308,7 +308,7 @@ test('service url should be required in Federation', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -317,7 +317,7 @@ test('service url should be required in Federation', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -333,7 +333,7 @@ test('service url should be required in Federation', async () => {
type: ProjectType.Federation,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -350,7 +350,7 @@ test('service url should be required in Federation', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -366,7 +366,7 @@ test('service url should be required in Federation', async () => {
'--service',
'users',
'fixtures/federation-init.graphql',
])
]),
).rejects.toThrowError(/url/);
});
@ -376,7 +376,7 @@ test('schema:publish should print a link to the website', async () => {
{
name: 'bar',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -385,7 +385,7 @@ test('schema:publish should print a link to the website', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -401,7 +401,7 @@ test('schema:publish should print a link to the website', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -419,18 +419,18 @@ test('schema:publish should print a link to the website', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
await expect(schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch(
'Available at https://app.graphql-hive.com/bar/foo/development'
);
await expect(
schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']),
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development');
await expect(schemaPublish(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql'])).resolves.toMatch(
'Available at https://app.graphql-hive.com/bar/foo/development/history/'
);
await expect(
schemaPublish(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']),
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/');
});
test('schema:check should notify user when registry is empty', async () => {
@ -439,7 +439,7 @@ test('schema:check should notify user when registry is empty', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -448,7 +448,7 @@ test('schema:check should notify user when registry is empty', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -464,7 +464,7 @@ test('schema:check should notify user when registry is empty', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -481,12 +481,14 @@ test('schema:check should notify user when registry is empty', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
await expect(schemaCheck(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch('empty');
await expect(
schemaCheck(['--token', writeToken, 'fixtures/init-schema.graphql']),
).resolves.toMatch('empty');
});
test('schema:check should throw on corrupted schema', async () => {
@ -495,7 +497,7 @@ test('schema:check should throw on corrupted schema', async () => {
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -504,7 +506,7 @@ test('schema:check should throw on corrupted schema', async () => {
email: 'some@email.com',
organization: org.cleanId,
},
owner_access_token
owner_access_token,
);
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
@ -520,7 +522,7 @@ test('schema:check should throw on corrupted schema', async () => {
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -537,7 +539,7 @@ test('schema:check should throw on corrupted schema', async () => {
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
@ -553,7 +555,7 @@ test('schema:publish should see Invalid Token error when token is invalid', asyn
{
name: 'foo',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
@ -563,7 +565,7 @@ test('schema:publish should see Invalid Token error when token is invalid', asyn
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const token = createHash('md5').update('nope').digest('hex').substring(0, 31);
@ -579,7 +581,7 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as
{
name: 'bar',
},
owner_access_token
owner_access_token,
);
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
const projectResult = await createProject(
@ -588,7 +590,7 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as
type: ProjectType.Single,
name: 'foo',
},
owner_access_token
owner_access_token,
);
const project = projectResult.body.data!.createProject.ok!.createdProject;
@ -606,16 +608,21 @@ test('schema:publish should support experimental_acceptBreakingChanges flag', as
projectScopes: [],
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
},
owner_access_token
owner_access_token,
);
expect(writeTokenResult.body.errors).not.toBeDefined();
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
await expect(schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql'])).resolves.toMatch(
'Available at https://app.graphql-hive.com/bar/foo/development'
);
await expect(
schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']),
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development');
await expect(
schemaPublish(['--token', writeToken, '--experimental_acceptBreakingChanges', 'fixtures/breaking-schema.graphql'])
schemaPublish([
'--token',
writeToken,
'--experimental_acceptBreakingChanges',
'fixtures/breaking-schema.graphql',
]),
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/');
});

View file

@ -1,7 +1,7 @@
{
"name": "graphql-hive",
"version": "0.0.0",
"type": "module",
"private": true,
"repository": {
"type": "git",
"url": "kamilkisiela/graphql-hive"
@ -12,29 +12,35 @@
"url": "https://the-guild.dev"
},
"license": "MIT",
"private": true,
"packageManager": "pnpm@7.14.2",
"engines": {
"node": ">=16",
"pnpm": ">=7"
},
"scripts": {
"seed": "node scripts/seed-local-env.js",
"build": "pnpm graphql:generate && pnpm turbo build --color",
"build:libraries": "pnpm graphql:generate && pnpm turbo build --filter=./packages/libraries/* --color",
"build:local": "pnpm graphql:generate && pnpm turbo build-local --color",
"build:services": "pnpm graphql:generate && pnpm turbo build --filter=./packages/services/* --color",
"build:web": "pnpm graphql:generate && pnpm turbo build --filter=./packages/web/* --color",
"docker:build": "docker buildx bake -f docker.hcl --load build",
"env:sync": "node ./scripts/sync-env-files.js",
"generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate",
"graphql:generate": "graphql-codegen",
"lint": "eslint --cache --ignore-path .gitignore \"packages/**/*.{ts,tsx}\"",
"lint:prettier": "prettier --cache --check .",
"postinstall": "husky install && node ./scripts/patch-manifests.js && pnpm env:sync && node ./scripts/turborepo-cleanup.js && node ./scripts/turborepo-setup.js",
"pre-commit": "lint-staged",
"prerelease": "pnpm build:libraries",
"release": "changeset publish",
"upload-sourcemaps": "./scripts/upload-sourcemaps.sh",
"test": "jest",
"lint": "eslint --cache --ignore-path .gitignore \"packages/**/*.{ts,tsx}\"",
"prettier": "prettier --cache --write --list-different .",
"lint:prettier": "prettier --cache --check .",
"release": "changeset publish",
"seed": "node scripts/seed-local-env.js",
"setup": "pnpm --filter @hive/storage setup",
"generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate",
"graphql:generate": "graphql-codegen",
"typecheck": "pnpm turbo typecheck --color",
"build": "pnpm graphql:generate && pnpm turbo build --color",
"build:libraries": "pnpm graphql:generate && pnpm turbo build --filter=./packages/libraries/* --color",
"build:web": "pnpm graphql:generate && pnpm turbo build --filter=./packages/web/* --color",
"build:services": "pnpm graphql:generate && pnpm turbo build --filter=./packages/services/* --color",
"build:local": "pnpm graphql:generate && pnpm turbo build-local --color",
"env:sync": "node ./scripts/sync-env-files.js",
"test": "jest",
"turbo": "env-cmd --silent turbo run",
"docker:build": "docker buildx bake -f docker.hcl --load build"
"typecheck": "pnpm turbo typecheck --color",
"upload-sourcemaps": "./scripts/upload-sourcemaps.sh"
},
"devDependencies": {
"@babel/core": "7.19.1",
@ -55,7 +61,7 @@
"@graphql-codegen/typescript-resolvers": "2.7.5",
"@sentry/cli": "2.9.0",
"@swc/core": "1.2.185",
"@theguild/prettier-config": "0.1.1",
"@theguild/prettier-config": "1.0.0",
"@types/jest": "29.2.0",
"@types/lru-cache": "7.6.1",
"@types/node": "16.11.22",
@ -106,11 +112,6 @@
"eslint --fix"
]
},
"engines": {
"node": ">=16",
"pnpm": ">=7"
},
"packageManager": "pnpm@7.14.2",
"pnpm": {
"patchedDependencies": {
"@n1ru4l/dockest@2.1.0-rc.6": "patches/@n1ru4l__dockest@2.1.0-rc.6.patch",
@ -123,6 +124,5 @@
"bob-the-bundler@4.0.0": "patches/bob-the-bundler@4.0.0.patch",
"oclif@3.2.25": "patches/oclif@3.2.25.patch"
}
},
"version": "0.0.0"
}
}

View file

@ -4,76 +4,102 @@
### Patch Changes
- Updated dependencies [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]:
- Updated dependencies
[[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]:
- @graphql-hive/core@0.2.3
## 0.19.9
### Patch Changes
- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests
- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655)
[`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2)
Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests
## 0.19.8
### Patch Changes
- [#648](https://github.com/kamilkisiela/graphql-hive/pull/648) [`84a78fc`](https://github.com/kamilkisiela/graphql-hive/commit/84a78fc2a4061e05b1bbe4a8d11006601c767384) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump
- [#648](https://github.com/kamilkisiela/graphql-hive/pull/648)
[`84a78fc`](https://github.com/kamilkisiela/graphql-hive/commit/84a78fc2a4061e05b1bbe4a8d11006601c767384)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump
## 0.19.7
### Patch Changes
- [#646](https://github.com/kamilkisiela/graphql-hive/pull/646) [`65f3372`](https://github.com/kamilkisiela/graphql-hive/commit/65f3372dfa047238352beee113ccb8506cc180ca) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - I hope it's final bump
- [#646](https://github.com/kamilkisiela/graphql-hive/pull/646)
[`65f3372`](https://github.com/kamilkisiela/graphql-hive/commit/65f3372dfa047238352beee113ccb8506cc180ca)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - I hope it's final bump
## 0.19.6
### Patch Changes
- [#645](https://github.com/kamilkisiela/graphql-hive/pull/645) [`7110555`](https://github.com/kamilkisiela/graphql-hive/commit/71105559b67f510087223ada2af23564ff053353) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Ignore npm-shrinkwrap.json
- [#645](https://github.com/kamilkisiela/graphql-hive/pull/645)
[`7110555`](https://github.com/kamilkisiela/graphql-hive/commit/71105559b67f510087223ada2af23564ff053353)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Ignore npm-shrinkwrap.json
## 0.19.5
### Patch Changes
- [#641](https://github.com/kamilkisiela/graphql-hive/pull/641) [`ce55b72`](https://github.com/kamilkisiela/graphql-hive/commit/ce55b724b00ff7fc93f3df4089e698e6f9d5086b) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Do not include npm-shrinkwrap.json
- [#641](https://github.com/kamilkisiela/graphql-hive/pull/641)
[`ce55b72`](https://github.com/kamilkisiela/graphql-hive/commit/ce55b724b00ff7fc93f3df4089e698e6f9d5086b)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Do not include npm-shrinkwrap.json
## 0.19.4
### Patch Changes
- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump
- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631)
[`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump
- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631) [`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump CLI
- [#631](https://github.com/kamilkisiela/graphql-hive/pull/631)
[`d4ca981`](https://github.com/kamilkisiela/graphql-hive/commit/d4ca98180bd0b2910fb41f623c2f5abb1f4b9214)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump CLI
## 0.19.3
### Patch Changes
- [#629](https://github.com/kamilkisiela/graphql-hive/pull/629) [`750b46d`](https://github.com/kamilkisiela/graphql-hive/commit/750b46d155c5d01ad4b3cee84409793736246603) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump
- [#629](https://github.com/kamilkisiela/graphql-hive/pull/629)
[`750b46d`](https://github.com/kamilkisiela/graphql-hive/commit/750b46d155c5d01ad4b3cee84409793736246603)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump
## 0.19.2
### Patch Changes
- [#627](https://github.com/kamilkisiela/graphql-hive/pull/627) [`78096dc`](https://github.com/kamilkisiela/graphql-hive/commit/78096dcfbd37059fbb309e8faa6bae1d14e18c79) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump
- [#627](https://github.com/kamilkisiela/graphql-hive/pull/627)
[`78096dc`](https://github.com/kamilkisiela/graphql-hive/commit/78096dcfbd37059fbb309e8faa6bae1d14e18c79)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - bump
## 0.19.1
### Patch Changes
- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages
- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466)
[`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c)
Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages
## 0.19.0
### Minor Changes
- [#357](https://github.com/kamilkisiela/graphql-hive/pull/357) [`30f11c4`](https://github.com/kamilkisiela/graphql-hive/commit/30f11c40054debfcbd8b6090316d129eb7851046) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Adds experimental_acceptBreakingChanges
- [#357](https://github.com/kamilkisiela/graphql-hive/pull/357)
[`30f11c4`](https://github.com/kamilkisiela/graphql-hive/commit/30f11c40054debfcbd8b6090316d129eb7851046)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Adds experimental_acceptBreakingChanges
## 0.18.2
### Patch Changes
- [#292](https://github.com/kamilkisiela/graphql-hive/pull/292) [`efb03e1`](https://github.com/kamilkisiela/graphql-hive/commit/efb03e184d5a878dbcca83295b2d1d53b3c9f8e3) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump @oclif/core dependency range to ^1.13.10
- [#292](https://github.com/kamilkisiela/graphql-hive/pull/292)
[`efb03e1`](https://github.com/kamilkisiela/graphql-hive/commit/efb03e184d5a878dbcca83295b2d1d53b3c9f8e3)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Bump @oclif/core dependency range to
^1.13.10
## 0.18.1
@ -187,9 +213,11 @@
- c5bfa4c9: Add a new `metadata` flag for publishing schema metadata (JSON) to Hive.
The `--metadata` can contain anything you wish to have attached to your GraphQL schema, and can support your runtime needs.
The `--metadata` can contain anything you wish to have attached to your GraphQL schema, and can
support your runtime needs.
You can either specify a path to a file: `--metadata my-file.json`, or an inline JSON object: `--metadata '{"test": 1}'`.
You can either specify a path to a file: `--metadata my-file.json`, or an inline JSON object:
`--metadata '{"test": 1}'`.
Metadata published to Hive will be available as part of Hive CDN, under `/metadata` route.
@ -403,7 +431,8 @@
### Minor Changes
- 078e758: Token per Target
- 1dd9cdb: --file flag is now replaced with a positional arg at the end, comments in graphql sdl files are now converted to descriptions, docs are updated to mention wildcard file uploads
- 1dd9cdb: --file flag is now replaced with a positional arg at the end, comments in graphql sdl
files are now converted to descriptions, docs are updated to mention wildcard file uploads
### Patch Changes

View file

@ -63,7 +63,8 @@ DESCRIPTION
deletes specific cli configuration
```
_See code: [dist/commands/config/delete.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/delete.js)_
_See code:
[dist/commands/config/delete.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/delete.js)_
## `hive config:get KEY`
@ -80,7 +81,8 @@ DESCRIPTION
prints specific cli configuration
```
_See code: [dist/commands/config/get.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/get.js)_
_See code:
[dist/commands/config/get.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/get.js)_
## `hive config:reset`
@ -94,7 +96,8 @@ DESCRIPTION
resets local cli configuration
```
_See code: [dist/commands/config/reset.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/reset.js)_
_See code:
[dist/commands/config/reset.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/reset.js)_
## `hive config:set KEY VALUE`
@ -112,7 +115,8 @@ DESCRIPTION
updates specific cli configuration
```
_See code: [dist/commands/config/set.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/set.js)_
_See code:
[dist/commands/config/set.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/config/set.js)_
## `hive help [COMMAND]`
@ -132,7 +136,8 @@ DESCRIPTION
Display help for hive.
```
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.18/src/commands/help.ts)_
_See code:
[@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.18/src/commands/help.ts)_
## `hive operations:check FILE`
@ -154,7 +159,8 @@ DESCRIPTION
checks operations against a published schema
```
_See code: [dist/commands/operations/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/check.js)_
_See code:
[dist/commands/operations/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/check.js)_
## `hive operations:publish FILE`
@ -177,7 +183,8 @@ DESCRIPTION
saves operations to the store
```
_See code: [dist/commands/operations/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/publish.js)_
_See code:
[dist/commands/operations/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/operations/publish.js)_
## `hive schema:check FILE`
@ -204,7 +211,8 @@ DESCRIPTION
checks schema
```
_See code: [dist/commands/schema/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/check.js)_
_See code:
[dist/commands/schema/check.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/check.js)_
## `hive schema:publish FILE`
@ -239,7 +247,8 @@ DESCRIPTION
publishes schema
```
_See code: [dist/commands/schema/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/publish.js)_
_See code:
[dist/commands/schema/publish.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/schema/publish.js)_
## `hive update [CHANNEL]`
@ -276,7 +285,8 @@ EXAMPLES
$ hive update --available
```
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v3.0.6/src/commands/update.ts)_
_See code:
[@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v3.0.6/src/commands/update.ts)_
## `hive whoami`
@ -294,7 +304,8 @@ DESCRIPTION
checks schema
```
_See code: [dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/whoami.js)_
_See code:
[dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hive/blob/v0.19.5/dist/commands/whoami.js)_
<!-- commandsstop -->
@ -302,7 +313,8 @@ _See code: [dist/commands/whoami.js](https://github.com/kamilkisiela/graphql-hiv
# Config
In addition to using the CLI args, you can also define your configuration in a JSON file which the CLI would pick up when it runs.
In addition to using the CLI args, you can also define your configuration in a JSON file which the
CLI would pick up when it runs.
You can use the `HIVE_CONFIG` environment variable to define the path to the JSON file as follows:

View file

@ -1,22 +1,22 @@
{
"name": "@graphql-hive/cli",
"description": "A CLI util to manage and control your GraphQL Hive",
"version": "0.19.10",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"description": "A CLI util to manage and control your GraphQL Hive",
"repository": {
"type": "git",
"url": "kamilkisiela/graphql-hive",
"directory": "packages/libraries/cli"
},
"homepage": "https://graphql-hive.com",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"license": "MIT",
"keywords": [
"graphql"
],
"engines": {
"node": ">=14.0.0"
},
"bin": {
"hive": "./bin/run"
},
@ -26,19 +26,22 @@
"/dist",
"/oclif.manifest.json"
],
"keywords": [
"graphql"
],
"scripts": {
"start": "./bin/dev",
"prebuild": "rimraf dist",
"build": "tsc",
"postpack": "rm -f oclif.manifest.json",
"prepack": "rimraf lib && tsc -b && oclif manifest && oclif readme",
"oclif:pack": "npm pack && pnpm oclif pack tarballs --no-xz",
"oclif:upload": "pnpm oclif upload tarballs --no-xz",
"version": "oclif readme && git add README.md",
"postpack": "rm -f oclif.manifest.json",
"prebuild": "rimraf dist",
"prepack": "rimraf lib && tsc -b && oclif manifest && oclif readme",
"schema:check:federation": "pnpm start schema:check examples/federation.graphql --service reviews",
"schema:check:single": "pnpm start schema:check examples/single.graphql",
"schema:check:stitching": "pnpm start schema:check examples/stitching.graphql --service posts",
"schema:check:federation": "pnpm start schema:check examples/federation.graphql --service reviews",
"schema:publish:federation": "pnpm start schema:publish --service reviews examples/federation.reviews.graphql"
"schema:publish:federation": "pnpm start schema:publish --service reviews examples/federation.reviews.graphql",
"start": "./bin/dev",
"version": "oclif readme && git add README.md"
},
"dependencies": {
"@graphql-hive/core": "0.2.3",
@ -46,8 +49,8 @@
"@graphql-tools/code-file-loader": "~7.3.6",
"@graphql-tools/graphql-file-loader": "~7.5.5",
"@graphql-tools/json-file-loader": "~7.4.6",
"@graphql-tools/url-loader": "~7.16.4",
"@graphql-tools/load": "~7.7.7",
"@graphql-tools/url-loader": "~7.16.4",
"@graphql-tools/utils": "8.12.0",
"@oclif/core": "^1.20.4",
"@oclif/plugin-help": "5.1.19",
@ -61,14 +64,18 @@
"log-symbols": "4.1.0",
"mkdirp": "1.0.4",
"rimraf": "3.0.2",
"tslib": "2.4.1",
"ts-node": "10.9.1"
"ts-node": "10.9.1",
"tslib": "2.4.1"
},
"devDependencies": {
"oclif": "^3.2.25",
"@types/env-ci": "3.1.1",
"@types/git-parse": "2.1.2",
"@types/mkdirp": "1.0.2"
"@types/mkdirp": "1.0.2",
"oclif": "^3.2.25"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"oclif": {
"commands": "./dist/commands",
@ -82,12 +89,5 @@
"bucket": "graphql-hive-cli"
}
}
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"engines": {
"node": ">=14.0.0"
}
}

View file

@ -54,7 +54,7 @@ export default abstract class extends Command {
TArgs extends {
[key: string]: any;
},
TKey extends keyof TArgs
TKey extends keyof TArgs,
>({
key,
args,
@ -104,7 +104,7 @@ export default abstract class extends Command {
TArgs extends {
[key: string]: any;
},
TKey extends keyof TArgs
TKey extends keyof TArgs,
>(key: TKey, args: TArgs): TArgs[TKey] | undefined {
if (args[key]) {
return args[key];
@ -129,7 +129,7 @@ export default abstract class extends Command {
'graphql-client-name': 'Hive CLI',
'graphql-client-version': this.config.version,
},
})
}),
);
}
@ -163,10 +163,12 @@ export default abstract class extends Command {
TFlags extends {
require: string[];
[key: string]: any;
}
},
>(flags: TFlags) {
if (flags.require && flags.require.length > 0) {
await Promise.all(flags.require.map(mod => import(require.resolve(mod, { paths: [process.cwd()] }))));
await Promise.all(
flags.require.map(mod => import(require.resolve(mod, { paths: [process.cwd()] }))),
);
}
}
}

View file

@ -74,7 +74,7 @@ export default class OperationsCheck extends Command {
const invalidOperations = validate(
schema,
operations.map(s => new Source(s.content, s.location))
operations.map(s => new Source(s.content, s.location)),
);
if (invalidOperations.length === 0) {
@ -85,7 +85,9 @@ export default class OperationsCheck extends Command {
this.fail('Some operations are invalid');
this.log(['', `Total: ${operations.length}`, `Invalid: ${invalidOperations.length}`, ''].join('\n'));
this.log(
['', `Total: ${operations.length}`, `Invalid: ${invalidOperations.length}`, ''].join('\n'),
);
this.printInvalidDocuments(invalidOperations, 'errors');
} catch (error) {
@ -98,7 +100,10 @@ export default class OperationsCheck extends Command {
}
}
private printInvalidDocuments(invalidDocuments: InvalidDocument[], listKey: 'errors' | 'deprecated'): void {
private printInvalidDocuments(
invalidDocuments: InvalidDocument[],
listKey: 'errors' | 'deprecated',
): void {
invalidDocuments.forEach(doc => {
if (doc.errors.length) {
this.renderErrors(doc.source.name, doc[listKey]).forEach(line => {

View file

@ -13,7 +13,8 @@ export default class OperationsPublish extends Command {
description: 'api token',
}),
require: Flags.string({
description: 'Loads specific require.extensions before running the codegen and reading the configuration',
description:
'Loads specific require.extensions before running the codegen and reading the configuration',
default: [],
multiple: true,
}),
@ -54,9 +55,11 @@ export default class OperationsPublish extends Command {
const noMissingHashes = operations.some(op => !!op.operationHash);
if (noMissingHashes) {
const comparisonResult = await this.registryApi(registry, token).comparePersistedOperations({
hashes: operations.map(op => op.operationHash!),
});
const comparisonResult = await this.registryApi(registry, token).comparePersistedOperations(
{
hashes: operations.map(op => op.operationHash!),
},
);
const operationsToPublish = comparisonResult.comparePersistedOperations;
@ -67,9 +70,13 @@ export default class OperationsPublish extends Command {
if (!operations.length) {
return this.success(
[`Nothing to publish`, '', ` Total: ${collectedOperationsTotal}`, ` Unchanged: ${unchangedTotal}`, ''].join(
'\n'
)
[
`Nothing to publish`,
'',
` Total: ${collectedOperationsTotal}`,
` Unchanged: ${unchangedTotal}`,
'',
].join('\n'),
);
}
@ -86,7 +93,7 @@ export default class OperationsPublish extends Command {
` Total: ${summary.total}`,
` Unchanged: ${summary.unchanged}`,
'',
].join('\n')
].join('\n'),
);
} else {
this.error('OOPS! An error occurred in publishing the operation(s)');

View file

@ -25,7 +25,8 @@ export default class SchemaCheck extends Command {
default: false,
}),
require: Flags.string({
description: 'Loads specific require.extensions before running the codegen and reading the configuration',
description:
'Loads specific require.extensions before running the codegen and reading the configuration',
default: [],
multiple: true,
}),
@ -70,7 +71,10 @@ export default class SchemaCheck extends Command {
invariant(typeof sdl === 'string' && sdl.length > 0, 'Schema seems empty');
if (usesGitHubApp) {
invariant(typeof commit === 'string', `Couldn't resolve commit sha required for GitHub Application`);
invariant(
typeof commit === 'string',
`Couldn't resolve commit sha required for GitHub Application`,
);
}
const result = await this.registryApi(registry, token).schemaCheck({

View file

@ -42,10 +42,12 @@ export default class SchemaPublish extends Command {
default: false,
}),
experimental_acceptBreakingChanges: Flags.boolean({
description: '(experimental) accept breaking changes and mark schema as valid (only if composable)',
description:
'(experimental) accept breaking changes and mark schema as valid (only if composable)',
}),
require: Flags.string({
description: 'Loads specific require.extensions before running the codegen and reading the configuration',
description:
'Loads specific require.extensions before running the codegen and reading the configuration',
default: [],
multiple: true,
}),
@ -76,7 +78,7 @@ export default class SchemaPublish extends Command {
if (!exists) {
throw new Error(
`Failed to load metadata from "${metadata}": Please specify a path to an existing file, or a string with valid JSON.`
`Failed to load metadata from "${metadata}": Please specify a path to an existing file, or a string with valid JSON.`,
);
}
@ -87,7 +89,7 @@ export default class SchemaPublish extends Command {
return fileContent;
} catch (e) {
throw new Error(
`Failed to load metadata from file "${metadata}": Please make sure the file is readable and contains a valid JSON`
`Failed to load metadata from file "${metadata}": Please make sure the file is readable and contains a valid JSON`,
);
}
}
@ -114,7 +116,10 @@ export default class SchemaPublish extends Command {
env: 'HIVE_TOKEN',
});
const force = this.maybe('force', flags);
const experimental_acceptBreakingChanges = this.maybe('experimental_acceptBreakingChanges', flags);
const experimental_acceptBreakingChanges = this.maybe(
'experimental_acceptBreakingChanges',
flags,
);
const metadata = this.resolveMetadata(this.maybe('metadata', flags));
const usesGitHubApp = this.maybe('github', flags) === true;
@ -152,7 +157,9 @@ export default class SchemaPublish extends Command {
} catch (err) {
if (err instanceof GraphQLError) {
const location = err.locations?.[0];
const locationString = location ? ` at line ${location.line}, column ${location.column}` : '';
const locationString = location
? ` at line ${location.line}, column ${location.column}`
: '';
throw new Error(`The SDL is not valid${locationString}:\n ${err.message}`);
}
throw err;
@ -191,10 +198,14 @@ export default class SchemaPublish extends Command {
this.info(`Available at ${result.schemaPublish.linkToWebsite}`);
}
} else if (result.schemaPublish.__typename === 'SchemaPublishMissingServiceError') {
this.fail(`${result.schemaPublish.missingServiceError} Please use the '--service <name>' parameter.`);
this.fail(
`${result.schemaPublish.missingServiceError} Please use the '--service <name>' parameter.`,
);
this.exit(1);
} else if (result.schemaPublish.__typename === 'SchemaPublishMissingUrlError') {
this.fail(`${result.schemaPublish.missingUrlError} Please use the '--url <url>' parameter.`);
this.fail(
`${result.schemaPublish.missingUrlError} Please use the '--url <url>' parameter.`,
);
this.exit(1);
} else if (result.schemaPublish.__typename === 'SchemaPublishError') {
const changes = result.schemaPublish.changes;
@ -220,7 +231,9 @@ export default class SchemaPublish extends Command {
} else if (result.schemaPublish.__typename === 'GitHubSchemaPublishSuccess') {
this.success(result.schemaPublish.message);
} else {
this.error('message' in result.schemaPublish ? result.schemaPublish.message : 'Unknown error');
this.error(
'message' in result.schemaPublish ? result.schemaPublish.message : 'Unknown error',
);
}
} catch (error) {
if (error instanceof Errors.ExitError) {

View file

@ -42,7 +42,9 @@ function useGitHubAction(): CIRunner {
env() {
const isPr =
// eslint-disable-next-line no-process-env
process.env.GITHUB_EVENT_NAME === 'pull_request' || process.env.GITHUB_EVENT_NAME === 'pull_request_target';
process.env.GITHUB_EVENT_NAME === 'pull_request' ||
// eslint-disable-next-line no-process-env
process.env.GITHUB_EVENT_NAME === 'pull_request_target';
if (isPr) {
try {
@ -85,7 +87,8 @@ export async function gitInfo(noGit: () => void) {
if (!commit || !author) {
const rootFromEnv = 'root' in env ? env.root : null;
const git = rootFromEnv ?? findParentDir(__dirname, '.git') ?? findParentDir(process.cwd(), '.git');
const git =
rootFromEnv ?? findParentDir(__dirname, '.git') ?? findParentDir(process.cwd(), '.git');
if (git) {
const commits = await gitToJs(git);

View file

@ -10,7 +10,7 @@ export async function loadOperations(
file: string,
options?: {
normalize?: boolean;
}
},
): Promise<
Array<{
operationHash?: string;
@ -24,7 +24,7 @@ export async function loadOperations(
const output: Record<string, string> = JSON.parse(
await fs.readFile(file, {
encoding: 'utf-8',
})
}),
);
const operations: Array<{

View file

@ -4,64 +4,90 @@
### Patch Changes
- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue
- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668)
[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue
- Updated dependencies [[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]:
- Updated dependencies
[[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)]:
- @graphql-hive/core@0.2.3
## 0.21.2
### Patch Changes
- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655) [`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2) Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests
- [#655](https://github.com/kamilkisiela/graphql-hive/pull/655)
[`2cbf27f`](https://github.com/kamilkisiela/graphql-hive/commit/2cbf27fdc9c18749b8969adb6d1598338762dba2)
Thanks [@n1ru4l](https://github.com/n1ru4l)! - Add User-Agent header to all http requests
## 0.21.1
### Patch Changes
- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466) [`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c) Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages
- [#466](https://github.com/kamilkisiela/graphql-hive/pull/466)
[`2e036ac`](https://github.com/kamilkisiela/graphql-hive/commit/2e036acc4ce1c27a493e90481bb10f5886c0a00c)
Thanks [@ardatan](https://github.com/ardatan)! - Update GraphQL Tools packages
## 0.21.0
### Minor Changes
- [#563](https://github.com/kamilkisiela/graphql-hive/pull/563) [`d58a470`](https://github.com/kamilkisiela/graphql-hive/commit/d58a470916b213230f495e896fe99ec0baa225e2) Thanks [@PabloSzx](https://github.com/PabloSzx)! - Fix createServicesFetcher handling null service url
- [#563](https://github.com/kamilkisiela/graphql-hive/pull/563)
[`d58a470`](https://github.com/kamilkisiela/graphql-hive/commit/d58a470916b213230f495e896fe99ec0baa225e2)
Thanks [@PabloSzx](https://github.com/PabloSzx)! - Fix createServicesFetcher handling null service
url
## 0.20.1
### Patch Changes
- [#456](https://github.com/kamilkisiela/graphql-hive/pull/456) [`fb9b624`](https://github.com/kamilkisiela/graphql-hive/commit/fb9b624ab80ff39658c9ecd45b55d10d906e15e7) Thanks [@dimatill](https://github.com/dimatill)! - (processVariables: true) do not collect input when corresponding variable is missing
- [#456](https://github.com/kamilkisiela/graphql-hive/pull/456)
[`fb9b624`](https://github.com/kamilkisiela/graphql-hive/commit/fb9b624ab80ff39658c9ecd45b55d10d906e15e7)
Thanks [@dimatill](https://github.com/dimatill)! - (processVariables: true) do not collect input
when corresponding variable is missing
## 0.20.0
### Minor Changes
- [#499](https://github.com/kamilkisiela/graphql-hive/pull/499) [`682cde8`](https://github.com/kamilkisiela/graphql-hive/commit/682cde81092fcb3a55de7f24035be4f2f64abfb3) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Add Self-Hosting options
- [#499](https://github.com/kamilkisiela/graphql-hive/pull/499)
[`682cde8`](https://github.com/kamilkisiela/graphql-hive/commit/682cde81092fcb3a55de7f24035be4f2f64abfb3)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Add Self-Hosting options
## 0.19.0
### Major Changes
- [#435](https://github.com/kamilkisiela/graphql-hive/pull/435) [`a79c253`](https://github.com/kamilkisiela/graphql-hive/commit/a79c253253614e44d01fef411016d353ef8c255e) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Use ETag and If-None-Match to save bandwidth and improve performance
- [#435](https://github.com/kamilkisiela/graphql-hive/pull/435)
[`a79c253`](https://github.com/kamilkisiela/graphql-hive/commit/a79c253253614e44d01fef411016d353ef8c255e)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Use ETag and If-None-Match to save
bandwidth and improve performance
## 0.18.5
### Patch Changes
- [#399](https://github.com/kamilkisiela/graphql-hive/pull/399) [`bd6e500`](https://github.com/kamilkisiela/graphql-hive/commit/bd6e500532ed4878a069883fadeaf3bb00e38aeb) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix the wrong cacheKey from #397
- [#399](https://github.com/kamilkisiela/graphql-hive/pull/399)
[`bd6e500`](https://github.com/kamilkisiela/graphql-hive/commit/bd6e500532ed4878a069883fadeaf3bb00e38aeb)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix the wrong cacheKey from #397
## 0.18.4
### Patch Changes
- [#379](https://github.com/kamilkisiela/graphql-hive/pull/379) [`2e7c8f3`](https://github.com/kamilkisiela/graphql-hive/commit/2e7c8f3c94013f890f42ca1054287841478ba7a6) Thanks [@dimatill](https://github.com/dimatill)! - Collect input fields from variables (opt-in with `processVariables` flag)
- [#379](https://github.com/kamilkisiela/graphql-hive/pull/379)
[`2e7c8f3`](https://github.com/kamilkisiela/graphql-hive/commit/2e7c8f3c94013f890f42ca1054287841478ba7a6)
Thanks [@dimatill](https://github.com/dimatill)! - Collect input fields from variables (opt-in
with `processVariables` flag)
## 0.18.3
### Patch Changes
- [#308](https://github.com/kamilkisiela/graphql-hive/pull/308) [`5a212f6`](https://github.com/kamilkisiela/graphql-hive/commit/5a212f61f206d7b73f8abf04667480851aa6066e) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Avoid marking the same type as used twice
- [#308](https://github.com/kamilkisiela/graphql-hive/pull/308)
[`5a212f6`](https://github.com/kamilkisiela/graphql-hive/commit/5a212f61f206d7b73f8abf04667480851aa6066e)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Avoid marking the same type as used
twice
## 0.18.2
@ -348,7 +374,8 @@
### Minor Changes
- 30da7e7: When disabled, run everything in dry mode (only http agent is disabled). This should help to catch errors in development.
- 30da7e7: When disabled, run everything in dry mode (only http agent is disabled). This should help
to catch errors in development.
### Patch Changes

View file

@ -1,9 +1,12 @@
# GraphQL Hive Client
[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema stitching, federation, or just a good old monolith.
[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage
and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema
stitching, federation, or just a good old monolith.
GraphQL Hive is currently available as a hosted service to be used by all.
We take care of the heavy lifting behind the scenes be managing the registry, scaling it for your needs, to free your time to focus on the most important things at hand.
GraphQL Hive is currently available as a hosted service to be used by all. We take care of the heavy
lifting behind the scenes be managing the registry, scaling it for your needs, to free your time to
focus on the most important things at hand.
## Installation
@ -13,11 +16,13 @@ npm install @graphql-hive/client
## Basic Usage
Hive Client comes with generic client and plugins for [Envelop](https://envelop.dev) and [Apollo Server](https://github.com/apollographql/apollo-server)
Hive Client comes with generic client and plugins for [Envelop](https://envelop.dev) and
[Apollo Server](https://github.com/apollographql/apollo-server)
### With GraphQL Yoga
[GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server) is a cross-platform GraphQL sever built on top of the envelop engine.
[GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server) is a cross-platform GraphQL sever
built on top of the envelop engine.
```ts
import { createYoga } from '@graphql-yoga/node'
@ -45,7 +50,9 @@ server.start()
### With Envelop
If you're not familiar with Envelop - in "short" it's a lightweight JavaScript library for wrapping GraphQL execution layer and flow, allowing developers to develop, share and collaborate on GraphQL-related plugins, while filling the missing pieces in GraphQL implementations.
If you're not familiar with Envelop - in "short" it's a lightweight JavaScript library for wrapping
GraphQL execution layer and flow, allowing developers to develop, share and collaborate on
GraphQL-related plugins, while filling the missing pieces in GraphQL implementations.
Here's [more](https://github.com/dotansimha/envelop#envelop) on that topic.
@ -102,10 +109,12 @@ const server = new ApolloServer({
First you need to instantiate the Hive Client.
The `collectUsage` method accepts the same arguments as execute function of graphql-js and returns a function that expects the execution result object.
The `collectUsage` method accepts the same arguments as execute function of graphql-js and returns a
function that expects the execution result object.
- `collectUsage(args)` - should be called when a GraphQL execution starts.
- `finish(result)` (function returned by `collectUsage(args)`) - has to be invoked right after execution finishes.
- `finish(result)` (function returned by `collectUsage(args)`) - has to be invoked right after
execution finishes.
```ts
import express from 'express'
@ -145,14 +154,17 @@ app.post(
### Using the registry when Stitching
Stitching could be done in many ways, that's why `@graphql-hive/client` provide generic functions, not something dedicated for stitching. Unfortunately the implementation of gateway + polling is up to you.
Stitching could be done in many ways, that's why `@graphql-hive/client` provide generic functions,
not something dedicated for stitching. Unfortunately the implementation of gateway + polling is up
to you.
Prerequisites:
- `HIVE_CDN_ENDPOINT` - the endpoint Hive generated for you in the previous step
- `HIVE_CDN_KEY` - the access key
The `createServicesFetcher` factory function returns another function that is responsible for fetching a list of services from Hive's high-availability endpoint.
The `createServicesFetcher` factory function returns another function that is responsible for
fetching a list of services from Hive's high-availability endpoint.
```ts
import { createServicesFetcher } from '@graphql-hive/client'
@ -214,7 +226,9 @@ server.listen().then(({ url }) => {
### Client Info
The schema usage operation information can be enriched with meta information that will be displayed on the Hive dashboard in order to get a better understanding of the origin of an executed GraphQL operation.
The schema usage operation information can be enriched with meta information that will be displayed
on the Hive dashboard in order to get a better understanding of the origin of an executed GraphQL
operation.
### GraphQL Yoga Example
@ -307,9 +321,11 @@ const server = new ApolloServer({
## Self-Hosting
To align the client with your own instance of GraphQL Hive, you should use `selfHosting` options in the client configuration.
To align the client with your own instance of GraphQL Hive, you should use `selfHosting` options in
the client configuration.
The example is based on GraphQL Yoga, but the same configuration applies to Apollo Server and others.
The example is based on GraphQL Yoga, but the same configuration applies to Apollo Server and
others.
```ts
import { createYoga } from '@graphql-yoga/node'
@ -332,4 +348,5 @@ const server = createYoga({
server.start()
```
> The `selfHosting` options take precedence over the deprecated `options.hosting.endpoint` and `options.usage.endpoint`.
> The `selfHosting` options take precedence over the deprecated `options.hosting.endpoint` and
> `options.usage.endpoint`.

View file

@ -1,21 +1,20 @@
{
"name": "@graphql-hive/client",
"description": "A NodeJS client for GraphQL Hive",
"version": "0.21.3",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"type": "module",
"description": "A NodeJS client for GraphQL Hive",
"repository": {
"type": "git",
"url": "kamilkisiela/graphql-hive",
"directory": "packages/libraries/client"
},
"homepage": "https://graphql-hive.com",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
@ -36,9 +35,6 @@
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"typescript": {
"definition": "dist/typings/index.d.ts"
},
"scripts": {
"build": "node scripts/update-version.mjs && bob build",
"check:build": "bob check"
@ -56,15 +52,19 @@
"devDependencies": {
"@apollo/federation": "0.38.1",
"@envelop/types": "3.0.0",
"graphql-yoga": "3.0.0-next.12",
"@types/async-retry": "1.4.5",
"apollo-server-core": "3.11.1",
"apollo-server-plugin-base": "3.5.3",
"graphql-yoga": "3.0.0-next.12",
"nock": "13.2.4"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public",
"directory": "dist"
},
"sideEffects": false,
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}

View file

@ -1,6 +1,10 @@
import type { ApolloServerPlugin } from 'apollo-server-plugin-base';
import type { DocumentNode } from 'graphql';
import type { HiveClient, HivePluginOptions, SupergraphSDLFetcherOptions } from './internal/types.js';
import type {
HiveClient,
HivePluginOptions,
SupergraphSDLFetcherOptions,
} from './internal/types.js';
import { createHash } from 'crypto';
import axios from 'axios';
import { createHive } from './client.js';
@ -59,9 +63,14 @@ export function createSupergraphSDLFetcher({ endpoint, key }: SupergraphSDLFetch
};
}
export function createSupergraphManager(options: { pollIntervalInMs?: number } & SupergraphSDLFetcherOptions) {
export function createSupergraphManager(
options: { pollIntervalInMs?: number } & SupergraphSDLFetcherOptions,
) {
const pollIntervalInMs = options.pollIntervalInMs ?? 30_000;
const fetchSupergraph = createSupergraphSDLFetcher({ endpoint: options.endpoint, key: options.key });
const fetchSupergraph = createSupergraphSDLFetcher({
endpoint: options.endpoint,
key: options.key,
});
let timer: ReturnType<typeof setTimeout> | null = null;
return {
@ -79,7 +88,9 @@ export function createSupergraphManager(options: { pollIntervalInMs?: number } &
hooks.update?.(result.supergraphSdl);
}
} catch (error) {
console.error(`Failed to update supergraph: ${error instanceof Error ? error.message : error}`);
console.error(
`Failed to update supergraph: ${error instanceof Error ? error.message : error}`,
);
}
poll();
}, pollIntervalInMs);

View file

@ -99,7 +99,7 @@ export function createHive(options: HivePluginOptions): HiveClient {
timeout: 30_000,
decompress: true,
responseType: 'json',
}
},
);
if (response.status >= 200 && response.status < 300) {
@ -108,10 +108,24 @@ export function createHive(options: HivePluginOptions): HiveClient {
if (result.data?.tokenInfo.__typename === 'TokenInfo') {
const { tokenInfo } = result.data;
const { organization, project, target, canReportSchema, canCollectUsage, canReadOperations } = tokenInfo;
const print = createPrinter([tokenInfo.token.name, organization.name, project.name, target.name]);
const {
organization,
project,
target,
canReportSchema,
canCollectUsage,
canReadOperations,
} = tokenInfo;
const print = createPrinter([
tokenInfo.token.name,
organization.name,
project.name,
target.name,
]);
const appUrl = options.selfHosting?.applicationUrl?.replace(/\/$/, '') ?? 'https://app.graphql-hive.com';
const appUrl =
options.selfHosting?.applicationUrl?.replace(/\/$/, '') ??
'https://app.graphql-hive.com';
const organizationUrl = `${appUrl}/${organization.cleanId}`;
const projectUrl = `${organizationUrl}/${project.cleanId}`;
const targetUrl = `${projectUrl}/${target.cleanId}`;
@ -129,14 +143,18 @@ export function createHive(options: HivePluginOptions): HiveClient {
`Can collect usage? ${print(canCollectUsage ? 'Yes' : 'No')}`,
`Can read operations? ${print(canReadOperations ? 'Yes' : 'No')}`,
'',
].join('\n')
].join('\n'),
);
} else if (result.data?.tokenInfo.message) {
logger.error(`[hive][info] Token not found. Reason: ${result.data?.tokenInfo.message}`);
logger.info(`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`);
logger.info(
`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`,
);
} else {
logger.error(`[hive][info] ${result.errors![0].message}`);
logger.info(`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`);
logger.info(
`[hive][info] How to create a token? https://docs.graphql-hive.com/features/tokens`,
);
}
} else {
logger.error(`[hive][info] Error ${response.status}: ${response.statusText}`);

View file

@ -86,7 +86,7 @@ export function createServicesFetcher({ endpoint, key }: ServicesFetcherOptions)
.update(service.name)
.digest('base64'),
...service,
}))
})),
);
};
}

View file

@ -60,7 +60,7 @@ export function createAgent<TEvent, TResult = void>(
};
body(): Buffer | string | Promise<string | Buffer>;
headers?(): Record<string, string>;
}
},
) {
const options: Required<AgentOptions> = {
timeout: 30_000,
@ -117,9 +117,18 @@ export function createAgent<TEvent, TResult = void>(
return send({ runOnce: true, throwOnError: true });
}
async function send<T>(sendOptions: { runOnce?: boolean; throwOnError: true }): Promise<T | null | never>;
async function send<T>(sendOptions: { runOnce?: boolean; throwOnError: false }): Promise<T | null>;
async function send<T>(sendOptions?: { runOnce?: boolean; throwOnError: boolean }): Promise<T | null | never> {
async function send<T>(sendOptions: {
runOnce?: boolean;
throwOnError: true;
}): Promise<T | null | never>;
async function send<T>(sendOptions: {
runOnce?: boolean;
throwOnError: false;
}): Promise<T | null>;
async function send<T>(sendOptions?: {
runOnce?: boolean;
throwOnError: boolean;
}): Promise<T | null | never> {
const runOnce = sendOptions?.runOnce ?? false;
if (!data.size()) {
@ -180,7 +189,9 @@ export function createAgent<TEvent, TResult = void>(
});
if (response.status < 200 || response.status >= 300) {
throw new Error(`[hive][${prefix}] Failed to send data (HTTP status ${response.status}): ${response.data}`);
throw new Error(
`[hive][${prefix}] Failed to send data (HTTP status ${response.status}): ${response.data}`,
);
}
debugLog(`Sent!`);

View file

@ -40,7 +40,9 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati
const load: OperationsStore['load'] = async () => {
const response = await axios.post(
selfHostingOptions?.graphqlEndpoint ?? operationsStoreOptions.endpoint ?? 'https://app.graphql-hive.com/graphql',
selfHostingOptions?.graphqlEndpoint ??
operationsStoreOptions.endpoint ??
'https://app.graphql-hive.com/graphql',
{
query,
operationName: 'loadStoredOperations',
@ -51,7 +53,7 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati
'content-type': 'application/json',
Authorization: `Bearer ${token}`,
},
}
},
);
const parsedData: {
@ -70,7 +72,7 @@ export function createOperationsStore(pluginOptions: HivePluginOptions): Operati
key,
parse(document, {
noLocation: true,
})
}),
);
});
};

View file

@ -27,14 +27,18 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte
logIf(
typeof reportingOptions.author !== 'string' || reportingOptions.author.length === 0,
'[hive][reporting] author is missing',
logger.error
logger.error,
);
logIf(
typeof reportingOptions.commit !== 'string' || reportingOptions.commit.length === 0,
'[hive][reporting] commit is missing',
logger.error
logger.error,
);
logIf(
typeof token !== 'string' || token.length === 0,
'[hive][reporting] token is missing',
logger.error,
);
logIf(typeof token !== 'string' || token.length === 0, '[hive][reporting] token is missing', logger.error);
let currentSchema: GraphQLSchema | null = null;
const agent = createAgent<GraphQLSchema, ExecutionResult<SchemaPublishMutation>>(
@ -42,7 +46,9 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte
logger,
...(pluginOptions.agent ?? {}),
endpoint:
selfHostingOptions?.graphqlEndpoint ?? reportingOptions.endpoint ?? 'https://app.graphql-hive.com/graphql',
selfHostingOptions?.graphqlEndpoint ??
reportingOptions.endpoint ??
'https://app.graphql-hive.com/graphql',
token: token,
enabled: pluginOptions.enabled,
debug: pluginOptions.debug,
@ -83,7 +89,7 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte
},
});
},
}
},
);
return {
@ -113,7 +119,9 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte
throw new Error('Service url is not defined');
}
case 'SchemaPublishError': {
logger.info(`[hive][reporting] Published schema (forced with ${data.errors.total} errors)`);
logger.info(
`[hive][reporting] Published schema (forced with ${data.errors.total} errors)`,
);
data.errors.nodes.slice(0, 5).forEach(error => {
logger.info(` - ${error.message}`);
});
@ -124,7 +132,7 @@ export function createReporting(pluginOptions: HivePluginOptions): SchemaReporte
logger.error(
`[hive][reporting] Failed to report schema: ${
error instanceof Error && 'message' in error ? error.message : error
}`
}`,
);
}
},
@ -226,6 +234,8 @@ function printSchemaWithDirectives(schema: GraphQLSchema) {
async function printToSDL(schema: GraphQLSchema) {
return stripIgnoredCharacters(
isFederatedSchema(schema) ? await extractFederationServiceSDL(schema) : printSchemaWithDirectives(schema)
isFederatedSchema(schema)
? await extractFederationServiceSDL(schema)
: printSchemaWithDirectives(schema),
);
}

View file

@ -13,7 +13,9 @@ export interface HiveClient {
export type AsyncIterableIteratorOrValue<T> = AsyncIterableIterator<T> | T;
export type CollectUsageCallback = (result: AsyncIterableIteratorOrValue<GraphQLErrorsResult>) => void;
export type CollectUsageCallback = (
result: AsyncIterableIteratorOrValue<GraphQLErrorsResult>,
) => void;
export interface ClientInfo {
name: string;
version: string;

View file

@ -28,8 +28,20 @@ import { normalizeOperation } from '@graphql-hive/core';
import { createAgent } from './agent.js';
import { randomSampling } from './sampling.js';
import { version } from '../version.js';
import { cache, cacheDocumentKey, measureDuration, memo, isAsyncIterableIterator, logIf } from './utils.js';
import type { HivePluginOptions, HiveUsagePluginOptions, CollectUsageCallback, ClientInfo } from './types.js';
import {
cache,
cacheDocumentKey,
measureDuration,
memo,
isAsyncIterableIterator,
logIf,
} from './utils.js';
import type {
HivePluginOptions,
HiveUsagePluginOptions,
CollectUsageCallback,
ClientInfo,
} from './types.js';
interface UsageCollector {
collect(args: ExecutionArgs): CollectUsageCallback;
@ -51,7 +63,8 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector {
map: {},
operations: [],
};
const options = typeof pluginOptions.usage === 'boolean' ? ({} as HiveUsagePluginOptions) : pluginOptions.usage;
const options =
typeof pluginOptions.usage === 'boolean' ? ({} as HiveUsagePluginOptions) : pluginOptions.usage;
const selfHostingOptions = pluginOptions.selfHosting;
const logger = pluginOptions.agent?.logger ?? console;
const collector = memo(createCollector, arg => arg.schema);
@ -62,7 +75,10 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector {
...(pluginOptions.agent ?? {
maxSize: 1500,
}),
endpoint: selfHostingOptions?.usageEndpoint ?? options.endpoint ?? 'https://app.graphql-hive.com/usage',
endpoint:
selfHostingOptions?.usageEndpoint ??
options.endpoint ??
'https://app.graphql-hive.com/usage',
token: pluginOptions.token,
enabled: pluginOptions.enabled,
debug: pluginOptions.debug,
@ -116,13 +132,13 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector {
body() {
return JSON.stringify(report);
},
}
},
);
logIf(
typeof pluginOptions.token !== 'string' || pluginOptions.token.length === 0,
'[hive][usage] token is missing',
logger.error
logger.error,
);
const shouldInclude = randomSampling(options.sampleRate ?? 1.0);
@ -141,7 +157,7 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector {
}
const rootOperation = args.document.definitions.find(
o => o.kind === Kind.OPERATION_DEFINITION
o => o.kind === Kind.OPERATION_DEFINITION,
) as OperationDefinitionNode;
const document = args.document;
const operationName = args.operationName || rootOperation.name?.value || 'anonymous';
@ -175,7 +191,8 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector {
},
// TODO: operationHash is ready to accept hashes of persisted operations
client:
typeof args.contextValue !== 'undefined' && typeof options.clientInfo !== 'undefined'
typeof args.contextValue !== 'undefined' &&
typeof options.clientInfo !== 'undefined'
? options.clientInfo(args.contextValue)
: null,
});
@ -210,7 +227,7 @@ export function createCollector({
doc: DocumentNode,
variables: {
[key: string]: unknown;
} | null
} | null,
): CacheResult {
const entries = new Set<string>();
const collected_entire_named_types = new Set<string>();
@ -377,7 +394,7 @@ export function createCollector({
collectNode(node);
collectInputType(parentInputTypeName, node.name.value);
},
})
}),
);
for (const inputTypeName in collectedInputTypes) {
@ -407,7 +424,7 @@ export function createCollector({
function cacheKey(doc, variables) {
return cacheDocumentKey(doc, processVariables === true ? variables : null);
},
LRU<CacheResult>(max, ttl)
LRU<CacheResult>(max, ttl),
);
}
@ -426,7 +443,10 @@ function unwrapType(type: GraphQLType): GraphQLNamedType {
return type;
}
type GraphQLNamedInputType = Exclude<GraphQLNamedType, GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType>;
type GraphQLNamedInputType = Exclude<
GraphQLNamedType,
GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType
>;
type GraphQLNamedOutputType = Exclude<GraphQLNamedType, GraphQLInputObjectType>;
export interface Report {

View file

@ -1,7 +1,9 @@
import { createHash } from 'crypto';
import type { HiveClient, HivePluginOptions, AsyncIterableIteratorOrValue } from './types.js';
export function isAsyncIterableIterator<T>(value: AsyncIterableIteratorOrValue<T>): value is AsyncIterableIterator<T> {
export function isAsyncIterableIterator<T>(
value: AsyncIterableIteratorOrValue<T>,
): value is AsyncIterableIterator<T> {
return typeof (value as any)?.[Symbol.asyncIterator] === 'function';
}
@ -29,7 +31,7 @@ export function cache<R, A, K, V>(
has(key: K): boolean;
set(key: K, value: R): void;
get(key: K): R | undefined;
}
},
) {
return (arg: A, arg2: V) => {
const key = cacheKeyFn(arg, arg2);
@ -68,7 +70,7 @@ export function cacheDocumentKey<T, V>(doc: T, variables: V | null) {
}
return '';
})
}),
);
}
@ -102,11 +104,15 @@ export function addProperty<T, K extends string>(key: K, value: undefined | null
export function addProperty<T, K extends string, V>(
key: K,
value: V,
obj: T
obj: T,
): T & {
[k in K]: V;
};
export function addProperty<T, K extends string, V>(key: K, value: V | undefined | null, obj: T): any {
export function addProperty<T, K extends string, V>(
key: K,
value: V | undefined | null,
obj: T,
): any {
if (value === null || typeof value === 'undefined') {
return obj;
}
@ -117,7 +123,9 @@ export function addProperty<T, K extends string, V>(key: K, value: V | undefined
};
}
export function isHiveClient(clientOrOptions: HiveClient | HivePluginOptions): clientOrOptions is HiveClient {
export function isHiveClient(
clientOrOptions: HiveClient | HivePluginOptions,
): clientOrOptions is HiveClient {
return 'operationsStore' in clientOrOptions;
}

View file

@ -88,13 +88,15 @@ test('should use selfHosting.graphqlEndpoint if provided', async () => {
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining(`[hive][info] Token details`));
expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Token name: \s+ My Token/));
expect(logger.info).toHaveBeenCalledWith(
expect.stringMatching(/Organization: \s+ Org \s+ http:\/\/localhost\/org-id/)
expect.stringMatching(/Organization: \s+ Org \s+ http:\/\/localhost\/org-id/),
);
expect(logger.info).toHaveBeenCalledWith(
expect.stringMatching(/Project: \s+ Project \s+ http:\/\/localhost\/org-id\/project-id/)
expect.stringMatching(/Project: \s+ Project \s+ http:\/\/localhost\/org-id\/project-id/),
);
expect(logger.info).toHaveBeenCalledWith(
expect.stringMatching(/Target: \s+ Target \s+ http:\/\/localhost\/org-id\/project-id\/target-id/)
expect.stringMatching(
/Target: \s+ Target \s+ http:\/\/localhost\/org-id\/project-id\/target-id/,
),
);
expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Can report schema\? \s+ Yes/));
expect(logger.info).toHaveBeenCalledWith(expect.stringMatching(/Can collect usage\? \s+ Yes/));

View file

@ -51,11 +51,15 @@ test('should not leak the exception', async () => {
await hive.dispose();
expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)');
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('[hive][reporting] Attempt 1 failed:'));
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining('[hive][reporting] Attempt 1 failed:'),
);
expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 2)');
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining(`[hive][reporting] Failed to report schema: connect ECONNREFUSED 127.0.0.1:55404`)
expect.stringContaining(
`[hive][reporting] Failed to report schema: connect ECONNREFUSED 127.0.0.1:55404`,
),
);
});
@ -426,7 +430,7 @@ test('should send original schema of a federated service', async () => {
type Query {
bar: String
}
`)
`),
),
});
@ -498,7 +502,9 @@ test('should display SchemaPublishMissingServiceError', async () => {
http.done();
expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)');
expect(logger.error).toHaveBeenCalledWith(`[hive][reporting] Failed to report schema: Service name is not defined`);
expect(logger.error).toHaveBeenCalledWith(
`[hive][reporting] Failed to report schema: Service name is not defined`,
);
});
test('should display SchemaPublishMissingUrlError', async () => {
@ -558,5 +564,7 @@ test('should display SchemaPublishMissingUrlError', async () => {
http.done();
expect(logger.info).toHaveBeenCalledWith('[hive][reporting] Sending (queue 1) (attempt 1)');
expect(logger.error).toHaveBeenCalledWith(`[hive][reporting] Failed to report schema: Service url is not defined`);
expect(logger.error).toHaveBeenCalledWith(
`[hive][reporting] Failed to report schema: Service url is not defined`,
);
});

View file

@ -121,7 +121,7 @@ test('collect enums and scalars as inputs', async () => {
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`Int`);
@ -144,7 +144,7 @@ test('collect enum values from object fields', async () => {
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`Int`);
@ -167,7 +167,7 @@ test('collect enum values from arguments', async () => {
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`ProjectType.FEDERATION`);
@ -189,7 +189,7 @@ test('collect arguments', async () => {
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`Query.projects.filter`);
@ -208,7 +208,7 @@ test('collect used-only input fields', async () => {
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`FilterInput.pagination`);
@ -230,7 +230,7 @@ test('collect all input fields when `processVariables` has not been passed and i
}
}
`),
{}
{},
).value;
expect(info.fields).toContain(`FilterInput.pagination`);
@ -327,7 +327,7 @@ test('(processVariables: true) collect used-only input fields', async () => {
limit: 1,
},
type: 'STITCHING',
}
},
).value;
expect(info.fields).toContain(`FilterInput.pagination`);
@ -352,7 +352,7 @@ test('(processVariables: true) should collect input object without fields when c
`),
{
type: 'STITCHING',
}
},
).value;
expect(info.fields).toContain(`FilterInput.type`);
@ -391,7 +391,7 @@ test('(processVariables: true) collect used-only input type fields from an array
limit: 10,
},
},
}
},
).value;
expect(info.fields).toContain(`FilterInput.pagination`);

View file

@ -319,10 +319,14 @@ test('should not leak the exception', async () => {
await hive.dispose();
expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sending (queue 1) (attempt 1)`);
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining(`[hive][usage] Attempt 1 failed:`));
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining(`[hive][usage] Attempt 1 failed:`),
);
expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sending (queue 1) (attempt 2)`);
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining(`[hive][usage] Failed to send data`));
expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining(`[hive][usage] Failed to send data`),
);
});
test('sendImmediately should not stop the schedule', async () => {

View file

@ -4,7 +4,9 @@
### Patch Changes
- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668) [`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue
- [#668](https://github.com/kamilkisiela/graphql-hive/pull/668)
[`e116841`](https://github.com/kamilkisiela/graphql-hive/commit/e116841a739bfd7f37c4a826544301cf23d61637)
Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix ESM/CJS issue
## 0.2.2

View file

@ -1,20 +1,19 @@
{
"name": "@graphql-hive/core",
"version": "0.2.3",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"type": "module",
"repository": {
"type": "git",
"url": "kamilkisiela/graphql-hive",
"directory": "packages/libraries/core"
},
"homepage": "https://graphql-hive.com",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"license": "MIT",
"type": "module",
"sideEffects": false,
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
@ -35,14 +34,6 @@
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"typescript": {
"definition": "dist/typings/index.d.ts"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public",
"directory": "dist"
},
"scripts": {
"build": "bob build",
"check:build": "bob check"
@ -56,5 +47,14 @@
"devDependencies": {
"@types/lodash.sortby": "4.7.7",
"tslib": "2.4.1"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public",
"directory": "dist"
},
"sideEffects": false,
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}

View file

@ -27,76 +27,86 @@ export function normalizeOperation({
}): string {
return stripIgnoredCharacters(
print(
visit(dropUnusedDefinitions(document, operationName ?? document.definitions.find(isOperationDef)?.name?.value), {
// hide literals
IntValue(node) {
return hideLiterals ? { ...node, value: '0' } : node;
visit(
dropUnusedDefinitions(
document,
operationName ?? document.definitions.find(isOperationDef)?.name?.value,
),
{
// hide literals
IntValue(node) {
return hideLiterals ? { ...node, value: '0' } : node;
},
FloatValue(node) {
return hideLiterals ? { ...node, value: '0' } : node;
},
StringValue(node) {
return hideLiterals ? { ...node, value: '', block: false } : node;
},
Field(node) {
return {
...node,
// remove aliases
alias: removeAliases ? undefined : node.alias,
// sort arguments
arguments: sortNodes(node.arguments),
};
},
Document(node) {
return {
...node,
definitions: sortNodes(node.definitions),
};
},
OperationDefinition(node) {
return {
...node,
variableDefinitions: sortNodes(node.variableDefinitions),
};
},
SelectionSet(node) {
return {
...node,
selections: sortNodes(node.selections),
};
},
FragmentSpread(node) {
return {
...node,
directives: sortNodes(node.directives),
};
},
InlineFragment(node) {
return {
...node,
directives: sortNodes(node.directives),
};
},
FragmentDefinition(node) {
return {
...node,
directives: sortNodes(node.directives),
variableDefinitions: sortNodes(node.variableDefinitions),
};
},
Directive(node) {
return { ...node, arguments: sortNodes(node.arguments) };
},
},
FloatValue(node) {
return hideLiterals ? { ...node, value: '0' } : node;
},
StringValue(node) {
return hideLiterals ? { ...node, value: '', block: false } : node;
},
Field(node) {
return {
...node,
// remove aliases
alias: removeAliases ? undefined : node.alias,
// sort arguments
arguments: sortNodes(node.arguments),
};
},
Document(node) {
return {
...node,
definitions: sortNodes(node.definitions),
};
},
OperationDefinition(node) {
return {
...node,
variableDefinitions: sortNodes(node.variableDefinitions),
};
},
SelectionSet(node) {
return {
...node,
selections: sortNodes(node.selections),
};
},
FragmentSpread(node) {
return {
...node,
directives: sortNodes(node.directives),
};
},
InlineFragment(node) {
return {
...node,
directives: sortNodes(node.directives),
};
},
FragmentDefinition(node) {
return {
...node,
directives: sortNodes(node.directives),
variableDefinitions: sortNodes(node.variableDefinitions),
};
},
Directive(node) {
return { ...node, arguments: sortNodes(node.arguments) };
},
})
)
),
),
);
}
function sortNodes(nodes: readonly DefinitionNode[]): readonly DefinitionNode[];
function sortNodes(nodes: readonly SelectionNode[]): readonly SelectionNode[];
function sortNodes(nodes: readonly ArgumentNode[] | undefined): readonly ArgumentNode[] | undefined;
function sortNodes(nodes: readonly VariableDefinitionNode[] | undefined): readonly VariableDefinitionNode[] | undefined;
function sortNodes(nodes: readonly DirectiveNode[] | undefined): readonly DirectiveNode[] | undefined;
function sortNodes(
nodes: readonly VariableDefinitionNode[] | undefined,
): readonly VariableDefinitionNode[] | undefined;
function sortNodes(
nodes: readonly DirectiveNode[] | undefined,
): readonly DirectiveNode[] | undefined;
function sortNodes(nodes: readonly any[] | undefined): readonly any[] | undefined {
if (nodes) {
if (nodes.length === 0) {
@ -115,7 +125,9 @@ function sortNodes(nodes: readonly any[] | undefined): readonly any[] | undefine
return sortBy(nodes, 'name.value');
}
if (isOfKindList<SelectionNode>(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])) {
if (
isOfKindList<SelectionNode>(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])
) {
return sortBy(nodes, 'kind', 'name.value');
}

View file

@ -1,5 +1,9 @@
# GraphQL Hive - external composition
[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema stitching, federation, or just a good old monolith.
[GraphQL Hive](https://graphql-hive.com) is a GraphQL schemas registry where you can host, manage
and collaborate on all your GraphQL schemas and operations, compatible with all architecture: schema
stitching, federation, or just a good old monolith.
Read the ["External schema composition"](https://docs.graphql-hive.com/features/external-schema-composition) to learn more.
Read the
["External schema composition"](https://docs.graphql-hive.com/features/external-schema-composition)
to learn more.

Some files were not shown because too many files have changed in this diff Show more