mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 06:37:15 +00:00
Creation and deprecation status of schema coordinates - schema age filter (part 1) (#5224)
This commit is contained in:
parent
b651e890c2
commit
8020419437
34 changed files with 2707 additions and 204 deletions
|
|
@ -130,6 +130,7 @@ module.exports = {
|
|||
'prefer-destructuring': 'off',
|
||||
'prefer-const': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-inner-declarations': 'off',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"slonik": "30.4.4",
|
||||
"strip-ansi": "7.1.0",
|
||||
"tslib": "2.6.3",
|
||||
"vitest": "1.6.0",
|
||||
"vitest": "2.0.3",
|
||||
"zod": "3.23.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,15 @@ export function initSeed() {
|
|||
}
|
||||
|
||||
return {
|
||||
async createDbConnection() {
|
||||
const pool = await createConnectionPool();
|
||||
return {
|
||||
pool,
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
await pool.end();
|
||||
},
|
||||
};
|
||||
},
|
||||
authenticate: authenticate,
|
||||
generateEmail: () => userEmail(generateUnique()),
|
||||
async createOwner() {
|
||||
|
|
|
|||
991
integration-tests/tests/api/schema/cleanup-tracker.spec.ts
Normal file
991
integration-tests/tests/api/schema/cleanup-tracker.spec.ts
Normal file
|
|
@ -0,0 +1,991 @@
|
|||
import 'reflect-metadata';
|
||||
import { sql, type CommonQueryMethods } from 'slonik';
|
||||
/* eslint-disable no-process-env */
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { test } from 'vitest';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
async function fetchCoordinates(db: CommonQueryMethods, target: { id: string }) {
|
||||
const result = await db.query<{
|
||||
coordinate: string;
|
||||
created_in_version_id: string;
|
||||
deprecated_in_version_id: string | null;
|
||||
}>(sql`
|
||||
SELECT coordinate, created_in_version_id, deprecated_in_version_id
|
||||
FROM schema_coordinate_status WHERE target_id = ${target.id}
|
||||
`);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
describe('schema cleanup tracker', () => {
|
||||
test.concurrent('single', async ({ expect }) => {
|
||||
const { publishSchema, target, createDbConnection } = await prepare();
|
||||
// This API is soooooooooooo awkward xD
|
||||
await using db = await createDbConnection();
|
||||
|
||||
const ver1 = await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver2 = await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hi',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.hi',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver4 = await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hi',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver5 = await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String @deprecated(reason: "no longer needed")
|
||||
bye: String
|
||||
goodbye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.bye',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.goodbye',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hi',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await publishSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
bye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hello',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.bye',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.goodbye',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.hi',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test.concurrent('federation', async ({ expect }) => {
|
||||
const { publishSchema, deleteSchema, target, createDbConnection } = await prepare(
|
||||
ProjectType.Federation,
|
||||
);
|
||||
await using db = await createDbConnection();
|
||||
|
||||
const serviceFoo = {
|
||||
name: 'foo',
|
||||
url: 'https://api.com/foo',
|
||||
};
|
||||
|
||||
const serviceBar = {
|
||||
name: 'bar',
|
||||
url: 'https://api.com/bar',
|
||||
};
|
||||
|
||||
const ver1 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
||||
|
||||
type Query {
|
||||
user(id: ID!): User
|
||||
}
|
||||
|
||||
type User @key(fields: "id") {
|
||||
id: ID!
|
||||
name: String
|
||||
}
|
||||
`,
|
||||
serviceFoo,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver2 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
||||
|
||||
type Query {
|
||||
users: User
|
||||
}
|
||||
|
||||
type User @key(fields: "id") {
|
||||
id: ID!
|
||||
pictureUrl: String
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await deleteSchema(serviceBar.name);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver4 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
||||
|
||||
type Query {
|
||||
users: User
|
||||
}
|
||||
|
||||
type User @key(fields: "id") {
|
||||
id: ID!
|
||||
pictureUrl: String
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver5 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
||||
|
||||
type Query {
|
||||
users: User @deprecated(reason: "no longer needed")
|
||||
randomUser: User
|
||||
admin: User
|
||||
}
|
||||
|
||||
type User @key(fields: "id") {
|
||||
id: ID!
|
||||
pictureUrl: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.randomUser',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.admin',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await publishSchema(
|
||||
/* GraphQL */ `
|
||||
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
||||
|
||||
type Query {
|
||||
users: User
|
||||
admin: User
|
||||
}
|
||||
|
||||
type User @key(fields: "id") {
|
||||
id: ID!
|
||||
pictureUrl: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.randomUser',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.admin',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test.concurrent('stitching', async ({ expect }) => {
|
||||
const { publishSchema, deleteSchema, target, createDbConnection } = await prepare(
|
||||
ProjectType.Stitching,
|
||||
);
|
||||
await using db = await createDbConnection();
|
||||
|
||||
const serviceFoo = {
|
||||
name: 'foo',
|
||||
url: 'https://api.com/foo',
|
||||
};
|
||||
|
||||
const serviceBar = {
|
||||
name: 'bar',
|
||||
url: 'https://api.com/bar',
|
||||
};
|
||||
|
||||
const ver1 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
user(id: ID!): User @merge
|
||||
}
|
||||
|
||||
type User @key(selectionSet: "{ id }") {
|
||||
id: ID!
|
||||
name: String
|
||||
}
|
||||
`,
|
||||
serviceFoo,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver2 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
users: User
|
||||
user(id: ID!): User @merge
|
||||
}
|
||||
|
||||
type User @key(selectionSet: "{ id }") {
|
||||
id: ID!
|
||||
pictureUrl: String
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await deleteSchema(serviceBar.name);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver2,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver4 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
users: User
|
||||
user(id: ID!): User @merge
|
||||
}
|
||||
|
||||
type User @key(selectionSet: "{ id }") {
|
||||
id: ID!
|
||||
pictureUrl: String
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const ver5 = await publishSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
users: User @deprecated(reason: "no longer needed")
|
||||
randomUser: User
|
||||
admin: User
|
||||
user(id: ID!): User @merge
|
||||
}
|
||||
|
||||
type User @key(selectionSet: "{ id }") {
|
||||
id: ID!
|
||||
pictureUrl: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.randomUser',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.admin',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await publishSchema(
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
users: User
|
||||
admin: User
|
||||
user(id: ID!): User @merge
|
||||
}
|
||||
|
||||
type User @key(selectionSet: "{ id }") {
|
||||
id: ID!
|
||||
pictureUrl: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
serviceBar,
|
||||
);
|
||||
|
||||
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
// foo
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.user.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.id',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.name',
|
||||
created_in_version_id: ver1,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
// bar
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.users',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
coordinate: 'Query.randomUser',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'Query.admin',
|
||||
created_in_version_id: ver5,
|
||||
deprecated_in_version_id: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
coordinate: 'User.pictureUrl',
|
||||
created_in_version_id: ver4,
|
||||
deprecated_in_version_id: ver5,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function prepare(projectType: ProjectType = ProjectType.Single) {
|
||||
const { createOwner, createDbConnection } = initSeed();
|
||||
const { createOrg } = await createOwner();
|
||||
const { createProject, organization } = await createOrg();
|
||||
const { createToken, project, target } = await createProject(projectType);
|
||||
const token = await createToken({
|
||||
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
||||
projectScopes: [],
|
||||
organizationScopes: [],
|
||||
});
|
||||
|
||||
return {
|
||||
createDbConnection,
|
||||
async publishSchema(
|
||||
sdl: string,
|
||||
service?: {
|
||||
name: string;
|
||||
url: string;
|
||||
},
|
||||
) {
|
||||
const result = await token
|
||||
.publishSchema({ sdl, service: service?.name, url: service?.url })
|
||||
.then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
if (result.schemaPublish.__typename !== 'SchemaPublishSuccess') {
|
||||
console.log(JSON.stringify(result.schemaPublish, null, 2));
|
||||
throw new Error(`Expected schemaPublish success, got ${result.schemaPublish.__typename}`);
|
||||
}
|
||||
|
||||
if (!result.schemaPublish.valid) {
|
||||
throw new Error('Expected schema to be valid');
|
||||
}
|
||||
|
||||
const version = await token.fetchLatestValidSchema();
|
||||
const versionId = version.latestValidVersion?.id;
|
||||
|
||||
if (!versionId) {
|
||||
throw new Error('Expected version id to be defined');
|
||||
}
|
||||
|
||||
return versionId;
|
||||
},
|
||||
async deleteSchema(serviceName: string) {
|
||||
const result = await token.deleteSchema(serviceName).then(r => r.expectNoGraphQLErrors());
|
||||
|
||||
if (result.schemaDelete.__typename !== 'SchemaDeleteSuccess') {
|
||||
throw new Error(`Expected schemaDelete success, got ${result.schemaDelete.__typename}`);
|
||||
}
|
||||
|
||||
if (!result.schemaDelete.valid) {
|
||||
throw new Error('Expected schema to be valid');
|
||||
}
|
||||
|
||||
const version = await token.fetchLatestValidSchema();
|
||||
const versionId = version.latestValidVersion?.id;
|
||||
|
||||
if (!versionId) {
|
||||
throw new Error('Expected version id to be defined');
|
||||
}
|
||||
|
||||
return versionId;
|
||||
},
|
||||
organization,
|
||||
project,
|
||||
target,
|
||||
};
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
"turbo": "1.13.4",
|
||||
"typescript": "5.5.3",
|
||||
"vite-tsconfig-paths": "4.3.2",
|
||||
"vitest": "1.6.0"
|
||||
"vitest": "2.0.3"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
"graphql": "16.9.0",
|
||||
"graphql-ws": "5.16.0",
|
||||
"nock": "14.0.0-beta.7",
|
||||
"vitest": "1.6.0",
|
||||
"vitest": "2.0.3",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ test('should not interrupt the process', async () => {
|
|||
|
||||
test('should capture client name and version headers', async () => {
|
||||
const clean = handleProcess();
|
||||
const fetchSpy = vi.fn<[RequestInfo | URL, options: RequestInit | undefined]>(async () =>
|
||||
const fetchSpy = vi.fn(async (_input: string | URL | globalThis.Request, _init?: RequestInit) =>
|
||||
Response.json({}, { status: 200 }),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
"graphql": "16.9.0",
|
||||
"nock": "14.0.0-beta.7",
|
||||
"tslib": "2.6.3",
|
||||
"vitest": "1.6.0"
|
||||
"vitest": "2.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
"graphql-ws": "5.16.0",
|
||||
"graphql-yoga": "5.6.0",
|
||||
"nock": "14.0.0-beta.7",
|
||||
"vitest": "1.6.0",
|
||||
"vitest": "2.0.3",
|
||||
"ws": "8.18.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ test('should not interrupt the process', async () => {
|
|||
}, 1_000);
|
||||
|
||||
test('should capture client name and version headers', async () => {
|
||||
const fetchSpy = vi.fn<[RequestInfo | URL, options: RequestInit | undefined]>(async () =>
|
||||
const fetchSpy = vi.fn(async (_input: string | URL | globalThis.Request, _init?: RequestInit) =>
|
||||
Response.json({}, { status: 200 }),
|
||||
);
|
||||
const clean = handleProcess();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"copyfiles": "2.4.1",
|
||||
"dotenv": "16.4.5",
|
||||
"got": "14.4.1",
|
||||
"graphql": "16.9.0",
|
||||
"p-limit": "4.0.0",
|
||||
"pg-promise": "11.9.0",
|
||||
"slonik": "30.4.4",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,496 @@
|
|||
import {
|
||||
buildSchema,
|
||||
GraphQLFieldMap,
|
||||
GraphQLSchema,
|
||||
isEnumType,
|
||||
isInputObjectType,
|
||||
isInterfaceType,
|
||||
isIntrospectionType,
|
||||
isObjectType,
|
||||
isScalarType,
|
||||
isUnionType,
|
||||
} from 'graphql';
|
||||
import { sql, type CommonQueryMethods } from 'slonik';
|
||||
import { env } from '../environment';
|
||||
import type { MigrationExecutor } from '../pg-migrator';
|
||||
|
||||
export default {
|
||||
name: '2024.07.23T09.36.00.schema-cleanup-tracker.ts',
|
||||
async run({ connection }) {
|
||||
await connection.query(sql`
|
||||
CREATE TABLE IF NOT EXISTS "schema_coordinate_status" (
|
||||
coordinate text NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_in_version_id UUID NOT NULL REFERENCES "schema_versions" ("id") ON DELETE CASCADE,
|
||||
deprecated_at TIMESTAMPTZ,
|
||||
deprecated_in_version_id UUID REFERENCES "schema_versions" ("id") ON DELETE CASCADE,
|
||||
"target_id" UUID NOT NULL REFERENCES "targets" ("id") ON DELETE CASCADE,
|
||||
PRIMARY KEY (coordinate, target_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_schema_coordinate_status_by_target_timestamp
|
||||
ON schema_coordinate_status(
|
||||
target_id,
|
||||
created_at,
|
||||
deprecated_at
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_schema_coordinate_status_by_target_coordinate_timestamp
|
||||
ON schema_coordinate_status(
|
||||
target_id,
|
||||
coordinate,
|
||||
created_at,
|
||||
deprecated_at
|
||||
);
|
||||
`);
|
||||
|
||||
if (env.isHiveCloud) {
|
||||
console.log('Skipping schema coordinate status migration for hive cloud');
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaVersionsTotal = await connection.oneFirst<number>(sql`
|
||||
SELECT count(*) as total FROM schema_versions
|
||||
`);
|
||||
console.log(`Found ${schemaVersionsTotal} schema versions`);
|
||||
|
||||
if (schemaVersionsTotal > 1000) {
|
||||
console.warn(
|
||||
`[WARN] There are more than 1000 schema versions (${schemaVersionsTotal}). Skipping a data backfill.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await schemaCoordinateStatusMigration(connection);
|
||||
},
|
||||
} satisfies MigrationExecutor;
|
||||
|
||||
type SchemaCoordinatesDiffResult = {
|
||||
/**
|
||||
* Coordinates that are in incoming but not in existing (including deprecated ones)
|
||||
*/
|
||||
added: Set<string>;
|
||||
/**
|
||||
* Coordinates that are deprecated in incoming, but were not deprecated in existing or non-existent
|
||||
*/
|
||||
deprecated: Set<string>;
|
||||
};
|
||||
|
||||
function diffSchemaCoordinates(
|
||||
existingSchema: GraphQLSchema,
|
||||
incomingSchema: GraphQLSchema,
|
||||
): SchemaCoordinatesDiffResult {
|
||||
const before = getSchemaCoordinates(existingSchema);
|
||||
const after = getSchemaCoordinates(incomingSchema);
|
||||
|
||||
const added = after.coordinates.difference(before.coordinates);
|
||||
const deprecated = after.deprecated.difference(before.deprecated);
|
||||
|
||||
return {
|
||||
added,
|
||||
deprecated,
|
||||
};
|
||||
}
|
||||
|
||||
export async function schemaCoordinateStatusMigration(connection: CommonQueryMethods) {
|
||||
// Fetch targets
|
||||
const targetResult = await connection.query<{ id: string }>(sql`
|
||||
SELECT id FROM targets WHERE ID NOT IN (SELECT target_id FROM schema_coordinate_status)
|
||||
`);
|
||||
|
||||
console.log(`Found ${targetResult.rowCount} targets`);
|
||||
|
||||
let i = 0;
|
||||
for await (const target of targetResult.rows) {
|
||||
try {
|
||||
console.log(`Processing target (${i++}/${targetResult.rowCount}) - ${target.id}`);
|
||||
|
||||
const latestSchema = await connection.maybeOne<{
|
||||
id: string;
|
||||
created_at: number;
|
||||
is_composable: boolean;
|
||||
sdl?: string;
|
||||
previous_schema_version_id?: string;
|
||||
}>(sql`
|
||||
SELECT
|
||||
id,
|
||||
created_at,
|
||||
is_composable,
|
||||
previous_schema_version_id,
|
||||
composite_schema_sdl as sdl
|
||||
FROM schema_versions
|
||||
WHERE target_id = ${target.id} AND is_composable = true
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
if (!latestSchema) {
|
||||
console.log('[SKIPPING] No latest composable schema found for target %s', target.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!latestSchema.sdl) {
|
||||
console.warn(
|
||||
`[SKIPPING] No latest, composable schema with non-empty sdl found for target ${target.id}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const schema = buildSchema(latestSchema.sdl, {
|
||||
assumeValid: true,
|
||||
assumeValidSDL: true,
|
||||
});
|
||||
const targetCoordinates = getSchemaCoordinates(schema);
|
||||
|
||||
// The idea here is to
|
||||
// 1. start from the latest composable version.
|
||||
// 2. create a list of coordinates that are in the latest version, all and deprecated.
|
||||
// 3. navigate to the previous version and compare the coordinates.
|
||||
// 4. if a coordinate is added, upsert it into the schema_coordinate_status and remove it from the list.
|
||||
// 5. if a coordinate is deprecated, upsert it into the schema_coordinate_status and remove it from the list of deprecated coordinates.
|
||||
// 6. if the list of coordinates is empty, stop the process.
|
||||
// 7. if the previous version is not composable, skip it and continue with the next previous version.
|
||||
// 8. if the previous version is not found, insert all remaining coordinates and stop the process. This step might create incorrect dates!
|
||||
await processVersion(1, connection, targetCoordinates, target.id, {
|
||||
schema,
|
||||
versionId: latestSchema.id,
|
||||
createdAt: latestSchema.created_at,
|
||||
previousVersionId: latestSchema.previous_schema_version_id ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error processing target ${target.id}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSchemaCoordinates(schema: GraphQLSchema): {
|
||||
coordinates: Set<string>;
|
||||
deprecated: Set<string>;
|
||||
} {
|
||||
const coordinates = new Set<string>();
|
||||
const deprecated = new Set<string>();
|
||||
|
||||
const typeMap = schema.getTypeMap();
|
||||
|
||||
for (const typeName in typeMap) {
|
||||
const typeDefinition = typeMap[typeName];
|
||||
|
||||
if (isIntrospectionType(typeDefinition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
coordinates.add(typeName);
|
||||
|
||||
if (isObjectType(typeDefinition) || isInterfaceType(typeDefinition)) {
|
||||
visitSchemaCoordinatesOfGraphQLFieldMap(
|
||||
typeName,
|
||||
typeDefinition.getFields(),
|
||||
coordinates,
|
||||
deprecated,
|
||||
);
|
||||
} else if (isInputObjectType(typeDefinition)) {
|
||||
const fieldMap = typeDefinition.getFields();
|
||||
for (const fieldName in fieldMap) {
|
||||
const fieldDefinition = fieldMap[fieldName];
|
||||
|
||||
coordinates.add(`${typeName}.${fieldName}`);
|
||||
if (fieldDefinition.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}`);
|
||||
}
|
||||
}
|
||||
} else if (isUnionType(typeDefinition)) {
|
||||
for (const member of typeDefinition.getTypes()) {
|
||||
coordinates.add(`${typeName}.${member.name}`);
|
||||
}
|
||||
} else if (isEnumType(typeDefinition)) {
|
||||
const values = typeDefinition.getValues();
|
||||
for (const value of values) {
|
||||
coordinates.add(`${typeName}.${value.name}`);
|
||||
if (value.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${value.name}`);
|
||||
}
|
||||
}
|
||||
} else if (isScalarType(typeDefinition)) {
|
||||
//
|
||||
} else {
|
||||
throw new Error(`Unsupported type kind ${typeName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
coordinates,
|
||||
deprecated,
|
||||
};
|
||||
}
|
||||
|
||||
function visitSchemaCoordinatesOfGraphQLFieldMap(
|
||||
typeName: string,
|
||||
fieldMap: GraphQLFieldMap<any, any>,
|
||||
coordinates: Set<string>,
|
||||
deprecated: Set<string>,
|
||||
) {
|
||||
for (const fieldName in fieldMap) {
|
||||
const fieldDefinition = fieldMap[fieldName];
|
||||
|
||||
coordinates.add(`${typeName}.${fieldName}`);
|
||||
if (fieldDefinition.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}`);
|
||||
}
|
||||
|
||||
for (const arg of fieldDefinition.args) {
|
||||
coordinates.add(`${typeName}.${fieldName}.${arg.name}`);
|
||||
if (arg.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}.${arg.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function insertRemainingCoordinates(
|
||||
connection: CommonQueryMethods,
|
||||
targetId: string,
|
||||
targetCoordinates: {
|
||||
coordinates: Set<string>;
|
||||
deprecated: Set<string>;
|
||||
},
|
||||
versionId: string,
|
||||
createdAt: number,
|
||||
) {
|
||||
if (targetCoordinates.coordinates.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pgDate = new Date(createdAt).toISOString();
|
||||
|
||||
// Deprecated only the coordinates that are still in the queue
|
||||
const remainingDeprecated = targetCoordinates.deprecated.intersection(
|
||||
targetCoordinates.coordinates,
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Adding remaining ${targetCoordinates.coordinates.size} coordinates for target ${targetId}`,
|
||||
);
|
||||
await connection.query(sql`
|
||||
INSERT INTO schema_coordinate_status
|
||||
( target_id, coordinate, created_at, created_in_version_id )
|
||||
SELECT * FROM ${sql.unnest(
|
||||
Array.from(targetCoordinates.coordinates).map(coordinate => [
|
||||
targetId,
|
||||
coordinate,
|
||||
pgDate,
|
||||
versionId,
|
||||
]),
|
||||
['uuid', 'text', 'date', 'uuid'],
|
||||
)}
|
||||
ON CONFLICT (target_id, coordinate)
|
||||
DO UPDATE SET created_at = ${pgDate}, created_in_version_id = ${versionId}
|
||||
`);
|
||||
|
||||
if (remainingDeprecated.size) {
|
||||
console.log(
|
||||
`Deprecating remaining ${remainingDeprecated.size} coordinates for target ${targetId}`,
|
||||
);
|
||||
await connection.query(sql`
|
||||
INSERT INTO schema_coordinate_status
|
||||
( target_id, coordinate, created_at, created_in_version_id, deprecated_at, deprecated_in_version_id )
|
||||
SELECT * FROM ${sql.unnest(
|
||||
Array.from(remainingDeprecated).map(coordinate => [
|
||||
targetId,
|
||||
coordinate,
|
||||
pgDate,
|
||||
versionId,
|
||||
pgDate,
|
||||
versionId,
|
||||
]),
|
||||
['uuid', 'text', 'date', 'uuid', 'date', 'uuid'],
|
||||
)}
|
||||
ON CONFLICT (target_id, coordinate)
|
||||
DO UPDATE SET deprecated_at = ${pgDate}, deprecated_in_version_id = ${versionId}
|
||||
`);
|
||||
// there will be a conflict, because we are going from deprecated to added order.
|
||||
}
|
||||
}
|
||||
|
||||
async function processVersion(
|
||||
depth: number,
|
||||
connection: CommonQueryMethods,
|
||||
targetCoordinates: {
|
||||
coordinates: Set<string>;
|
||||
deprecated: Set<string>;
|
||||
},
|
||||
targetId: string,
|
||||
after: {
|
||||
schema: GraphQLSchema;
|
||||
versionId: string;
|
||||
createdAt: number;
|
||||
previousVersionId: string | null;
|
||||
},
|
||||
): Promise<void> {
|
||||
console.log(`Processing target %s at depth %s - version`, targetId, depth, after.versionId);
|
||||
const previousVersionId = after.previousVersionId;
|
||||
if (!previousVersionId) {
|
||||
// Seems like there is no previous version.
|
||||
console.log(
|
||||
`[END] No previous version found. Inserting all remaining coordinates for ${targetId}`,
|
||||
);
|
||||
await insertRemainingCoordinates(
|
||||
connection,
|
||||
targetId,
|
||||
targetCoordinates,
|
||||
after.versionId,
|
||||
after.createdAt,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const versionBefore = await connection.maybeOne<{
|
||||
id: string;
|
||||
sdl?: string;
|
||||
previous_schema_version_id?: string;
|
||||
created_at: number;
|
||||
is_composable: boolean;
|
||||
}>(sql`
|
||||
SELECT
|
||||
id,
|
||||
composite_schema_sdl as sdl,
|
||||
previous_schema_version_id,
|
||||
created_at,
|
||||
is_composable
|
||||
FROM schema_versions
|
||||
WHERE id = ${previousVersionId} AND target_id = ${targetId}
|
||||
`);
|
||||
|
||||
if (!versionBefore) {
|
||||
console.error(
|
||||
`[ERROR] No schema found for version ${previousVersionId}. Inserting all remaining coordinates for ${targetId}`,
|
||||
);
|
||||
await insertRemainingCoordinates(
|
||||
connection,
|
||||
targetId,
|
||||
targetCoordinates,
|
||||
after.versionId,
|
||||
after.createdAt,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!versionBefore.is_composable) {
|
||||
// Skip non-composable schemas and continue with the previous version.
|
||||
return processVersion(depth + 1, connection, targetCoordinates, targetId, {
|
||||
schema: after.schema,
|
||||
versionId: after.versionId,
|
||||
createdAt: after.createdAt,
|
||||
previousVersionId: versionBefore.previous_schema_version_id ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
if (!versionBefore.sdl) {
|
||||
console.error(
|
||||
`[ERROR] No SDL found for version ${previousVersionId}. Inserting all remaining coordinates for ${targetId}`,
|
||||
);
|
||||
await insertRemainingCoordinates(
|
||||
connection,
|
||||
targetId,
|
||||
targetCoordinates,
|
||||
after.versionId,
|
||||
after.createdAt,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const before: {
|
||||
schema: GraphQLSchema;
|
||||
versionId: string;
|
||||
createdAt: number;
|
||||
previousVersionId: string | null;
|
||||
} = {
|
||||
schema: buildSchema(versionBefore.sdl, {
|
||||
assumeValid: true,
|
||||
assumeValidSDL: true,
|
||||
}),
|
||||
versionId: versionBefore.id,
|
||||
createdAt: versionBefore.created_at,
|
||||
previousVersionId: versionBefore.previous_schema_version_id ?? null,
|
||||
};
|
||||
const diff = diffSchemaCoordinates(before.schema, after.schema);
|
||||
|
||||
// We don't have to track undeprecated or deleted coordinates
|
||||
// as we only want to represent the current state of the schema.
|
||||
const added: string[] = [];
|
||||
const deprecated: string[] = [];
|
||||
const deleteAdded = new Set<string>();
|
||||
const deleteDeprecated = new Set<string>();
|
||||
|
||||
for (const coordinate of diff.added) {
|
||||
if (targetCoordinates.coordinates.has(coordinate)) {
|
||||
added.push(coordinate);
|
||||
// We found a schema version that added a coordinate, so we don't have to look further
|
||||
deleteAdded.add(coordinate);
|
||||
}
|
||||
}
|
||||
|
||||
for (const coordinate of diff.deprecated) {
|
||||
if (targetCoordinates.deprecated.has(coordinate)) {
|
||||
deprecated.push(coordinate);
|
||||
deleteDeprecated.add(coordinate);
|
||||
}
|
||||
}
|
||||
|
||||
const datePG = new Date(after.createdAt).toISOString();
|
||||
|
||||
if (added.length) {
|
||||
console.log(`Adding ${added.length} coordinates for target ${targetId}`);
|
||||
await connection.query(sql`
|
||||
INSERT INTO schema_coordinate_status
|
||||
( target_id, coordinate, created_at, created_in_version_id )
|
||||
SELECT * FROM ${sql.unnest(
|
||||
added.map(coordinate => [targetId, coordinate, datePG, after.versionId]),
|
||||
['uuid', 'text', 'date', 'uuid'],
|
||||
)}
|
||||
ON CONFLICT (target_id, coordinate)
|
||||
DO UPDATE SET created_at = ${datePG}, created_in_version_id = ${after.versionId}
|
||||
`);
|
||||
// there will be a conflict, because we are going from deprecated to added order.
|
||||
}
|
||||
|
||||
if (deprecated.length) {
|
||||
console.log(`deprecating ${deprecated.length} coordinates for target ${targetId}`);
|
||||
|
||||
await connection.query(sql`
|
||||
INSERT INTO schema_coordinate_status
|
||||
( target_id, coordinate, created_at, created_in_version_id, deprecated_at, deprecated_in_version_id )
|
||||
SELECT * FROM ${sql.unnest(
|
||||
deprecated.map(coordinate => [
|
||||
targetId,
|
||||
coordinate,
|
||||
datePG,
|
||||
after.versionId,
|
||||
datePG,
|
||||
after.versionId,
|
||||
]),
|
||||
['uuid', 'text', 'date', 'uuid', 'date', 'uuid'],
|
||||
)}
|
||||
ON CONFLICT (target_id, coordinate)
|
||||
DO UPDATE SET deprecated_at = ${datePG}, deprecated_in_version_id = ${after.versionId}
|
||||
`);
|
||||
// there will be a conflict, because we are going from deprecated to added order.
|
||||
}
|
||||
|
||||
// Remove coordinates that were added in this diff.
|
||||
// We don't need to look for them in previous versions.
|
||||
for (const coordinate of deleteAdded) {
|
||||
targetCoordinates.coordinates.delete(coordinate);
|
||||
}
|
||||
// Remove coordinates that were deprecated in this diff.
|
||||
// To avoid marking them as deprecated later on.
|
||||
for (const coordinate of deleteDeprecated) {
|
||||
targetCoordinates.deprecated.delete(coordinate);
|
||||
}
|
||||
|
||||
if (deleteAdded.size) {
|
||||
console.log(`Deleted ${deleteAdded.size} coordinates from the stack`);
|
||||
console.log(`Coordinates in queue: ${targetCoordinates.coordinates.size}`);
|
||||
}
|
||||
|
||||
return processVersion(depth + 1, connection, targetCoordinates, targetId, before);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
import { createPool } from 'slonik';
|
||||
import { schemaCoordinateStatusMigration } from './actions/2024.07.23T09.36.00.schema-cleanup-tracker';
|
||||
import { migrateClickHouse } from './clickhouse';
|
||||
import { createConnectionString } from './connection-string';
|
||||
import { env } from './environment';
|
||||
|
|
@ -13,9 +14,21 @@ const slonik = await createPool(createConnectionString(env.postgres), {
|
|||
// This is used by production build of this package.
|
||||
// We are building a "cli" out of the package, so we need a workaround to pass the command to run.
|
||||
|
||||
console.log('Running the UP migrations');
|
||||
// This is only used for GraphQL Hive Cloud to perform a long running migration.
|
||||
// eslint-disable-next-line no-process-env
|
||||
if (process.env.SCHEMA_COORDINATE_STATUS_MIGRATION === '1') {
|
||||
try {
|
||||
console.log('Running the SCHEMA_COORDINATE_STATUS_MIGRATION');
|
||||
await schemaCoordinateStatusMigration(slonik);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Running the UP migrations');
|
||||
await runPGMigrations({ slonik });
|
||||
if (env.clickhouse) {
|
||||
await migrateClickHouse(env.isClickHouseMigrator, env.isHiveCloud, env.clickhouse);
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import migration_2024_04_09T10_10_00_check_approval_comment from './actions/2024
|
|||
import migration_2024_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11T10-10-00.ms-teams-webhook';
|
||||
import migration_2024_07_16T13_44_00_oidc_only_access from './actions/2024.07.16T13-44-00.oidc-only-access';
|
||||
import migration_2024_07_17T00_00_00_app_deployments from './actions/2024.07.17T00-00-00.app-deployments';
|
||||
import migration_2024_07_23T_09_36_00_schema_cleanup_tracker from './actions/2024.07.23T09.36.00.schema-cleanup-tracker';
|
||||
import { runMigrations } from './pg-migrator';
|
||||
|
||||
export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string }) =>
|
||||
|
|
@ -140,5 +141,6 @@ export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string })
|
|||
migration_2024_06_11T10_10_00_ms_teams_webhook,
|
||||
migration_2024_07_16T13_44_00_oidc_only_access,
|
||||
migration_2024_07_17T00_00_00_app_deployments,
|
||||
migration_2024_07_23T_09_36_00_schema_cleanup_tracker,
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,246 @@
|
|||
import assert from 'node:assert';
|
||||
import { describe, test } from 'node:test';
|
||||
import { sql } from 'slonik';
|
||||
import { createStorage } from '../../services/storage/src/index';
|
||||
import { initMigrationTestingEnvironment } from './utils/testkit';
|
||||
|
||||
await describe('migration: schema-cleanup-tracker', async () => {
|
||||
await test('schema coordinates backfill', async () => {
|
||||
const { db, runTo, complete, done, seed, connectionString } =
|
||||
await initMigrationTestingEnvironment();
|
||||
const storage = await createStorage(connectionString, 1);
|
||||
try {
|
||||
// Run migrations all the way to the point before the one we are testing
|
||||
await runTo('2024.07.17T00-00-00.app-deployments.ts');
|
||||
|
||||
// Seed the database with some data (schema_sdl, supergraph_sdl, composite_schema_sdl)
|
||||
const admin = await seed.user({
|
||||
user: {
|
||||
name: 'test1',
|
||||
email: 'test1@test.com',
|
||||
},
|
||||
});
|
||||
|
||||
const organization = await seed.organization({
|
||||
organization: {
|
||||
name: 'org-1',
|
||||
},
|
||||
user: admin,
|
||||
});
|
||||
|
||||
const project = await seed.project({
|
||||
project: {
|
||||
name: 'project-1',
|
||||
type: 'SINGLE',
|
||||
},
|
||||
organization,
|
||||
});
|
||||
|
||||
const target = await seed.target({
|
||||
target: {
|
||||
name: 'target-1',
|
||||
},
|
||||
project,
|
||||
});
|
||||
|
||||
async function createVersion(
|
||||
schema: string,
|
||||
previousSchemaVersionId: string | null,
|
||||
): Promise<string> {
|
||||
const logId = await db.oneFirst<string>(sql`
|
||||
INSERT INTO schema_log
|
||||
(
|
||||
author,
|
||||
service_name,
|
||||
service_url,
|
||||
commit,
|
||||
sdl,
|
||||
project_id,
|
||||
target_id,
|
||||
metadata,
|
||||
action
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
${'Kamil'},
|
||||
${null},
|
||||
${null},
|
||||
${'random'},
|
||||
${schema},
|
||||
${project.id},
|
||||
${target.id},
|
||||
${null},
|
||||
'PUSH'
|
||||
)
|
||||
RETURNING id
|
||||
`);
|
||||
|
||||
const versionId = await db.oneFirst<string>(sql`
|
||||
INSERT INTO schema_versions
|
||||
(
|
||||
record_version,
|
||||
is_composable,
|
||||
target_id,
|
||||
action_id,
|
||||
base_schema,
|
||||
has_persisted_schema_changes,
|
||||
previous_schema_version_id,
|
||||
diff_schema_version_id,
|
||||
composite_schema_sdl,
|
||||
supergraph_sdl,
|
||||
schema_composition_errors,
|
||||
github_repository,
|
||||
github_sha,
|
||||
tags,
|
||||
has_contract_composition_errors,
|
||||
conditional_breaking_change_metadata
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'2024-01-10',
|
||||
${true},
|
||||
${target.id},
|
||||
${logId},
|
||||
${null},
|
||||
${true},
|
||||
${previousSchemaVersionId},
|
||||
${previousSchemaVersionId},
|
||||
${schema},
|
||||
${null},
|
||||
${null},
|
||||
${null},
|
||||
${null},
|
||||
${null},
|
||||
${false},
|
||||
${null}
|
||||
)
|
||||
RETURNING id
|
||||
`);
|
||||
|
||||
return versionId;
|
||||
}
|
||||
|
||||
const schemas = [
|
||||
// [0]
|
||||
// first
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`,
|
||||
// [1]
|
||||
// second
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String
|
||||
}
|
||||
`,
|
||||
// [2]
|
||||
// third
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`,
|
||||
// [3]
|
||||
// fourth
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String
|
||||
}
|
||||
`,
|
||||
// [4]
|
||||
// fifth
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String @deprecated(reason: "no longer needed")
|
||||
bye: String
|
||||
goodbye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
// [5]
|
||||
// sixth
|
||||
/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
bye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
// insert schema versions
|
||||
let previousSchemaVersionId: string | null = null;
|
||||
for await (const schema of schemas) {
|
||||
const versionId = await createVersion(schema, previousSchemaVersionId);
|
||||
previousSchemaVersionId = versionId;
|
||||
}
|
||||
|
||||
// Run the remaining migrations
|
||||
await complete();
|
||||
|
||||
// check that coordinates are correct
|
||||
|
||||
const versions = await db.manyFirst<string>(sql`
|
||||
SELECT id FROM schema_versions WHERE target_id = ${target.id} ORDER BY created_at ASC
|
||||
`);
|
||||
|
||||
const coordinates = await db.many<{
|
||||
coordinate: string;
|
||||
created_in_version_id: string;
|
||||
deprecated_in_version_id: string | null;
|
||||
}>(sql`
|
||||
SELECT * FROM schema_coordinate_status WHERE target_id = ${target.id}
|
||||
`);
|
||||
|
||||
assert.strictEqual(versions.length, 6);
|
||||
|
||||
const queryType = coordinates.find(c => c.coordinate === 'Query');
|
||||
const helloField = coordinates.find(c => c.coordinate === 'Query.hello');
|
||||
const hiField = coordinates.find(c => c.coordinate === 'Query.hi');
|
||||
const byeField = coordinates.find(c => c.coordinate === 'Query.bye');
|
||||
const goodbyeField = coordinates.find(c => c.coordinate === 'Query.goodbye');
|
||||
|
||||
assert.ok(queryType, 'Query type not found');
|
||||
assert.ok(helloField, 'Query.hello field not found');
|
||||
assert.ok(hiField, 'Query.hi field not found');
|
||||
assert.ok(byeField, 'Query.bye field not found');
|
||||
|
||||
// Query
|
||||
// was create in the first version
|
||||
// never deprecated
|
||||
assert.strictEqual(queryType.created_in_version_id, versions[0]);
|
||||
assert.strictEqual(queryType.deprecated_in_version_id, null);
|
||||
|
||||
// Query.hello
|
||||
// was created in the first version,
|
||||
// deprecated in fifth
|
||||
// undeprecated in the sixth
|
||||
assert.strictEqual(helloField.created_in_version_id, versions[0]);
|
||||
assert.strictEqual(helloField.deprecated_in_version_id, null);
|
||||
|
||||
// Query.hi
|
||||
// was created in the second version
|
||||
// removed in the third
|
||||
// added back in the fourth
|
||||
// deprecated in the fifth
|
||||
assert.strictEqual(hiField.created_in_version_id, versions[3]);
|
||||
assert.strictEqual(hiField.deprecated_in_version_id, versions[4]);
|
||||
|
||||
// Query.bye
|
||||
// was created in the fifth version
|
||||
assert.strictEqual(byeField.created_in_version_id, versions[4]);
|
||||
|
||||
// Query.goodbye
|
||||
// was created in the fifth version
|
||||
// removed in the sixth
|
||||
assert.ok(!goodbyeField, 'Query.goodbye field should not be found');
|
||||
} finally {
|
||||
await done();
|
||||
await storage.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -2,3 +2,4 @@ import './2023.02.22T09.27.02.delete-personal-org.test';
|
|||
import './2023.09.25T15.23.00.github-check-with-project-name.test';
|
||||
import './2023.11.20T10-00-00.organization-member-roles.test';
|
||||
import './2024.01.26T00.00.01.schema-check-purging.test';
|
||||
import './2024.07.23T09.36.00.schema-cleanup-tracker.test';
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"slonik": "30.4.4",
|
||||
"supertokens-node": "15.2.1",
|
||||
"tslib": "2.6.3",
|
||||
"vitest": "1.6.0",
|
||||
"vitest": "2.0.3",
|
||||
"zod": "3.23.8",
|
||||
"zod-validation-error": "3.3.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,371 @@
|
|||
import 'reflect-metadata';
|
||||
import { buildSchema } from 'graphql';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { diffSchemaCoordinates } from './inspector';
|
||||
|
||||
describe('diffSchemaCoordinates', () => {
|
||||
test('should return empty arrays when schemas are equal', () => {
|
||||
const schema = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(schema, schema);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('field becomes deprecated', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
goodbye: String
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String @deprecated
|
||||
goodbye: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {},
|
||||
deprecated: Set {
|
||||
Query.hello,
|
||||
},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('added field is deprecated', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String @deprecated
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
Query.hi,
|
||||
},
|
||||
deleted: Set {},
|
||||
deprecated: Set {
|
||||
Query.hi,
|
||||
},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('field is deleted', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
goodbye: String
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hi: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
Query.hi,
|
||||
},
|
||||
deleted: Set {
|
||||
Query.goodbye,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('deprecated field is deleted', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
goodbye: String @deprecated
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {
|
||||
Query.goodbye,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('deprecated field is undeprecated', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String @deprecated
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
hi: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {
|
||||
Query.hi,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('deprecated field is undeprecated, undeprecated field is deprecated', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String @deprecated(reason: "no longer needed")
|
||||
bye: String
|
||||
goodbye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
bye: String
|
||||
hi: String @deprecated(reason: "no longer needed")
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {
|
||||
Query.goodbye,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {
|
||||
Query.hello,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('removed a deprecated field', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
goodbye: String @deprecated
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {},
|
||||
deleted: Set {
|
||||
Query.goodbye,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('added and removed an argument on deprecated and non-deprecated fields', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello(lang: String): String
|
||||
hi: String @deprecated
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String @deprecated
|
||||
hi(lang: String): String
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
Query.hi.lang,
|
||||
},
|
||||
deleted: Set {
|
||||
Query.hello.lang,
|
||||
},
|
||||
deprecated: Set {
|
||||
Query.hello,
|
||||
},
|
||||
undeprecated: Set {
|
||||
Query.hi,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('added removed enum members', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
enum Role {
|
||||
ADMIN
|
||||
USER
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
enum Role {
|
||||
ANONYMOUS
|
||||
USER
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
Role.ANONYMOUS,
|
||||
},
|
||||
deleted: Set {
|
||||
Role.ADMIN,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('added removed union members', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
union Account = Admin | User
|
||||
type Admin {
|
||||
id: ID
|
||||
}
|
||||
type User {
|
||||
id: ID
|
||||
}
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
union Account = Anonymous | User
|
||||
type Anonymous {
|
||||
ip: String
|
||||
}
|
||||
type User {
|
||||
id: ID
|
||||
}
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
Account.Anonymous,
|
||||
Anonymous,
|
||||
Anonymous.ip,
|
||||
},
|
||||
deleted: Set {
|
||||
Account.Admin,
|
||||
Admin,
|
||||
Admin.id,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('added removed scalars', () => {
|
||||
const before = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
scalar GOODBYE
|
||||
`);
|
||||
const after = buildSchema(/* GraphQL */ `
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
scalar HELLO
|
||||
`);
|
||||
|
||||
const result = diffSchemaCoordinates(before, after);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
added: Set {
|
||||
HELLO,
|
||||
},
|
||||
deleted: Set {
|
||||
GOODBYE,
|
||||
},
|
||||
deprecated: Set {},
|
||||
undeprecated: Set {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,18 @@
|
|||
import { type GraphQLSchema } from 'graphql';
|
||||
import {
|
||||
GraphQLFieldMap,
|
||||
isEnumType,
|
||||
isInputObjectType,
|
||||
isInterfaceType,
|
||||
isIntrospectionType,
|
||||
isObjectType,
|
||||
isScalarType,
|
||||
isUnionType,
|
||||
type GraphQLSchema,
|
||||
} from 'graphql';
|
||||
import { Injectable, Scope } from 'graphql-modules';
|
||||
import { Change, ChangeType, diff } from '@graphql-inspector/core';
|
||||
import { traceFn } from '@hive/service-common';
|
||||
import { HiveSchemaChangeModel, SchemaChangeType } from '@hive/storage';
|
||||
import { HiveSchemaChangeModel } from '@hive/storage';
|
||||
import { Logger } from '../../shared/providers/logger';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -21,7 +31,7 @@ export class Inspector {
|
|||
'hive.diff.changes.count': result.length,
|
||||
}),
|
||||
})
|
||||
async diff(existing: GraphQLSchema, incoming: GraphQLSchema): Promise<Array<SchemaChangeType>> {
|
||||
async diff(existing: GraphQLSchema, incoming: GraphQLSchema) {
|
||||
this.logger.debug('Comparing Schemas');
|
||||
|
||||
const changes = await diff(existing, incoming);
|
||||
|
|
@ -120,3 +130,130 @@ function matchChange<R, T extends ChangeType>(
|
|||
return pattern[change.type]?.(change);
|
||||
}
|
||||
}
|
||||
|
||||
export type SchemaCoordinatesDiffResult = {
|
||||
/**
|
||||
* Coordinates that are in incoming but not in existing (including deprecated ones)
|
||||
*/
|
||||
added: Set<string>;
|
||||
/**
|
||||
* Coordinates that are in existing but not in incoming (including deprecated ones)
|
||||
*/
|
||||
deleted: Set<string>;
|
||||
/**
|
||||
* Coordinates that are deprecated in incoming, but were not deprecated in existing or non-existent
|
||||
*/
|
||||
deprecated: Set<string>;
|
||||
/**
|
||||
* Coordinates that exists in incoming and are not deprecated in incoming, but were deprecated in existing
|
||||
*/
|
||||
undeprecated: Set<string>;
|
||||
};
|
||||
|
||||
export function diffSchemaCoordinates(
|
||||
existingSchema: GraphQLSchema | null,
|
||||
incomingSchema: GraphQLSchema,
|
||||
): SchemaCoordinatesDiffResult {
|
||||
const before = existingSchema
|
||||
? getSchemaCoordinates(existingSchema)
|
||||
: { coordinates: new Set<string>(), deprecated: new Set<string>() };
|
||||
const after = getSchemaCoordinates(incomingSchema);
|
||||
|
||||
const added = after.coordinates.difference(before.coordinates);
|
||||
const deleted = before.coordinates.difference(after.coordinates);
|
||||
const deprecated = after.deprecated.difference(before.deprecated);
|
||||
const undeprecated = before.deprecated
|
||||
.difference(after.deprecated)
|
||||
.intersection(after.coordinates);
|
||||
|
||||
return {
|
||||
added,
|
||||
deleted,
|
||||
deprecated,
|
||||
undeprecated,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSchemaCoordinates(schema: GraphQLSchema): {
|
||||
coordinates: Set<string>;
|
||||
deprecated: Set<string>;
|
||||
} {
|
||||
const coordinates = new Set<string>();
|
||||
const deprecated = new Set<string>();
|
||||
|
||||
const typeMap = schema.getTypeMap();
|
||||
|
||||
for (const typeName in typeMap) {
|
||||
const typeDefinition = typeMap[typeName];
|
||||
|
||||
if (isIntrospectionType(typeDefinition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
coordinates.add(typeName);
|
||||
|
||||
if (isObjectType(typeDefinition) || isInterfaceType(typeDefinition)) {
|
||||
visitSchemaCoordinatesOfGraphQLFieldMap(
|
||||
typeName,
|
||||
typeDefinition.getFields(),
|
||||
coordinates,
|
||||
deprecated,
|
||||
);
|
||||
} else if (isInputObjectType(typeDefinition)) {
|
||||
const fieldMap = typeDefinition.getFields();
|
||||
for (const fieldName in fieldMap) {
|
||||
const fieldDefinition = fieldMap[fieldName];
|
||||
|
||||
coordinates.add(`${typeName}.${fieldName}`);
|
||||
if (fieldDefinition.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}`);
|
||||
}
|
||||
}
|
||||
} else if (isUnionType(typeDefinition)) {
|
||||
coordinates.add(typeName);
|
||||
for (const member of typeDefinition.getTypes()) {
|
||||
coordinates.add(`${typeName}.${member.name}`);
|
||||
}
|
||||
} else if (isEnumType(typeDefinition)) {
|
||||
const values = typeDefinition.getValues();
|
||||
for (const value of values) {
|
||||
coordinates.add(`${typeName}.${value.name}`);
|
||||
if (value.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${value.name}`);
|
||||
}
|
||||
}
|
||||
} else if (isScalarType(typeDefinition)) {
|
||||
coordinates.add(typeName);
|
||||
} else {
|
||||
throw new Error(`Unsupported type kind ${typeName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
coordinates,
|
||||
deprecated,
|
||||
};
|
||||
}
|
||||
|
||||
function visitSchemaCoordinatesOfGraphQLFieldMap(
|
||||
typeName: string,
|
||||
fieldMap: GraphQLFieldMap<any, any>,
|
||||
coordinates: Set<string>,
|
||||
deprecated: Set<string>,
|
||||
) {
|
||||
for (const fieldName in fieldMap) {
|
||||
const fieldDefinition = fieldMap[fieldName];
|
||||
|
||||
coordinates.add(`${typeName}.${fieldName}`);
|
||||
if (fieldDefinition.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}`);
|
||||
}
|
||||
|
||||
for (const arg of fieldDefinition.args) {
|
||||
coordinates.add(`${typeName}.${fieldName}.${arg.name}`);
|
||||
if (arg.deprecationReason) {
|
||||
deprecated.add(`${typeName}.${fieldName}.${arg.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,6 +347,11 @@ export class CompositeLegacyModel {
|
|||
messages,
|
||||
changes,
|
||||
breakingChanges: breakingChanges ?? null,
|
||||
coordinatesDiff:
|
||||
diffCheck.result?.coordinatesDiff ??
|
||||
diffCheck.reason?.coordinatesDiff ??
|
||||
diffCheck.data?.coordinatesDiff ??
|
||||
null,
|
||||
compositionErrors,
|
||||
schema: incoming,
|
||||
schemas,
|
||||
|
|
@ -372,6 +377,7 @@ export class CompositeLegacyModel {
|
|||
code: PublishFailureReasonCode.BreakingChanges,
|
||||
changes: diffCheck.reason.all ?? [],
|
||||
breakingChanges: diffCheck.reason.breaking ?? [],
|
||||
coordinatesDiff: diffCheck.reason?.coordinatesDiff ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -483,6 +483,11 @@ export class CompositeModel {
|
|||
composable: compositionCheck.status === 'completed',
|
||||
initial: latestVersion === null,
|
||||
changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null,
|
||||
coordinatesDiff:
|
||||
diffCheck.result?.coordinatesDiff ??
|
||||
diffCheck.reason?.coordinatesDiff ??
|
||||
diffCheck.data?.coordinatesDiff ??
|
||||
null,
|
||||
messages,
|
||||
breakingChanges: null,
|
||||
compositionErrors: compositionCheck.reason?.errors ?? null,
|
||||
|
|
@ -688,6 +693,11 @@ export class CompositeModel {
|
|||
...composablePartial,
|
||||
changes,
|
||||
breakingChanges,
|
||||
coordinatesDiff:
|
||||
diffCheck.result?.coordinatesDiff ??
|
||||
diffCheck.reason?.coordinatesDiff ??
|
||||
diffCheck.data?.coordinatesDiff ??
|
||||
null,
|
||||
compositionErrors: compositionCheck.reason?.errors ?? [],
|
||||
supergraph: compositionCheck.result?.supergraph ?? null,
|
||||
tags: compositionCheck.result?.tags ?? null,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ import { PushedCompositeSchema, SingleSchema } from 'packages/services/api/src/s
|
|||
import type { CheckPolicyResponse } from '@hive/policy';
|
||||
import { CompositionFailureError } from '@hive/schema';
|
||||
import type { SchemaChangeType, SchemaCompositionError } from '@hive/storage';
|
||||
import { type Contract, type ValidContractVersion } from '../contracts';
|
||||
import {
|
||||
import type { Contract, ValidContractVersion } from '../contracts';
|
||||
import type { SchemaCoordinatesDiffResult } from '../inspector';
|
||||
import type {
|
||||
ContractCompositionResult,
|
||||
ContractCompositionSuccess,
|
||||
RegistryChecks,
|
||||
SchemaDiffResult,
|
||||
SchemaDiffSkip,
|
||||
SchemaDiffSuccess,
|
||||
type RegistryChecks,
|
||||
} from '../registry-checks';
|
||||
|
||||
export const SchemaPublishConclusion = {
|
||||
|
|
@ -207,6 +208,7 @@ export type SchemaPublishFailureReason =
|
|||
code: (typeof PublishFailureReasonCode)['BreakingChanges'];
|
||||
breakingChanges: Array<SchemaChangeType>;
|
||||
changes: Array<SchemaChangeType>;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult;
|
||||
};
|
||||
|
||||
type ContractResult = {
|
||||
|
|
@ -223,6 +225,7 @@ type SchemaPublishSuccess = {
|
|||
state: {
|
||||
composable: boolean;
|
||||
initial: boolean;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
changes: Array<SchemaChangeType> | null;
|
||||
messages: string[] | null;
|
||||
breakingChanges: Array<{
|
||||
|
|
@ -277,6 +280,7 @@ export type SchemaDeleteSuccess = {
|
|||
schemas: PushedCompositeSchema[];
|
||||
breakingChanges: Array<SchemaChangeType> | null;
|
||||
compositionErrors: Array<SchemaCompositionError> | null;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
supergraph: string | null;
|
||||
tags: null | Array<string>;
|
||||
contracts: null | Array<ContractResult>;
|
||||
|
|
|
|||
|
|
@ -279,6 +279,11 @@ export class SingleLegacyModel {
|
|||
messages,
|
||||
changes,
|
||||
breakingChanges: breakingChanges ?? null,
|
||||
coordinatesDiff:
|
||||
diffCheck.result?.coordinatesDiff ??
|
||||
diffCheck.reason?.coordinatesDiff ??
|
||||
diffCheck.data?.coordinatesDiff ??
|
||||
null,
|
||||
compositionErrors,
|
||||
schema: incoming,
|
||||
schemas,
|
||||
|
|
@ -304,6 +309,7 @@ export class SingleLegacyModel {
|
|||
code: PublishFailureReasonCode.BreakingChanges,
|
||||
changes: diffCheck.reason.all ?? [],
|
||||
breakingChanges: diffCheck.reason.breaking ?? [],
|
||||
coordinatesDiff: diffCheck.reason?.coordinatesDiff ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,11 @@ export class SingleModel {
|
|||
changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null,
|
||||
messages,
|
||||
breakingChanges: null,
|
||||
coordinatesDiff:
|
||||
diffCheck.result?.coordinatesDiff ??
|
||||
diffCheck.reason?.coordinatesDiff ??
|
||||
diffCheck.data?.coordinatesDiff ??
|
||||
null,
|
||||
compositionErrors: compositionCheck.reason?.errors ?? null,
|
||||
schema: incoming,
|
||||
schemas,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import type {
|
|||
SingleSchema,
|
||||
} from './../../../shared/entities';
|
||||
import { Logger } from './../../shared/providers/logger';
|
||||
import { Inspector } from './inspector';
|
||||
import { diffSchemaCoordinates, Inspector, SchemaCoordinatesDiffResult } from './inspector';
|
||||
import { SchemaCheckWarning } from './models/shared';
|
||||
import { extendWithBase, isCompositeSchema, SchemaHelper } from './schema-helper';
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export type ConditionalBreakingChangeDiffConfig = {
|
|||
|
||||
// The reason why I'm using `result` and `reason` instead of just `data` for both:
|
||||
// https://bit.ly/hive-check-result-data
|
||||
export type CheckResult<C = unknown, F = unknown> =
|
||||
export type CheckResult<C = unknown, F = unknown, S = unknown> =
|
||||
| {
|
||||
status: 'completed';
|
||||
result: C;
|
||||
|
|
@ -48,6 +48,7 @@ export type CheckResult<C = unknown, F = unknown> =
|
|||
}
|
||||
| {
|
||||
status: 'skipped';
|
||||
data?: S;
|
||||
};
|
||||
|
||||
type Schemas = [SingleSchema] | PushedCompositeSchema[];
|
||||
|
|
@ -134,6 +135,7 @@ type SchemaDiffFailure = {
|
|||
breaking: Array<SchemaChangeType> | null;
|
||||
safe: Array<SchemaChangeType> | null;
|
||||
all: Array<SchemaChangeType> | null;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
};
|
||||
result?: never;
|
||||
};
|
||||
|
|
@ -144,6 +146,7 @@ export type SchemaDiffSuccess = {
|
|||
breaking: Array<SchemaChangeType> | null;
|
||||
safe: Array<SchemaChangeType> | null;
|
||||
all: Array<SchemaChangeType> | null;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
};
|
||||
reason?: never;
|
||||
};
|
||||
|
|
@ -412,32 +415,39 @@ export class RegistryChecks {
|
|||
/** Settings for fetching conditional breaking changes. */
|
||||
conditionalBreakingChangeConfig: null | ConditionalBreakingChangeDiffConfig;
|
||||
}) {
|
||||
if (args.existingSdl == null || args.incomingSdl == null) {
|
||||
this.logger.debug('Skip diff check due to either existing or incoming SDL being absent.');
|
||||
let existingSchema: GraphQLSchema | null = null;
|
||||
let incomingSchema: GraphQLSchema | null = null;
|
||||
|
||||
try {
|
||||
existingSchema = args.existingSdl
|
||||
? buildSortedSchemaFromSchemaObject(
|
||||
this.helper.createSchemaObject({
|
||||
sdl: args.existingSdl,
|
||||
}),
|
||||
)
|
||||
: null;
|
||||
|
||||
incomingSchema = args.incomingSdl
|
||||
? buildSortedSchemaFromSchemaObject(
|
||||
this.helper.createSchemaObject({
|
||||
sdl: args.incomingSdl,
|
||||
}),
|
||||
)
|
||||
: null;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to build schema for diff. Skip diff check.');
|
||||
return {
|
||||
status: 'skipped',
|
||||
} satisfies CheckResult;
|
||||
}
|
||||
|
||||
let existingSchema: GraphQLSchema;
|
||||
let incomingSchema: GraphQLSchema;
|
||||
|
||||
try {
|
||||
existingSchema = buildSortedSchemaFromSchemaObject(
|
||||
this.helper.createSchemaObject({
|
||||
sdl: args.existingSdl,
|
||||
}),
|
||||
);
|
||||
|
||||
incomingSchema = buildSortedSchemaFromSchemaObject(
|
||||
this.helper.createSchemaObject({
|
||||
sdl: args.incomingSdl,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to build schema for diff. Skip diff check.');
|
||||
if (existingSchema === null || incomingSchema === null) {
|
||||
this.logger.debug('Skip diff check due to either existing or incoming SDL being absent.');
|
||||
return {
|
||||
status: 'skipped',
|
||||
data: {
|
||||
coordinatesDiff: incomingSchema ? diffSchemaCoordinates(null, incomingSchema) : null,
|
||||
},
|
||||
} satisfies CheckResult;
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +521,8 @@ export class RegistryChecks {
|
|||
const safeChanges: Array<SchemaChangeType> = [];
|
||||
const breakingChanges: Array<SchemaChangeType> = [];
|
||||
|
||||
const coordinatesDiff = diffSchemaCoordinates(existingSchema, incomingSchema);
|
||||
|
||||
for (const change of inspectorChanges) {
|
||||
if (change.criticality === CriticalityLevel.Breaking) {
|
||||
if (change.isSafeBasedOnUsage === true) {
|
||||
|
|
@ -549,6 +561,7 @@ export class RegistryChecks {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
coordinatesDiff,
|
||||
},
|
||||
} satisfies SchemaDiffFailure;
|
||||
}
|
||||
|
|
@ -568,6 +581,7 @@ export class RegistryChecks {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
coordinatesDiff,
|
||||
},
|
||||
} satisfies SchemaDiffSuccess;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import { TargetManager } from '../../target/providers/target-manager';
|
|||
import { BreakingSchemaChangeUsageHelper } from './breaking-schema-changes-helper';
|
||||
import { SCHEMA_MODULE_CONFIG, type SchemaModuleConfig } from './config';
|
||||
import { Contracts } from './contracts';
|
||||
import type { SchemaCoordinatesDiffResult } from './inspector';
|
||||
import { FederationOrchestrator } from './orchestrators/federation';
|
||||
import { SingleOrchestrator } from './orchestrators/single';
|
||||
import { StitchingOrchestrator } from './orchestrators/stitching';
|
||||
|
|
@ -427,6 +428,7 @@ export class SchemaManager {
|
|||
projectType: ProjectType;
|
||||
actionFn(): Promise<void>;
|
||||
changes: Array<SchemaChangeType>;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
previousSchemaVersion: string | null;
|
||||
diffSchemaVersionId: string | null;
|
||||
github: null | {
|
||||
|
|
@ -460,13 +462,18 @@ export class SchemaManager {
|
|||
) {
|
||||
this.logger.info(
|
||||
'Creating a new version (input=%o)',
|
||||
lodash.omit(input, [
|
||||
'schema',
|
||||
'actionFn',
|
||||
'changes',
|
||||
'compositeSchemaSDL',
|
||||
'supergraphSDL',
|
||||
'schemaCompositionErrors',
|
||||
lodash.pick(input, [
|
||||
'commit',
|
||||
'author',
|
||||
'valid',
|
||||
'service',
|
||||
'logIds',
|
||||
'url',
|
||||
'projectType',
|
||||
'previousSchemaVersion',
|
||||
'diffSchemaVersionId',
|
||||
'github',
|
||||
'conditionalBreakingChangeMetadata',
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1028,7 +1028,14 @@ export class SchemaPublisher {
|
|||
);
|
||||
|
||||
const token = this.authManager.ensureApiToken();
|
||||
const contracts = await this.contracts.getActiveContractsByTargetId({ targetId: input.target });
|
||||
const [contracts, latestVersion] = await Promise.all([
|
||||
this.contracts.getActiveContractsByTargetId({ targetId: input.target }),
|
||||
this.schemaManager.getMaybeLatestVersion({
|
||||
organization: input.organization,
|
||||
project: input.project,
|
||||
target: input.target,
|
||||
}),
|
||||
]);
|
||||
|
||||
const checksum = createHash('md5')
|
||||
.update(
|
||||
|
|
@ -1042,6 +1049,10 @@ export class SchemaPublisher {
|
|||
contractId: contract.id,
|
||||
contractName: contract.contractName,
|
||||
})),
|
||||
// We include the latest version ID to avoid caching a schema publication that targets different versions.
|
||||
// When deleting a schema, and publishing it again, the latest version ID will be different.
|
||||
// If we don't include it, the cache will return the previous result.
|
||||
latestVersionId: latestVersion?.id,
|
||||
}),
|
||||
)
|
||||
.update(token)
|
||||
|
|
@ -1335,6 +1346,7 @@ export class SchemaPublisher {
|
|||
composable: deleteResult.state.composable,
|
||||
diffSchemaVersionId,
|
||||
changes: deleteResult.state.changes,
|
||||
coordinatesDiff: deleteResult.state.coordinatesDiff,
|
||||
contracts:
|
||||
deleteResult.state.contracts?.map(contract => ({
|
||||
contractId: contract.contractId,
|
||||
|
|
@ -1909,6 +1921,7 @@ export class SchemaPublisher {
|
|||
}
|
||||
},
|
||||
changes,
|
||||
coordinatesDiff: publishResult.state.coordinatesDiff,
|
||||
diffSchemaVersionId,
|
||||
previousSchemaVersion: latestVersion?.version ?? null,
|
||||
conditionalBreakingChangeMetadata: await this.getConditionalBreakingChangeMetadata({
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import type { OrganizationAccessScope } from '../../auth/providers/organization-
|
|||
import type { ProjectAccessScope } from '../../auth/providers/project-access';
|
||||
import type { TargetAccessScope } from '../../auth/providers/target-access';
|
||||
import type { Contracts } from '../../schema/providers/contracts';
|
||||
import type { SchemaCoordinatesDiffResult } from '../../schema/providers/inspector';
|
||||
|
||||
export interface OrganizationSelector {
|
||||
organization: string;
|
||||
|
|
@ -442,6 +443,7 @@ export interface Storage {
|
|||
diffSchemaVersionId: string | null;
|
||||
conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata;
|
||||
contracts: null | Array<CreateContractVersionInput>;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
} & TargetSelector &
|
||||
(
|
||||
| {
|
||||
|
|
@ -480,6 +482,7 @@ export interface Storage {
|
|||
};
|
||||
contracts: null | Array<CreateContractVersionInput>;
|
||||
conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult | null;
|
||||
} & TargetSelector) &
|
||||
(
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"itty-router": "4.2.2",
|
||||
"toucan-js": "3.4.0",
|
||||
"undici": "6.19.2",
|
||||
"vitest": "1.6.0",
|
||||
"vitest": "2.0.3",
|
||||
"workers-loki-logger": "0.1.15",
|
||||
"zod": "3.23.8"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -271,6 +271,15 @@ export interface schema_checks {
|
|||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface schema_coordinate_status {
|
||||
coordinate: string;
|
||||
created_at: Date;
|
||||
created_in_version_id: string;
|
||||
deprecated_at: Date | null;
|
||||
deprecated_in_version_id: string | null;
|
||||
target_id: string;
|
||||
}
|
||||
|
||||
export interface schema_log {
|
||||
action: string;
|
||||
author: string;
|
||||
|
|
@ -418,6 +427,7 @@ export interface DBTables {
|
|||
projects: projects;
|
||||
schema_change_approvals: schema_change_approvals;
|
||||
schema_checks: schema_checks;
|
||||
schema_coordinate_status: schema_coordinate_status;
|
||||
schema_log: schema_log;
|
||||
schema_policy_config: schema_policy_config;
|
||||
schema_version_changes: schema_version_changes;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import type {
|
|||
} from '@hive/api';
|
||||
import { context, SpanKind, SpanStatusCode, trace } from '@hive/service-common';
|
||||
import { batch } from '@theguild/buddy';
|
||||
import type { SchemaCoordinatesDiffResult } from '../../api/src/modules/schema/providers/inspector';
|
||||
import {
|
||||
createSDLHash,
|
||||
OrganizationMemberRoleModel,
|
||||
|
|
@ -2644,6 +2645,14 @@ export async function createStorage(
|
|||
});
|
||||
}
|
||||
|
||||
if (args.coordinatesDiff) {
|
||||
await updateSchemaCoordinateStatus(trx, {
|
||||
targetId: args.target,
|
||||
versionId: newVersion.id,
|
||||
coordinatesDiff: args.coordinatesDiff,
|
||||
});
|
||||
}
|
||||
|
||||
for (const contract of args.contracts ?? []) {
|
||||
const schemaVersionContractId = await insertSchemaVersionContract(trx, {
|
||||
schemaVersionId: newVersion.id,
|
||||
|
|
@ -2756,6 +2765,14 @@ export async function createStorage(
|
|||
});
|
||||
}
|
||||
|
||||
if (input.coordinatesDiff) {
|
||||
await updateSchemaCoordinateStatus(trx, {
|
||||
targetId: input.target,
|
||||
versionId: version.id,
|
||||
coordinatesDiff: input.coordinatesDiff,
|
||||
});
|
||||
}
|
||||
|
||||
await input.actionFn();
|
||||
|
||||
return {
|
||||
|
|
@ -5137,6 +5154,80 @@ async function insertSchemaVersionContract(
|
|||
return zod.string().parse(id);
|
||||
}
|
||||
|
||||
async function updateSchemaCoordinateStatus(
|
||||
trx: DatabaseTransactionConnection,
|
||||
args: {
|
||||
targetId: string;
|
||||
versionId: string;
|
||||
coordinatesDiff: SchemaCoordinatesDiffResult;
|
||||
},
|
||||
) {
|
||||
const actions: Promise<unknown>[] = [];
|
||||
|
||||
if (args.coordinatesDiff.deleted) {
|
||||
actions.push(
|
||||
trx.query(sql`/* schema_coordinate_status_deleted */
|
||||
DELETE FROM schema_coordinate_status
|
||||
WHERE
|
||||
target_id = ${args.targetId}
|
||||
AND
|
||||
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.deleted), 'text')})
|
||||
AND
|
||||
created_at <= NOW()
|
||||
`),
|
||||
);
|
||||
}
|
||||
|
||||
if (args.coordinatesDiff.added) {
|
||||
actions.push(
|
||||
trx.query(sql`/* schema_coordinate_status_inserted */
|
||||
INSERT INTO schema_coordinate_status
|
||||
( target_id, coordinate, created_in_version_id, deprecated_at, deprecated_in_version_id )
|
||||
SELECT * FROM ${sql.unnest(
|
||||
Array.from(args.coordinatesDiff.added).map(coordinate => {
|
||||
const isDeprecatedAsWell = args.coordinatesDiff.deprecated.has(coordinate);
|
||||
return [
|
||||
args.targetId,
|
||||
coordinate,
|
||||
args.versionId,
|
||||
// if it's added and deprecated at the same time
|
||||
isDeprecatedAsWell ? 'NOW()' : null,
|
||||
isDeprecatedAsWell ? args.versionId : null,
|
||||
];
|
||||
}),
|
||||
['uuid', 'text', 'uuid', 'date', 'uuid'],
|
||||
)}
|
||||
`),
|
||||
);
|
||||
}
|
||||
|
||||
if (args.coordinatesDiff.undeprecated) {
|
||||
actions.push(
|
||||
trx.query(sql`/* schema_coordinate_status_undeprecated */
|
||||
UPDATE schema_coordinate_status
|
||||
SET deprecated_at = NULL, deprecated_in_version_id = NULL
|
||||
WHERE
|
||||
target_id = ${args.targetId}
|
||||
AND
|
||||
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.undeprecated), 'text')})
|
||||
`),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(actions);
|
||||
|
||||
if (args.coordinatesDiff.deprecated) {
|
||||
await trx.query(sql`/* schema_coordinate_status_deprecated */
|
||||
UPDATE schema_coordinate_status
|
||||
SET deprecated_at = NOW(), deprecated_in_version_id = ${args.versionId}
|
||||
WHERE
|
||||
target_id = ${args.targetId}
|
||||
AND
|
||||
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.deprecated), 'text')})
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Small helper utility for jsonifying a nullable object.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
DiffEditor as MonacoDiffEditor,
|
||||
Editor as MonacoEditor,
|
||||
} from '@monaco-editor/react';
|
||||
import pkg from '../../package.json' assert { type: 'json' };
|
||||
import pkg from '../../package.json' with { type: 'json' };
|
||||
|
||||
loader.config({
|
||||
paths: {
|
||||
|
|
|
|||
375
pnpm-lock.yaml
375
pnpm-lock.yaml
|
|
@ -159,7 +159,7 @@ importers:
|
|||
version: 0.18.1(prettier@3.3.3)
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: 0.6.5
|
||||
version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3))(prettier@3.3.3)
|
||||
version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3)
|
||||
pretty-quick:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0(prettier@3.3.3)
|
||||
|
|
@ -185,8 +185,8 @@ importers:
|
|||
specifier: 4.3.2
|
||||
version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
|
||||
deployment:
|
||||
dependencies:
|
||||
|
|
@ -333,8 +333,8 @@ importers:
|
|||
specifier: 2.6.3
|
||||
version: 2.6.3
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
zod:
|
||||
specifier: 3.23.8
|
||||
version: 3.23.8
|
||||
|
|
@ -367,8 +367,8 @@ importers:
|
|||
specifier: 14.0.0-beta.7
|
||||
version: 14.0.0-beta.7
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
ws:
|
||||
specifier: 8.18.0
|
||||
version: 8.18.0
|
||||
|
|
@ -496,8 +496,8 @@ importers:
|
|||
specifier: 2.6.3
|
||||
version: 2.6.3
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
publishDirectory: dist
|
||||
|
||||
packages/libraries/envelop:
|
||||
|
|
@ -573,8 +573,8 @@ importers:
|
|||
specifier: 14.0.0-beta.7
|
||||
version: 14.0.0-beta.7
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
ws:
|
||||
specifier: 8.18.0
|
||||
version: 8.18.0
|
||||
|
|
@ -612,6 +612,9 @@ importers:
|
|||
got:
|
||||
specifier: 14.4.1
|
||||
version: 14.4.1(patch_hash=b6pwqmrs3qqykctltsasvrfwti)
|
||||
graphql:
|
||||
specifier: 16.9.0
|
||||
version: 16.9.0
|
||||
p-limit:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
|
|
@ -800,8 +803,8 @@ importers:
|
|||
specifier: 2.6.3
|
||||
version: 2.6.3
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
zod:
|
||||
specifier: 3.23.8
|
||||
version: 3.23.8
|
||||
|
|
@ -833,8 +836,8 @@ importers:
|
|||
specifier: 6.19.2
|
||||
version: 6.19.2
|
||||
vitest:
|
||||
specifier: 1.6.0
|
||||
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
specifier: 2.0.3
|
||||
version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
workers-loki-logger:
|
||||
specifier: 0.1.15
|
||||
version: 0.1.15
|
||||
|
|
@ -1744,7 +1747,7 @@ importers:
|
|||
version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
|
||||
'@storybook/addon-interactions':
|
||||
specifier: 8.2.2
|
||||
version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
'@storybook/addon-links':
|
||||
specifier: 8.2.2
|
||||
version: 8.2.2(react@18.3.1)(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
|
||||
|
|
@ -4021,6 +4024,7 @@ packages:
|
|||
|
||||
'@fastify/vite@6.0.7':
|
||||
resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==}
|
||||
bundledDependencies: []
|
||||
|
||||
'@floating-ui/core@1.2.6':
|
||||
resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==}
|
||||
|
|
@ -4735,6 +4739,15 @@ packages:
|
|||
'@vue/compiler-sfc':
|
||||
optional: true
|
||||
|
||||
'@ianvs/prettier-plugin-sort-imports@4.3.1':
|
||||
resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==}
|
||||
peerDependencies:
|
||||
'@vue/compiler-sfc': 2.7.x || 3.x
|
||||
prettier: 2 || 3
|
||||
peerDependenciesMeta:
|
||||
'@vue/compiler-sfc':
|
||||
optional: true
|
||||
|
||||
'@inquirer/confirm@3.1.8':
|
||||
resolution: {integrity: sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4820,10 +4833,6 @@ packages:
|
|||
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/set-array@1.1.2':
|
||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/set-array@1.2.1':
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -8150,18 +8159,30 @@ packages:
|
|||
'@vitest/expect@1.6.0':
|
||||
resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
|
||||
|
||||
'@vitest/runner@1.6.0':
|
||||
resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
|
||||
'@vitest/expect@2.0.3':
|
||||
resolution: {integrity: sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==}
|
||||
|
||||
'@vitest/snapshot@1.6.0':
|
||||
resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==}
|
||||
'@vitest/pretty-format@2.0.3':
|
||||
resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==}
|
||||
|
||||
'@vitest/runner@2.0.3':
|
||||
resolution: {integrity: sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==}
|
||||
|
||||
'@vitest/snapshot@2.0.3':
|
||||
resolution: {integrity: sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==}
|
||||
|
||||
'@vitest/spy@1.6.0':
|
||||
resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
|
||||
|
||||
'@vitest/spy@2.0.3':
|
||||
resolution: {integrity: sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==}
|
||||
|
||||
'@vitest/utils@1.6.0':
|
||||
resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
|
||||
|
||||
'@vitest/utils@2.0.3':
|
||||
resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==}
|
||||
|
||||
'@webassemblyjs/ast@1.12.1':
|
||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||
|
||||
|
|
@ -8518,6 +8539,10 @@ packages:
|
|||
assertion-error@1.1.0:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ast-types-flow@0.0.8:
|
||||
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
|
||||
|
||||
|
|
@ -8880,6 +8905,10 @@ packages:
|
|||
resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
chai@5.1.1:
|
||||
resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
chalk@2.3.0:
|
||||
resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -8936,6 +8965,10 @@ packages:
|
|||
check-error@1.0.3:
|
||||
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
|
||||
|
||||
check-error@2.1.1:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
check-more-types@2.24.0:
|
||||
resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -9628,6 +9661,10 @@ packages:
|
|||
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
deep-eql@5.0.2:
|
||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
deep-equal@2.1.0:
|
||||
resolution: {integrity: sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==}
|
||||
|
||||
|
|
@ -11848,9 +11885,6 @@ packages:
|
|||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-tokens@8.0.3:
|
||||
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
|
|
@ -11953,9 +11987,6 @@ packages:
|
|||
resolution: {integrity: sha512-qCRJWlbP2v6HbmKW7R3lFbeiVWHo+oMJ0j+MizwvauqnCV/EvtAeEeuCgoc/ErtsuoKgYB8U4Ih8AxJbXoE6/g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
jsonc-parser@3.2.0:
|
||||
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||
|
||||
jsonfile@4.0.0:
|
||||
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
|
||||
|
||||
|
|
@ -12159,10 +12190,6 @@ packages:
|
|||
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
local-pkg@0.5.0:
|
||||
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
localforage@1.10.0:
|
||||
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
|
||||
|
||||
|
|
@ -12262,6 +12289,9 @@ packages:
|
|||
loupe@2.3.7:
|
||||
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
|
||||
|
||||
loupe@3.1.1:
|
||||
resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
|
||||
|
||||
lower-case-first@2.0.2:
|
||||
resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==}
|
||||
|
||||
|
|
@ -12321,6 +12351,9 @@ packages:
|
|||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
|
||||
magic-string@0.30.5:
|
||||
resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -12940,9 +12973,6 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mlly@1.4.2:
|
||||
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
|
||||
|
||||
mnemonist@0.39.6:
|
||||
resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
|
||||
|
||||
|
|
@ -13419,10 +13449,6 @@ packages:
|
|||
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
p-limit@5.0.0:
|
||||
resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-limit@6.1.0:
|
||||
resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -13626,9 +13652,16 @@ packages:
|
|||
pathe@1.1.1:
|
||||
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
|
||||
|
||||
pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
|
||||
pathval@1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
|
||||
pathval@2.0.0:
|
||||
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
|
|
@ -13768,9 +13801,6 @@ packages:
|
|||
resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
pkg-types@1.0.3:
|
||||
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
|
||||
|
||||
pluralize@8.0.0:
|
||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -15228,8 +15258,8 @@ packages:
|
|||
std-env@3.3.3:
|
||||
resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
|
||||
|
||||
std-env@3.6.0:
|
||||
resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==}
|
||||
std-env@3.7.0:
|
||||
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
|
||||
|
||||
storybook@8.2.2:
|
||||
resolution: {integrity: sha512-xDT9gyzAEFQNeK7P+Mj/8bNzN+fbm6/4D6ihdSzmczayjydpNjMs74HDHMY6S4Bfu6tRVyEK2ALPGnr6ZVofBA==}
|
||||
|
|
@ -15338,9 +15368,6 @@ packages:
|
|||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-literal@2.0.0:
|
||||
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
|
||||
|
||||
stripe@14.25.0:
|
||||
resolution: {integrity: sha512-wQS3GNMofCXwH8TSje8E1SE8zr6ODiGtHQgPtO95p9Mb4FhKC9jvXR2NUTpZ9ZINlckJcFidCmaTFV4P6vsb9g==}
|
||||
engines: {node: '>=12.*'}
|
||||
|
|
@ -15580,17 +15607,25 @@ packages:
|
|||
tiny-warning@1.0.3:
|
||||
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||
|
||||
tinybench@2.5.1:
|
||||
resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
|
||||
tinybench@2.8.0:
|
||||
resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
|
||||
|
||||
tinypool@0.8.3:
|
||||
resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==}
|
||||
tinypool@1.0.0:
|
||||
resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
|
||||
tinyrainbow@1.2.0:
|
||||
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@2.2.0:
|
||||
resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@3.0.0:
|
||||
resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
title-case@3.0.3:
|
||||
resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==}
|
||||
|
||||
|
|
@ -15901,9 +15936,6 @@ packages:
|
|||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
ufo@1.3.0:
|
||||
resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
|
||||
|
||||
uglify-js@3.17.4:
|
||||
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
|
@ -16225,8 +16257,8 @@ packages:
|
|||
vfile@6.0.1:
|
||||
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
|
||||
|
||||
vite-node@1.6.0:
|
||||
resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
|
||||
vite-node@2.0.3:
|
||||
resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -16266,15 +16298,15 @@ packages:
|
|||
terser:
|
||||
optional: true
|
||||
|
||||
vitest@1.6.0:
|
||||
resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
|
||||
vitest@2.0.3:
|
||||
resolution: {integrity: sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': 1.6.0
|
||||
'@vitest/ui': 1.6.0
|
||||
'@vitest/browser': 2.0.3
|
||||
'@vitest/ui': 2.0.3
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -18015,19 +18047,19 @@ snapshots:
|
|||
'@babel/helper-module-transforms@7.23.3(@babel/core@7.22.9)':
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-environment-visitor': 7.24.7
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@babel/helper-simple-access': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/helper-split-export-declaration': 7.24.7
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
|
||||
'@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-environment-visitor': 7.24.7
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@babel/helper-simple-access': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/helper-split-export-declaration': 7.24.7
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
|
||||
'@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)':
|
||||
|
|
@ -18821,7 +18853,7 @@ snapshots:
|
|||
'@babel/template@7.24.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.2
|
||||
'@babel/parser': 7.24.5
|
||||
'@babel/parser': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
|
||||
'@babel/template@7.24.7':
|
||||
|
|
@ -18848,12 +18880,12 @@ snapshots:
|
|||
'@babel/traverse@7.24.5':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.2
|
||||
'@babel/generator': 7.24.5
|
||||
'@babel/generator': 7.24.7
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-function-name': 7.23.0
|
||||
'@babel/helper-hoist-variables': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.24.5
|
||||
'@babel/parser': 7.24.5
|
||||
'@babel/parser': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
globals: 11.12.0
|
||||
|
|
@ -20933,15 +20965,28 @@ snapshots:
|
|||
'@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
'@babel/generator': 7.23.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/traverse': 7.24.0
|
||||
'@babel/generator': 7.24.7
|
||||
'@babel/parser': 7.24.7
|
||||
'@babel/traverse': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
prettier: 3.3.3
|
||||
semver: 7.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
'@babel/generator': 7.24.7
|
||||
'@babel/parser': 7.24.7
|
||||
'@babel/traverse': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
prettier: 3.3.3
|
||||
semver: 7.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
'@inquirer/confirm@3.1.8':
|
||||
dependencies:
|
||||
'@inquirer/core': 8.2.1
|
||||
|
|
@ -21046,9 +21091,9 @@ snapshots:
|
|||
|
||||
'@jridgewell/gen-mapping@0.3.3':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.1.2
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.20
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
dependencies:
|
||||
|
|
@ -21058,8 +21103,6 @@ snapshots:
|
|||
|
||||
'@jridgewell/resolve-uri@3.1.1': {}
|
||||
|
||||
'@jridgewell/set-array@1.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
||||
'@jridgewell/source-map@0.3.6':
|
||||
|
|
@ -24213,11 +24256,11 @@ snapshots:
|
|||
'@storybook/global': 5.0.0
|
||||
storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))
|
||||
|
||||
'@storybook/addon-interactions@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
'@storybook/addon-interactions@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
|
||||
'@storybook/test': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
'@storybook/test': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
polished: 4.2.2
|
||||
storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))
|
||||
ts-dedent: 2.2.0
|
||||
|
|
@ -24411,12 +24454,12 @@ snapshots:
|
|||
optionalDependencies:
|
||||
typescript: 5.5.3
|
||||
|
||||
'@storybook/test@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
'@storybook/test@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
dependencies:
|
||||
'@storybook/csf': 0.1.11
|
||||
'@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
|
||||
'@testing-library/dom': 10.1.0
|
||||
'@testing-library/jest-dom': 6.4.5(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
'@testing-library/jest-dom': 6.4.5(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
|
||||
'@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0)
|
||||
'@vitest/expect': 1.6.0
|
||||
'@vitest/spy': 1.6.0
|
||||
|
|
@ -24581,7 +24624,7 @@ snapshots:
|
|||
lz-string: 1.5.0
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/jest-dom@6.4.5(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
'@testing-library/jest-dom@6.4.5(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
|
||||
dependencies:
|
||||
'@adobe/css-tools': 4.3.3
|
||||
'@babel/runtime': 7.24.7
|
||||
|
|
@ -24592,7 +24635,7 @@ snapshots:
|
|||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
vitest: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
vitest: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
|
||||
'@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0)':
|
||||
dependencies:
|
||||
|
|
@ -24655,8 +24698,8 @@ snapshots:
|
|||
'@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3)
|
||||
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
|
||||
eslint-config-prettier: 9.1.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-jsonc: 2.11.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-mdx: 3.0.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
|
|
@ -24776,7 +24819,7 @@ snapshots:
|
|||
|
||||
'@types/babel__template@7.4.1':
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.5
|
||||
'@babel/parser': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
|
||||
'@types/babel__traverse@7.18.3':
|
||||
|
|
@ -25300,22 +25343,36 @@ snapshots:
|
|||
'@vitest/utils': 1.6.0
|
||||
chai: 4.4.1
|
||||
|
||||
'@vitest/runner@1.6.0':
|
||||
'@vitest/expect@2.0.3':
|
||||
dependencies:
|
||||
'@vitest/utils': 1.6.0
|
||||
p-limit: 5.0.0
|
||||
pathe: 1.1.1
|
||||
'@vitest/spy': 2.0.3
|
||||
'@vitest/utils': 2.0.3
|
||||
chai: 5.1.1
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/snapshot@1.6.0':
|
||||
'@vitest/pretty-format@2.0.3':
|
||||
dependencies:
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
pretty-format: 29.7.0
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/runner@2.0.3':
|
||||
dependencies:
|
||||
'@vitest/utils': 2.0.3
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/snapshot@2.0.3':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.0.3
|
||||
magic-string: 0.30.10
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/spy@1.6.0':
|
||||
dependencies:
|
||||
tinyspy: 2.2.0
|
||||
|
||||
'@vitest/spy@2.0.3':
|
||||
dependencies:
|
||||
tinyspy: 3.0.0
|
||||
|
||||
'@vitest/utils@1.6.0':
|
||||
dependencies:
|
||||
diff-sequences: 29.6.3
|
||||
|
|
@ -25323,6 +25380,13 @@ snapshots:
|
|||
loupe: 2.3.7
|
||||
pretty-format: 29.7.0
|
||||
|
||||
'@vitest/utils@2.0.3':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.0.3
|
||||
estree-walker: 3.0.3
|
||||
loupe: 3.1.1
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@webassemblyjs/ast@1.12.1':
|
||||
dependencies:
|
||||
'@webassemblyjs/helper-numbers': 1.11.6
|
||||
|
|
@ -25716,6 +25780,8 @@ snapshots:
|
|||
|
||||
assertion-error@1.1.0: {}
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
ast-types-flow@0.0.8: {}
|
||||
|
||||
ast-types@0.16.1:
|
||||
|
|
@ -26187,6 +26253,14 @@ snapshots:
|
|||
pathval: 1.1.1
|
||||
type-detect: 4.0.8
|
||||
|
||||
chai@5.1.1:
|
||||
dependencies:
|
||||
assertion-error: 2.0.1
|
||||
check-error: 2.1.1
|
||||
deep-eql: 5.0.2
|
||||
loupe: 3.1.1
|
||||
pathval: 2.0.0
|
||||
|
||||
chalk@2.3.0:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
|
|
@ -26261,6 +26335,8 @@ snapshots:
|
|||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
check-more-types@2.24.0: {}
|
||||
|
||||
cheerio-select@1.6.0:
|
||||
|
|
@ -27037,6 +27113,8 @@ snapshots:
|
|||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
|
||||
deep-eql@5.0.2: {}
|
||||
|
||||
deep-equal@2.1.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.5
|
||||
|
|
@ -27564,13 +27642,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
dependencies:
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
enhanced-resolve: 5.17.0
|
||||
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.5
|
||||
is-core-module: 2.13.1
|
||||
|
|
@ -27601,14 +27679,14 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
dependencies:
|
||||
debug: 3.2.7(supports-color@8.1.1)
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3)
|
||||
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -27624,7 +27702,7 @@ snapshots:
|
|||
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
|
||||
eslint-compat-utils: 0.1.2(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
|
||||
dependencies:
|
||||
array-includes: 3.1.7
|
||||
array.prototype.findlastindex: 1.2.3
|
||||
|
|
@ -27634,7 +27712,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
|
||||
hasown: 2.0.0
|
||||
is-core-module: 2.13.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -29803,8 +29881,6 @@ snapshots:
|
|||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@8.0.3: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
|
|
@ -29923,8 +29999,6 @@ snapshots:
|
|||
espree: 9.6.1
|
||||
semver: 7.6.2
|
||||
|
||||
jsonc-parser@3.2.0: {}
|
||||
|
||||
jsonfile@4.0.0:
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -30171,11 +30245,6 @@ snapshots:
|
|||
emojis-list: 3.0.0
|
||||
json5: 1.0.2
|
||||
|
||||
local-pkg@0.5.0:
|
||||
dependencies:
|
||||
mlly: 1.4.2
|
||||
pkg-types: 1.0.3
|
||||
|
||||
localforage@1.10.0:
|
||||
dependencies:
|
||||
lie: 3.1.1
|
||||
|
|
@ -30265,6 +30334,10 @@ snapshots:
|
|||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
loupe@3.1.1:
|
||||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
lower-case-first@2.0.2:
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
|
@ -30314,6 +30387,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
magic-string@0.30.10:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
magic-string@0.30.5:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
|
@ -31534,13 +31611,6 @@ snapshots:
|
|||
|
||||
mkdirp@3.0.1: {}
|
||||
|
||||
mlly@1.4.2:
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
pathe: 1.1.1
|
||||
pkg-types: 1.0.3
|
||||
ufo: 1.3.0
|
||||
|
||||
mnemonist@0.39.6:
|
||||
dependencies:
|
||||
obliterator: 2.0.4
|
||||
|
|
@ -32106,10 +32176,6 @@ snapshots:
|
|||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
|
||||
p-limit@5.0.0:
|
||||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
|
||||
p-limit@6.1.0:
|
||||
dependencies:
|
||||
yocto-queue: 1.1.1
|
||||
|
|
@ -32346,8 +32412,12 @@ snapshots:
|
|||
|
||||
pathe@1.1.1: {}
|
||||
|
||||
pathe@1.1.2: {}
|
||||
|
||||
pathval@1.1.1: {}
|
||||
|
||||
pathval@2.0.0: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
performance-now@2.1.0: {}
|
||||
|
|
@ -32516,12 +32586,6 @@ snapshots:
|
|||
dependencies:
|
||||
find-up: 6.3.0
|
||||
|
||||
pkg-types@1.0.3:
|
||||
dependencies:
|
||||
jsonc-parser: 3.2.0
|
||||
mlly: 1.4.2
|
||||
pathe: 1.1.1
|
||||
|
||||
pluralize@8.0.0: {}
|
||||
|
||||
polished@4.2.2:
|
||||
|
|
@ -32786,11 +32850,11 @@ snapshots:
|
|||
sql-formatter: 15.0.2
|
||||
tslib: 2.6.3
|
||||
|
||||
prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3))(prettier@3.3.3):
|
||||
prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3):
|
||||
dependencies:
|
||||
prettier: 3.3.3
|
||||
optionalDependencies:
|
||||
'@ianvs/prettier-plugin-sort-imports': 4.2.1(prettier@3.3.3)
|
||||
'@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.3.3)
|
||||
|
||||
prettier@2.8.8: {}
|
||||
|
||||
|
|
@ -34117,7 +34181,7 @@ snapshots:
|
|||
|
||||
std-env@3.3.3: {}
|
||||
|
||||
std-env@3.6.0: {}
|
||||
std-env@3.7.0: {}
|
||||
|
||||
storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)):
|
||||
dependencies:
|
||||
|
|
@ -34270,10 +34334,6 @@ snapshots:
|
|||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strip-literal@2.0.0:
|
||||
dependencies:
|
||||
js-tokens: 8.0.3
|
||||
|
||||
stripe@14.25.0:
|
||||
dependencies:
|
||||
'@types/node': 20.14.10
|
||||
|
|
@ -34571,12 +34631,16 @@ snapshots:
|
|||
|
||||
tiny-warning@1.0.3: {}
|
||||
|
||||
tinybench@2.5.1: {}
|
||||
tinybench@2.8.0: {}
|
||||
|
||||
tinypool@0.8.3: {}
|
||||
tinypool@1.0.0: {}
|
||||
|
||||
tinyrainbow@1.2.0: {}
|
||||
|
||||
tinyspy@2.2.0: {}
|
||||
|
||||
tinyspy@3.0.0: {}
|
||||
|
||||
title-case@3.0.3:
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
|
@ -34870,8 +34934,6 @@ snapshots:
|
|||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
ufo@1.3.0: {}
|
||||
|
||||
uglify-js@3.17.4: {}
|
||||
|
||||
unbox-primitive@1.0.2:
|
||||
|
|
@ -35252,12 +35314,12 @@ snapshots:
|
|||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
vite-node@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
|
||||
vite-node@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
pathe: 1.1.1
|
||||
picocolors: 1.0.1
|
||||
pathe: 1.1.2
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
|
@ -35291,27 +35353,26 @@ snapshots:
|
|||
less: 4.2.0
|
||||
terser: 5.31.1
|
||||
|
||||
vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
|
||||
vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
|
||||
dependencies:
|
||||
'@vitest/expect': 1.6.0
|
||||
'@vitest/runner': 1.6.0
|
||||
'@vitest/snapshot': 1.6.0
|
||||
'@vitest/spy': 1.6.0
|
||||
'@vitest/utils': 1.6.0
|
||||
acorn-walk: 8.3.2
|
||||
chai: 4.4.1
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@vitest/expect': 2.0.3
|
||||
'@vitest/pretty-format': 2.0.3
|
||||
'@vitest/runner': 2.0.3
|
||||
'@vitest/snapshot': 2.0.3
|
||||
'@vitest/spy': 2.0.3
|
||||
'@vitest/utils': 2.0.3
|
||||
chai: 5.1.1
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
execa: 8.0.1
|
||||
local-pkg: 0.5.0
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
picocolors: 1.0.1
|
||||
std-env: 3.6.0
|
||||
strip-literal: 2.0.0
|
||||
tinybench: 2.5.1
|
||||
tinypool: 0.8.3
|
||||
magic-string: 0.30.10
|
||||
pathe: 1.1.2
|
||||
std-env: 3.7.0
|
||||
tinybench: 2.8.0
|
||||
tinypool: 1.0.0
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
vite-node: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
vite-node: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
|
||||
why-is-node-running: 2.2.2
|
||||
optionalDependencies:
|
||||
'@types/node': 20.14.10
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ const { plugins, ...prettierConfig } = require('@theguild/prettier-config');
|
|||
|
||||
module.exports = {
|
||||
...prettierConfig,
|
||||
importOrderParserPlugins: ['importAssertions', ...prettierConfig.importOrderParserPlugins],
|
||||
importOrderParserPlugins: [
|
||||
'importAssertions',
|
||||
// `using` keyword
|
||||
'explicitResourceManagement',
|
||||
...prettierConfig.importOrderParserPlugins,
|
||||
],
|
||||
plugins: [
|
||||
'prettier-plugin-sql',
|
||||
...plugins,
|
||||
|
|
|
|||
Loading…
Reference in a new issue