Admin/team billing downgrader (#2040)
* downgrade func * fix security hole lol * fix query conditions * - set to trial not free - auto create stripe customer if missing - fix production check * Extracts downgrade logic to script, fixes ts-node conflicts with prisma * Adds trialEndsAt field to users * Updates trial/downgrade logic * Typo * Legibility fixes * Update team-billing.ts * Legibility improvements Co-authored-by: Jamie <ijamespine@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
f4b6a16a9e
commit
0a8509d721
20 changed files with 174 additions and 60 deletions
|
@ -1 +0,0 @@
|
||||||
export * from "@calcom/prisma/client";
|
|
|
@ -13,9 +13,11 @@ const TrialBanner = () => {
|
||||||
|
|
||||||
if (!user || user.plan !== "TRIAL") return null;
|
if (!user || user.plan !== "TRIAL") return null;
|
||||||
|
|
||||||
const trialDaysLeft = dayjs(user.createdDate)
|
const trialDaysLeft = user.trialEndsAt
|
||||||
.add(TRIAL_LIMIT_DAYS + 1, "day")
|
? dayjs(user.trialEndsAt).add(1, "day").diff(dayjs(), "day")
|
||||||
.diff(dayjs(), "day");
|
: dayjs(user.createdDate)
|
||||||
|
.add(TRIAL_LIMIT_DAYS + 1, "day")
|
||||||
|
.diff(dayjs(), "day");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
import stripe from "@ee/lib/stripe/server";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { HttpError as HttpCode } from "@lib/core/http/error";
|
import { HttpError as HttpCode } from "@lib/core/http/error";
|
||||||
import { prisma } from "@lib/prisma";
|
|
||||||
|
|
||||||
export async function getStripeCustomerFromUser(userId: number) {
|
import stripe from "./server";
|
||||||
|
|
||||||
|
export async function getStripeCustomerIdFromUserId(userId: number) {
|
||||||
// Get user
|
// Get user
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|
90
apps/web/ee/lib/stripe/downgrade.ts
Executable file
90
apps/web/ee/lib/stripe/downgrade.ts
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
#!/usr/bin/env ts-node
|
||||||
|
// To run this script: `yarn downgrade 2>&1 | tee result.log`
|
||||||
|
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
|
||||||
|
|
||||||
|
import { getStripeCustomerIdFromUserId } from "./customer";
|
||||||
|
import stripe from "./server";
|
||||||
|
import { getPremiumPlanPrice, getProPlanPrice } from "./team-billing";
|
||||||
|
|
||||||
|
export async function downgradeIllegalProUsers() {
|
||||||
|
const usersDowngraded: string[] = [];
|
||||||
|
const illegalProUsers = await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
role: {
|
||||||
|
not: MembershipRole.OWNER,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
plan: {
|
||||||
|
not: UserPlan.PRO,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const downgrade = async (member: typeof illegalProUsers[number]) => {
|
||||||
|
console.log(`Downgrading: ${member.user.email}`);
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: member.user.id },
|
||||||
|
data: {
|
||||||
|
plan: UserPlan.TRIAL,
|
||||||
|
trialEndsAt: dayjs().add(TRIAL_LIMIT_DAYS, "day").toDate(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(`Downgraded: ${member.user.email}`);
|
||||||
|
usersDowngraded.push(member.user.username || `${member.user.id}`);
|
||||||
|
};
|
||||||
|
for (const member of illegalProUsers) {
|
||||||
|
const metadata = (member.user.metadata as Prisma.JsonObject) ?? {};
|
||||||
|
// if their pro is already sponsored by a team, do not downgrade
|
||||||
|
if (metadata.proPaidForTeamId !== undefined) continue;
|
||||||
|
|
||||||
|
const stripeCustomerId = await getStripeCustomerIdFromUserId(member.user.id);
|
||||||
|
if (!stripeCustomerId) {
|
||||||
|
await downgrade(member);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customer = await stripe.customers.retrieve(stripeCustomerId, {
|
||||||
|
expand: ["subscriptions.data.plan"],
|
||||||
|
});
|
||||||
|
if (!customer || customer.deleted) {
|
||||||
|
await downgrade(member);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = customer.subscriptions?.data[0];
|
||||||
|
if (!subscription) {
|
||||||
|
await downgrade(member);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasProPlan = !!subscription.items.data.find(
|
||||||
|
(item) => item.plan.id === getProPlanPrice() || item.plan.id === getPremiumPlanPrice()
|
||||||
|
);
|
||||||
|
// if they're pro, do not downgrade
|
||||||
|
if (hasProPlan) continue;
|
||||||
|
|
||||||
|
await downgrade(member);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
usersDowngraded,
|
||||||
|
usersDowngradedAmount: usersDowngraded.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
downgradeIllegalProUsers()
|
||||||
|
.then(({ usersDowngraded, usersDowngradedAmount }) => {
|
||||||
|
console.log(`Downgraded ${usersDowngradedAmount} illegal pro users`);
|
||||||
|
console.table(usersDowngraded);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
|
@ -2,10 +2,11 @@ import { PaymentType, Prisma } from "@prisma/client";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@lib/emails/email-manager";
|
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@lib/emails/email-manager";
|
||||||
import { getErrorFromUnknown } from "@lib/errors";
|
import { getErrorFromUnknown } from "@lib/errors";
|
||||||
import { CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar";
|
import { CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
|
|
||||||
import { createPaymentLink } from "./client";
|
import { createPaymentLink } from "./client";
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
|
|
||||||
import { getStripeCustomerFromUser } from "@ee/lib/stripe/customer";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { getStripeCustomerIdFromUserId } from "@ee/lib/stripe/customer";
|
||||||
|
|
||||||
import { HOSTED_CAL_FEATURES } from "@lib/config/constants";
|
import { HOSTED_CAL_FEATURES } from "@lib/config/constants";
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
|
|
||||||
import stripe from "./server";
|
import stripe from "./server";
|
||||||
|
|
||||||
// get team owner's Pro Plan subscription from Cal userId
|
// get team owner's Pro Plan subscription from Cal userId
|
||||||
export async function getProPlanSubscription(userId: number) {
|
export async function getProPlanSubscription(userId: number) {
|
||||||
const stripeCustomerId = await getStripeCustomerFromUser(userId);
|
const stripeCustomerId = await getStripeCustomerIdFromUserId(userId);
|
||||||
if (!stripeCustomerId) return null;
|
if (!stripeCustomerId) return null;
|
||||||
|
|
||||||
const customer = await stripe.customers.retrieve(stripeCustomerId, {
|
const customer = await stripe.customers.retrieve(stripeCustomerId, {
|
||||||
|
@ -82,11 +82,17 @@ export async function upgradeTeam(userId: number, teamId: number) {
|
||||||
const { membersMissingSeats, ownerIsMissingSeat } = await getMembersMissingSeats(teamId);
|
const { membersMissingSeats, ownerIsMissingSeat } = await getMembersMissingSeats(teamId);
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
const customer = await getStripeCustomerFromUser(userId);
|
let customerId = await getStripeCustomerIdFromUserId(userId);
|
||||||
if (!customer) throw new HttpError({ statusCode: 400, message: "User has no Stripe customer" });
|
if (!customerId) {
|
||||||
|
// create stripe customer if it doesn't already exist
|
||||||
|
const res = await stripe.customers.create({
|
||||||
|
email: ownerUser.user.email,
|
||||||
|
});
|
||||||
|
customerId = res.id;
|
||||||
|
}
|
||||||
// create a checkout session with the quantity of missing seats
|
// create a checkout session with the quantity of missing seats
|
||||||
const session = await createCheckoutSession(
|
const session = await createCheckoutSession(
|
||||||
customer,
|
customerId,
|
||||||
membersMissingSeats.length,
|
membersMissingSeats.length,
|
||||||
teamId,
|
teamId,
|
||||||
ownerIsMissingSeat
|
ownerIsMissingSeat
|
||||||
|
@ -257,19 +263,20 @@ export async function ensureSubscriptionQuantityCorrectness(userId: number, team
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isProductionSite =
|
||||||
|
process.env.NEXT_PUBLIC_BASE_URL === "https://app.cal.com" && process.env.VERCEL_ENV === "production";
|
||||||
|
|
||||||
// TODO: these should be moved to env vars
|
// TODO: these should be moved to env vars
|
||||||
export function getPerSeatProPlanPrice(): string {
|
export function getPerSeatProPlanPrice(): string {
|
||||||
return process.env.NODE_ENV === "production"
|
return isProductionSite ? "price_1KHkoeH8UDiwIftkkUbiggsM" : "price_1KLD4GH8UDiwIftkWQfsh1Vh";
|
||||||
? "price_1KHkoeH8UDiwIftkkUbiggsM"
|
|
||||||
: "price_1KLD4GH8UDiwIftkWQfsh1Vh";
|
|
||||||
}
|
}
|
||||||
export function getProPlanPrice(): string {
|
export function getProPlanPrice(): string {
|
||||||
return process.env.NODE_ENV === "production"
|
return isProductionSite ? "price_1KHkoeH8UDiwIftkkUbiggsM" : "price_1JZ0J3H8UDiwIftk0YIHYKr8";
|
||||||
? "price_1KHkoeH8UDiwIftkkUbiggsM"
|
|
||||||
: "price_1JZ0J3H8UDiwIftk0YIHYKr8";
|
|
||||||
}
|
}
|
||||||
export function getPremiumPlanPrice(): string {
|
export function getPremiumPlanPrice(): string {
|
||||||
return process.env.NODE_ENV === "production"
|
return isProductionSite ? "price_1Jv3CMH8UDiwIftkFgyXbcHN" : "price_1Jv3CMH8UDiwIftkFgyXbcHN";
|
||||||
? "price_1Jv3CMH8UDiwIftkFgyXbcHN"
|
}
|
||||||
: "price_1Jv3CMH8UDiwIftkFgyXbcHN";
|
|
||||||
|
export function getProPlanProduct(): string {
|
||||||
|
return isProductionSite ? "prod_JVxwoOF5odFiZ8" : "prod_KDRBg0E4HyVZee";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { getStripeCustomerFromUser } from "@ee/lib/stripe/customer";
|
import { getStripeCustomerIdFromUserId } from "@ee/lib/stripe/customer";
|
||||||
import stripe from "@ee/lib/stripe/server";
|
import stripe from "@ee/lib/stripe/server";
|
||||||
|
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
|
@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const customerId = await getStripeCustomerFromUser(session.user.id);
|
const customerId = await getStripeCustomerIdFromUserId(session.user.id);
|
||||||
|
|
||||||
if (!customerId) {
|
if (!customerId) {
|
||||||
res.status(500).json({ message: "Missing customer id" });
|
res.status(500).json({ message: "Missing customer id" });
|
||||||
|
|
|
@ -1,20 +1 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
export { default } from "@calcom/prisma";
|
||||||
|
|
||||||
import { IS_PRODUCTION } from "@lib/config/constants";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var prisma: PrismaClient | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const prisma =
|
|
||||||
globalThis.prisma ||
|
|
||||||
new PrismaClient({
|
|
||||||
// log: ["query", "error", "warn"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!IS_PRODUCTION) {
|
|
||||||
globalThis.prisma = prisma;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default prisma;
|
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"lint:fix": "next lint . --ext .ts,.js,.tsx,.jsx --fix",
|
"lint:fix": "next lint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||||
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts"
|
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts",
|
||||||
|
"downgrade": "ts-node ee/lib/stripe/downgrade.ts"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.x",
|
"node": ">=14.x",
|
||||||
|
@ -135,7 +136,7 @@
|
||||||
"eslint": "^8.9.0",
|
"eslint": "^8.9.0",
|
||||||
"tailwindcss": "^3.0.0",
|
"tailwindcss": "^3.0.0",
|
||||||
"ts-jest": "^26.0.0",
|
"ts-jest": "^26.0.0",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.6.0",
|
||||||
"typescript": "^4.5.3"
|
"typescript": "^4.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
plan: "TRIAL",
|
plan: "TRIAL",
|
||||||
createdDate: {
|
OR: [
|
||||||
lt: dayjs().subtract(TRIAL_LIMIT_DAYS, "day").toDate(),
|
/**
|
||||||
},
|
* If the user doesn't have a trial end date,
|
||||||
|
* use the default 14 day trial from creation.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
createdDate: {
|
||||||
|
lt: dayjs().subtract(TRIAL_LIMIT_DAYS, "day").toDate(),
|
||||||
|
},
|
||||||
|
trialEndsAt: null,
|
||||||
|
},
|
||||||
|
/** If it does, then honor the trial end date. */
|
||||||
|
{
|
||||||
|
trialEndsAt: {
|
||||||
|
lt: dayjs().toDate(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
|
|
|
@ -67,6 +67,7 @@ async function getUserFromSession({
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
locale: true,
|
locale: true,
|
||||||
timeFormat: true,
|
timeFormat: true,
|
||||||
|
trialEndsAt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
timeFormat: user.timeFormat,
|
timeFormat: user.timeFormat,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
createdDate: user.createdDate,
|
createdDate: user.createdDate,
|
||||||
|
trialEndsAt: user.trialEndsAt,
|
||||||
completedOnboarding: user.completedOnboarding,
|
completedOnboarding: user.completedOnboarding,
|
||||||
twoFactorEnabled: user.twoFactorEnabled,
|
twoFactorEnabled: user.twoFactorEnabled,
|
||||||
identityProvider: user.identityProvider,
|
identityProvider: user.identityProvider,
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"@components/*": ["components/*"],
|
"@components/*": ["components/*"],
|
||||||
"@lib/*": ["lib/*"],
|
"@lib/*": ["lib/*"],
|
||||||
"@server/*": ["server/*"],
|
"@server/*": ["server/*"],
|
||||||
"@ee/*": ["ee/*"]
|
"@ee/*": ["ee/*"],
|
||||||
|
"@prisma/client/*": ["@calcom/prisma/client/*"]
|
||||||
},
|
},
|
||||||
"typeRoots": ["./types"],
|
"typeRoots": ["./types"],
|
||||||
"types": ["@types/jest"]
|
"types": ["@types/jest"]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var prisma: PrismaClient;
|
var prisma: PrismaClient | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prisma =
|
export const prisma =
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "trialEndsAt" TIMESTAMP(3);
|
|
@ -20,7 +20,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prisma": "3.9.2",
|
"prisma": "3.9.2",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.6.0",
|
||||||
"zod-prisma": "^0.5.4"
|
"zod-prisma": "^0.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -29,6 +29,11 @@
|
||||||
},
|
},
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"client",
|
||||||
|
"zod",
|
||||||
|
"zod-utils.ts"
|
||||||
|
],
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "ts-node ./seed.ts"
|
"seed": "ts-node ./seed.ts"
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ model User {
|
||||||
hideBranding Boolean @default(false)
|
hideBranding Boolean @default(false)
|
||||||
theme String?
|
theme String?
|
||||||
createdDate DateTime @default(now()) @map(name: "created")
|
createdDate DateTime @default(now()) @map(name: "created")
|
||||||
|
trialEndsAt DateTime?
|
||||||
eventTypes EventType[] @relation("user_eventtype")
|
eventTypes EventType[] @relation("user_eventtype")
|
||||||
credentials Credential[]
|
credentials Credential[]
|
||||||
teams Membership[]
|
teams Membership[]
|
||||||
|
@ -353,6 +354,6 @@ model Webhook {
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
active Boolean @default(true)
|
active Boolean @default(true)
|
||||||
eventTriggers WebhookTriggerEvents[]
|
eventTriggers WebhookTriggerEvents[]
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
eventType EventType? @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
|
"files": true,
|
||||||
|
"require": ["tsconfig-paths/register"],
|
||||||
|
"experimentalResolverFeatures": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
|
|
|
@ -7,5 +7,8 @@
|
||||||
"base.json",
|
"base.json",
|
||||||
"nextjs.json",
|
"nextjs.json",
|
||||||
"react-library.json"
|
"react-library.json"
|
||||||
]
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"tsconfig-paths": "^3.12.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13900,10 +13900,10 @@ ts-morph@^13.0.2:
|
||||||
"@ts-morph/common" "~0.12.3"
|
"@ts-morph/common" "~0.12.3"
|
||||||
code-block-writer "^11.0.0"
|
code-block-writer "^11.0.0"
|
||||||
|
|
||||||
ts-node@^10.2.1:
|
ts-node@^10.6.0:
|
||||||
version "10.5.0"
|
version "10.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.6.0.tgz#c3f4195d5173ce3affdc8f2fd2e9a7ac8de5376a"
|
||||||
integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==
|
integrity sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cspotcode/source-map-support" "0.7.0"
|
"@cspotcode/source-map-support" "0.7.0"
|
||||||
"@tsconfig/node10" "^1.0.7"
|
"@tsconfig/node10" "^1.0.7"
|
||||||
|
|
Loading…
Reference in a new issue