diff --git a/apps/zipper.dev/package.json b/apps/zipper.dev/package.json index 4fb5eaee4..668fe3712 100644 --- a/apps/zipper.dev/package.json +++ b/apps/zipper.dev/package.json @@ -79,7 +79,7 @@ "@types/react-timeago": "^4.1.5", "@uploadthing/react": "^6.0.2", "@vercel/kv": "^1.0.0", - "@zipper-inc/client-js": "^0.1.6", + "@zipper-inc/client-js": "^0.1.7", "@zipper/types": "*", "@zipper/ui": "*", "@zipper/utils": "*", @@ -97,6 +97,7 @@ "into-stream": "^6.0.0", "ioredis": "^5.2.2", "jose": "^4.11.4", + "jscodeshift": "^0.15.1", "jsonwebtoken": "^9.0.0", "jszip": "^3.10.1", "langchain": "^0.0.130", @@ -154,7 +155,7 @@ "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.13", "@types/jest": "^29.4.0", - "@types/jscodeshift": "^0.11.10", + "@types/jscodeshift": "^0.11.11", "@types/jsonwebtoken": "^9.0.2", "@types/lodash.debounce": "^4.0.7", "@types/lodash.shuffle": "^4.2.7", diff --git a/apps/zipper.dev/src/components/context/editor-context.tsx b/apps/zipper.dev/src/components/context/editor-context.tsx index e3fc5fc75..9c09e0cb8 100644 --- a/apps/zipper.dev/src/components/context/editor-context.tsx +++ b/apps/zipper.dev/src/components/context/editor-context.tsx @@ -35,6 +35,7 @@ import { } from '~/utils/playground.utils'; import { runZipperLinter } from '~/utils/zipper-editor-linter'; import { rewriteSpecifier } from '~/utils/rewrite-imports'; +import { getFileExtension } from '~/utils/file-extension'; type OnValidate = AddParameters< Required['onValidate'], @@ -56,6 +57,7 @@ export type EditorContextType = { setModelIsDirty: (path: string, isDirty: boolean) => void; isEditorDirty: () => boolean; modelHasErrors: (path: string) => boolean; + getModelByFilename: (filename: string) => monaco.editor.ITextModel | null; setModelHasErrors: (path: string, isErroring: boolean) => void; editorHasErrors: () => boolean; getErrorFiles: () => string[]; @@ -107,6 +109,7 @@ export const EditorContext = createContext({ setModelIsDirty: noop, isEditorDirty: () => false, modelHasErrors: () => false, + getModelByFilename: () => null, setModelHasErrors: () => false, editorHasErrors: () => false, getErrorFiles: () => [], @@ -698,6 +701,16 @@ const EditorContextProvider = ({ .filter(([, value]) => value) .map(([filename]) => filename); + const getModelByFilename = (filename: string) => { + if (!monacoRef?.current || !editor) return null; + const uri = getUriFromPath( + `file:///${filename}`, + monacoRef.current.Uri.parse, + getFileExtension(filename) || 'tsx', + ); + return editor.getModel(uri); + }; + return ( Promise; + }) => Promise; boot: (params?: { shouldSave?: boolean }) => Promise; bootPromise: MutableRefObject>; configs: Zipper.BootPayload['configs']; @@ -67,7 +76,7 @@ export const RunAppContext = createContext({ userAuthConnectors: [], setResults: noop, bootPromise: { current: Promise.resolve({} as BootPayload) }, - run: () => Promise.resolve(''), + run: () => Promise.resolve(undefined), boot: () => Promise.resolve({} as BootPayload), configs: {}, }); @@ -391,7 +400,7 @@ export function RunAppProvider({ cleanUpLogTimers(); updateLogs({ version, runId, fromTimestamp: oneSecondAgo }); - return runId; + return { ...result, runId }; }; return ( diff --git a/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet-connectors.tsx b/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet-connectors.tsx index 435f113c6..e2f8414cd 100644 --- a/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet-connectors.tsx +++ b/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet-connectors.tsx @@ -22,13 +22,6 @@ export const AppEditSidebarAppletConnectors = () => { const [githubAuthRequired, setGitHubAuthRequired] = useState(false); // state to hold whether user needs to authenticate with github - // get the existing Slack connector data from the database - const slackConnector = trpc.slackConnector.get.useQuery( - { appId: appInfo.id }, - { - enabled: slackAuthRequired, - }, - ); // get the existing GitHub connector data from the database const githubConnector = trpc.githubConnector.get.useQuery( { appId: appInfo.id }, @@ -87,16 +80,9 @@ export const AppEditSidebarAppletConnectors = () => { // get the Slack auth URL -- if required --from the backend // (it includes an encrypted state value that links the auth request to the app) const slackAuthURL = trpc.slackConnector.getAuthUrl.useQuery( + { appId }, { - appId, - scopes: { - bot: slackConnector.data?.workspaceScopes || [], - user: slackConnector.data?.userScopes || [], - }, - postInstallationRedirect: window.location.href, - }, - { - enabled: slackConnector.isFetched, + enabled: slackAuthRequired, }, ); diff --git a/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet.tsx b/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet.tsx index cd3e0b6ed..1483c1cf3 100644 --- a/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet.tsx +++ b/apps/zipper.dev/src/components/playground/app-edit-sidebar-applet.tsx @@ -246,7 +246,7 @@ export const AppEditSidebarApplet = ({ appSlug }: { appSlug: string }) => { setRunId( await run({ shouldSave: appInfo.canUserEdit, - }), + }).then((r) => r?.runId), ); }} display="flex" diff --git a/apps/zipper.dev/src/components/playground/tab-code.tsx b/apps/zipper.dev/src/components/playground/tab-code.tsx index 055983ce2..752aba8f7 100644 --- a/apps/zipper.dev/src/components/playground/tab-code.tsx +++ b/apps/zipper.dev/src/components/playground/tab-code.tsx @@ -222,6 +222,8 @@ export const CodeTab: React.FC = ({ app, mainScript }) => { w="full" spacing={0} onMouseEnter={onMouseEnter('PlaygroundCode')} + overflow="scroll" + pb="4" onMouseLeave={onMouseLeave} border={ hoveredElement === 'PlaygroundCode' diff --git a/apps/zipper.dev/src/connectors/slack/constants.ts b/apps/zipper.dev/src/connectors/slack/constants.ts index c892f44e4..621003e68 100644 --- a/apps/zipper.dev/src/connectors/slack/constants.ts +++ b/apps/zipper.dev/src/connectors/slack/constants.ts @@ -1,4 +1,13 @@ -export const code = `import { WebClient } from "https://deno.land/x/slack_web_api@6.7.2/mod.js"; +const redirectHost = + process.env.NODE_ENV === 'development' + ? process.env.NEXT_PUBLIC_ZIPPER_DOT_DEV_HOST + : 'https://zipper.dev'; + +export const code = `import { WebClient } from 'https://deno.land/x/slack_web_api@6.7.2/mod.js'; +import { initApplet } from 'https://deno.land/x/zipper_client_js@v0.1.7/mod.ts'; +import { SlackUserAuth } from 'https://zipper.dev/zipper-inc/slack-install-link/src/main.ts'; + +export const slackConnectorConfig: Partial = {}; // This is an Slack API client intialized with the applet developer's Slack token // THIS CLIENT DOES NOT USE THE USER TOKEN @@ -15,6 +24,40 @@ client.sendToChannel = async (text: string, channelName: string) => { // The current user's Slack token is available in the context of a handler function export const getUserClient = (userToken: string) => new WebClient(userToken); +type SlackConnectorInput = { + action: + | 'get-auth-url' // Install the app + | 'get-config'; // Needed to exchange code for token +}; + +export async function handler( + input: SlackConnectorInput, + ctx: Zipper.HandlerContext, +) { + const thisAppletPlaygroundUrl = \`${redirectHost}/\${ + ctx.appInfo.author?.organization || ctx.appInfo.author?.slug + }/\${ctx.appInfo.slug}/src/slack-connector.ts\`; + + const config = { + ...slackConnectorConfig, + zipperAppId: ctx.appInfo.id, + postInstallRedirect: thisAppletPlaygroundUrl, + }; + + switch (input.action) { + case 'get-auth-url': { + const link = await initApplet('slack-install-link') + .path('link-only') + .run(config); + + return link; + } + case 'get-config': { + return config; + } + } +} + export default client; `; diff --git a/apps/zipper.dev/src/connectors/slack/index.tsx b/apps/zipper.dev/src/connectors/slack/index.tsx index e4897dfe5..698b62dd5 100644 --- a/apps/zipper.dev/src/connectors/slack/index.tsx +++ b/apps/zipper.dev/src/connectors/slack/index.tsx @@ -40,6 +40,9 @@ import { useRouter } from 'next/router'; import { code, userScopes, workspaceScopes } from './constants'; import { useRunAppContext } from '~/components/context/run-app-context'; import { useUser } from '~/hooks/use-user'; +import { useEditorContext } from '~/components/context/editor-context'; +import { initLocalApplet } from '~/utils/local-applet'; +import { updateConnectorConfig } from '~/utils/connector-codemod'; // configure the Slack connector export const slackConnector = createConnector({ @@ -52,29 +55,22 @@ export const slackConnector = createConnector({ workspaceScopes, }); +const defaultBotScope = 'channels:read'; +const botTokenName = 'SLACK_BOT_TOKEN'; +const userTokenName = 'SLACK_USER_TOKEN'; +// convert the scopes to options for the multi-select menus +const __bot_options = workspaceScopes.map((scope) => ({ + label: scope, + value: scope, +})); +const __user_options = userScopes.map((scope) => ({ + label: scope, + value: scope, +})); function SlackConnectorForm({ appId }: { appId: string }) { - const utils = trpc.useContext(); - const { isOpen, onOpen, onClose } = useDisclosure(); - const cancelRef = useRef() as React.MutableRefObject; - const { appInfo } = useRunAppContext(); const { user } = useUser(); const [clientId, setClientId] = useState(''); - const [clientSecret, setClientSecret] = useState(''); - - // convert the scopes to options for the multi-select menus - const __bot_options = workspaceScopes.map((scope) => ({ - label: scope, - value: scope, - })); - const __user_options = userScopes.map((scope) => ({ - label: scope, - value: scope, - })); - - const defaultBotScope = 'channels:read'; - const botTokenName = 'SLACK_BOT_TOKEN'; - const userTokenName = 'SLACK_USER_TOKEN'; // useMultiSelect hook gives us the value state and the onChange and setValue methods // for the multi-select menus @@ -104,192 +100,24 @@ function SlackConnectorForm({ appId }: { appId: string }) { { onSuccess: (data) => { if (data && setBotValue && setUserValue) { - setBotValue(botValue || data.workspaceScopes || [defaultBotScope]); + setBotValue(botValue || data.botScopes || [defaultBotScope]); setUserValue(userValue || data?.userScopes || []); + + // do we want to set this? + if (data?.clientId) { + setIsOwnClientIdRequired(true); + setClientId(data?.clientId || ''); + } } }, }, ); - const [isUserAuthRequired, setIsUserAuthRequired] = useState( - connector.data?.isUserAuthRequired, - ); + const A = trpc.slackConnector.get; const [isOwnClientIdRequired, setIsOwnClientIdRequired] = useState(false); - const [isSaving, setIsSaving] = useState(false); - - // get the existing Slack bot token from the database - const existingSecret = trpc.secret.get.useQuery( - { appId, key: botTokenName }, - { enabled: !!user }, - ); - - const existingUserSecret = trpc.secret.get.useQuery( - { appId, key: userTokenName }, - { enabled: !!user }, - ); - - // get the Slack auth URL from the backend (it includes an encrypted state value that links - // the auth request to the app) - const slackAuthURL = trpc.slackConnector.getAuthUrl.useQuery({ - appId, - scopes: { bot: botValue as string[], user: userValue as string[] }, - postInstallationRedirect: window.location.href, - }); - - const [slackAuthInProgress, setSlackAuthInProgress] = useState(false); - - const context = trpc.useContext(); - const router = useRouter(); - const updateAppConnectorMutation = trpc.appConnector.update.useMutation({ - onSuccess: () => { - context.app.byResourceOwnerAndAppSlugs.invalidate({ - appSlug: router.query['app-slug'] as string, - resourceOwnerSlug: router.query['resource-owner'] as string, - }); - }, - }); - - const addSecretMutation = trpc.secret.add.useMutation(); - - const deleteConnectorMutation = trpc.slackConnector.delete.useMutation({ - async onSuccess() { - await utils.slackConnector.get.invalidate({ appId }); - await utils.secret.get.invalidate({ appId, key: botTokenName }); - }, - }); - - useEffect(() => { - setIsUserAuthRequired(connector.data?.isUserAuthRequired); - setClientId(connector.data?.clientId || ''); - setIsOwnClientIdRequired(!!connector.data?.clientId); - }, [connector.isSuccess]); - - useEffect(() => { - if (slackAuthInProgress && slackAuthURL.data?.url) { - router.push(slackAuthURL.data?.url); - setSlackAuthInProgress(false); - } - }, [slackAuthInProgress, slackAuthURL.data?.url]); - - const existingInstallation = - (existingSecret.data || existingUserSecret.data) && - connector.data?.metadata; - - if ( - existingUserSecret.isLoading || - existingSecret.isLoading || - connector.isLoading - ) { - return <>; - } - - const userAuthSwitch = (mutateOnChange?: boolean) => { - return ( - - - - Require users to auth? - - { - if (mutateOnChange) { - updateAppConnectorMutation.mutate({ - appId, - type: 'slack', - data: { - isUserAuthRequired: e.target.checked, - }, - }); - } - setIsUserAuthRequired(e.target.checked); - }} - /> - - - When checked, users will have to authorize the Slack integration - before they're able to run this Zipper app and see the output. - Requires at least 1 user scope. The user's Slack token will be - available via the HandlerContext at runtime. - - - - ); - }; - - const requiresOwnClientIdSwitch = () => { - return ( - - - - Custom client ID? - - { - setIsOwnClientIdRequired(e.target.checked); - }} - /> - - - When checked, you can specify your own Slack client ID and secret. - Useful if you need to enable features like the Events API or Slash - Commands. - - - - - Client ID - setClientId(e.target.value)} - /> - - - - - Client Secret - - SLACK_CLIENT_SECRET - - - - setClientSecret(e.target.value)} - /> - - - - Redirect URL - - Set your Slack app's redirect URL to:{' '} - {process.env.NODE_ENV === 'development' ? ( - https://your.ngrok.url/connectors/slack/auth - ) : ( - https://zipper.dev/connectors/slack/auth - )} - - - - - - ); - }; - return ( {slackConnector && ( @@ -299,224 +127,21 @@ function SlackConnectorForm({ appId }: { appId: string }) { {appInfo.canUserEdit ? ( - <> - {existingInstallation ? ( - <> - - - - Configuration - - Installed to - - - - { - (connector.data?.metadata as any)['team'][ - 'name' - ] - } - - - - - - Installation details - - - } - fontSize="sm" - py="2" - > - {connector.data?.clientId && ( - - Client ID: - {connector.data?.clientId} - - )} - - Bot User ID: - - { - (connector.data?.metadata as any)[ - 'bot_user_id' - ] - } - - - - Bot Scopes: - - {( - (connector.data?.metadata as any)[ - 'scope' - ] || '' - ) - .split(',') - .map((scope: string) => ( - {scope} - ))} - - - - {(connector.data?.metadata as any)[ - 'authed_user' - ] && - (connector.data?.metadata as any)[ - 'authed_user' - ]['scope'] && ( - - User Scopes: - - {(connector.data?.metadata as any)[ - 'authed_user' - ]['scope'] - .split(',') - .map((scope: string) => ( - {scope} - ))} - - - )} - - - - - - {slackAuthURL.data && ( - - )} - - {userAuthSwitch(true)} - - - - - - - - Uninstall Slack App - - - - Are you sure? You can't undo this action afterwards. - - - - - - - - - - - ) : ( - - - - Configuration - - - Bot Scopes - - - - - User Scopes - - - - {userAuthSwitch()} - {requiresOwnClientIdSwitch()} - - - - - - - )} - + ) : ( @@ -532,11 +157,9 @@ function SlackConnectorForm({ appId }: { appId: string }) { Bot Scopes: - {connector.data?.workspaceScopes.map( - (scope: string) => ( - {scope} - ), - )} + {connector.data?.botScopes.map((scope: string) => ( + {scope} + ))} @@ -549,12 +172,12 @@ function SlackConnectorForm({ appId }: { appId: string }) { - + {/* User auth required? {connector.data?.isUserAuthRequired ? 'Yes' : 'No'} - + */} @@ -570,4 +193,449 @@ function SlackConnectorForm({ appId }: { appId: string }) { ); } +type UserAuthSwitch = { + isUserAuthRequired: any; + setIsUserAuthRequired: any; + userValue: any; +}; +function UserAuthSwitch({ + isUserAuthRequired, + setIsUserAuthRequired, + userValue, +}: UserAuthSwitch) { + return ( + + + + Require users to auth? + + { + setIsUserAuthRequired(e.target.checked); + }} + /> + + + When checked, users will have to authorize the Slack integration + before they're able to run this Zipper app and see the output. + Requires at least 1 user scope. The user's Slack token will be + available via the HandlerContext at runtime. + + + + ); +} + +function SlackConnectorFormConUserEdit({ + appId, + botOnChange, + botOptions, + botValue, + clientId, + connector, + isOwnClientIdRequired, + setClientId, + setIsOwnClientIdRequired, + user, + userOnChange, + userOptions, + userValue, +}: any) { + const [isUserAuthRequired, setIsUserAuthRequired] = useState( + connector.data?.isUserAuthRequired, + ); + const utils = trpc.useContext(); + const [isSaving, setIsSaving] = useState(false); + const [clientSecret, setClientSecret] = useState(''); + + const existingSecret = trpc.secret.get.useQuery( + { appId, key: botTokenName }, + { enabled: !!user }, + ); + + const existingUserSecret = trpc.secret.get.useQuery( + { appId, key: userTokenName }, + { enabled: !!user }, + ); + + const addSecretMutation = trpc.secret.add.useMutation(); + const getAppById = trpc.app.byId.useQuery({ id: appId }); + + const deleteConnectorMutation = trpc.slackConnector.delete.useMutation({ + async onSuccess() { + await utils.slackConnector.get.invalidate({ appId }); + await utils.secret.get.invalidate({ appId, key: botTokenName }); + }, + }); + + const { getModelByFilename } = useEditorContext(); + const { boot } = useRunAppContext(); + + const existingInstallation = + (existingSecret.data || existingUserSecret.data) && + connector.data?.metadata; + + const router = useRouter(); + + useEffect(() => { + setIsUserAuthRequired(connector.data?.isUserAuthRequired); + setClientId(connector.data?.clientId || ''); + setIsOwnClientIdRequired(!!connector.data?.clientId); + }, [connector.isSuccess]); + + if (existingInstallation) { + return ( + + ); + } + + return ( + <> + + + + Configuration + + + Bot Scopes + + + + User Scopes + + + + {/* {requiresOwnClientIdSwitch()} */} + + + + + + + + ); +} +type SlackCOnnectorExistingInstalationP = { + connector: any; + isUserAuthRequired: any; + setIsUserAuthRequired: any; + userValue: any; + isSaving: boolean; + setIsSaving: any; + deleteConnectorMutation: any; + appId: any; +}; +function SlackConnectorExistingInstalation( + props: SlackCOnnectorExistingInstalationP, +) { + const cancelRef = useRef() as React.MutableRefObject; + const { isOpen, onClose } = useDisclosure(); + + return ( + <> + + + + Configuration + + Installed to + + + + {(props.connector.data?.metadata as any)['team']['name']} + + + + + Installation details + + } + fontSize="sm" + py="2" + > + {props.connector.data?.clientId && ( + + Client ID: + {props.connector.data?.clientId} + + )} + + Bot User ID: + + { + (props.connector.data?.metadata as any)[ + 'bot_user_id' + ] + } + + + + Bot Scopes: + + {( + (props.connector.data?.metadata as any)['scope'] || + '' + ) + .split(',') + .map((scope: string) => ( + {scope} + ))} + + + + {(props.connector.data?.metadata as any)['authed_user'] && + (props.connector.data?.metadata as any)['authed_user'][ + 'scope' + ] && ( + + User Scopes: + + {(props.connector.data?.metadata as any)[ + 'authed_user' + ]['scope'] + .split(',') + .map((scope: string) => ( + {scope} + ))} + + + )} + + + + + + {/* TODO: Uninstall */} + {/* {slackAuthURL.data && ( + + )} */} + + + + + + + + + + Uninstall Slack App + + + + Are you sure? You can't undo this action afterwards. + + + + + + + + + + + ); +} + +type RequireOwnClientIdSwitchP = { + isOwnClientIdRequired: any; + setIsOwnClientIdRequired: any; + clientId: any; + setClientId: any; + clientSecret: string; + setClientSecret: any; +}; +function RequireOwnClientIdSwitch(props: RequireOwnClientIdSwitchP) { + return ( + + + + Custom client ID? + + { + props.setIsOwnClientIdRequired(e.target.checked); + }} + /> + + + When checked, you can specify your own Slack client ID and secret. + Useful if you need to enable features like the Events API or Slash + Commands. + + + + + Client ID + props.setClientId(e.target.value)} + /> + + + + + Client Secret + + SLACK_CLIENT_SECRET + + + + props.setClientSecret(e.target.value)} + /> + + + + Redirect URL + + Set your Slack app's redirect URL to:{' '} + {process.env.NODE_ENV === 'development' ? ( + https://your.ngrok.url/connectors/slack/auth + ) : ( + https://zipper.dev/connectors/slack/auth + )} + + + + + + ); +} + export default SlackConnectorForm; diff --git a/apps/zipper.dev/src/pages/api/omni/migrate-slack-connector/index.ts b/apps/zipper.dev/src/pages/api/omni/migrate-slack-connector/index.ts new file mode 100644 index 000000000..3d7d64b85 --- /dev/null +++ b/apps/zipper.dev/src/pages/api/omni/migrate-slack-connector/index.ts @@ -0,0 +1,83 @@ +import { + successResponse, + methodNotAllowed, + createOmniApiHandler, +} from '~/server/utils/omni.utils'; +import { HttpMethod } from '@zipper/types'; +import { buildAndStoreApplet } from '~/utils/eszip-build-applet'; +import { prisma } from '~/server/prisma'; +import { code as SLACK_CONNECTOR_V2_CODE } from '~/connectors/slack/constants'; +import { updateConnectorConfig } from '~/utils/connector-codemod'; + +const script = async () => { + const allSlackScripts = await prisma.script.findMany({ + where: { filename: 'slack-connector.ts' }, + }); + + // for each slack-connectors in scripts table: + for (const script of allSlackScripts) { + // - grab the client_id, bot/user scopes from the database + const connectorData = await prisma.appConnector.findFirst({ + where: { type: 'slack', appId: script.appId }, + }); + + // If there is a connector in the database, we need to add the config to the code + if (connectorData?.clientId) { + // Add the config to the code + const codeWithConfig = updateConnectorConfig( + SLACK_CONNECTOR_V2_CODE, + 'slackConnectorConfig', + { + clientId: connectorData.clientId, + botScopes: connectorData.workspaceScopes, + userScopes: connectorData.userScopes, + }, + ); + // Update the code column in the database + await prisma.script.update({ + where: { id: script.id }, + data: { code: codeWithConfig }, + }); + } else { + // Slack connector isnt in the database, lets just update the code to V2 + await prisma.script.update({ + where: { id: script.id }, + data: { code: SLACK_CONNECTOR_V2_CODE }, + }); + } + + // - ship a new version (playground only) // TODO: how? + const app = await prisma.app.findFirst({ + where: { id: script.appId }, + include: { scripts: true }, + }); + + if (!app) throw new Error(`App not found for script ${script.id}`); + + const { hash } = await buildAndStoreApplet({ app }); + if (hash !== app.playgroundVersionHash) { + // update the playground hash to use the new version + await prisma.app.update({ + where: { id: app.id }, + data: { playgroundVersionHash: hash }, + }); + } + } +}; + +export default createOmniApiHandler(async (req, res) => { + switch (req.method) { + case HttpMethod.GET: + await script(); + return successResponse({ + res, + status: 200, + body: { + data: [], + ok: true, + }, + }); + default: + return methodNotAllowed({ method: req.method, res }); + } +}); diff --git a/apps/zipper.dev/src/pages/connectors/slack/auth.tsx b/apps/zipper.dev/src/pages/connectors/slack/auth.tsx index 996cfedf1..703c5a9ae 100644 --- a/apps/zipper.dev/src/pages/connectors/slack/auth.tsx +++ b/apps/zipper.dev/src/pages/connectors/slack/auth.tsx @@ -25,17 +25,6 @@ const SlackAuth: NextPageWithLayout = () => { }, ); - if (error || errorMessage) { - return ( -
- - - {error_description || errorMessage} - -
- ); - } - if (!state) return <>Missing state; if (!code) return <>Missing code; @@ -67,7 +56,9 @@ const SlackAuth: NextPageWithLayout = () => { - Exchanging one-time code from Slack for an API token. Hold tight... + {error || errorMessage + ? error_description || errorMessage + : 'Exchanging one-time code from Slack for an API token. Hold tight...'} diff --git a/apps/zipper.dev/src/pages/connectors/slack/startAuth.tsx b/apps/zipper.dev/src/pages/connectors/slack/startAuth.tsx index d84236722..537ded09d 100644 --- a/apps/zipper.dev/src/pages/connectors/slack/startAuth.tsx +++ b/apps/zipper.dev/src/pages/connectors/slack/startAuth.tsx @@ -5,15 +5,8 @@ import { trpc } from '~/utils/trpc'; const SlackStartAuth: NextPageWithLayout = () => { const router = useRouter(); - console.log(router.query.appId as string); const slackAuthURL = trpc.slackConnector.getAuthUrl.useQuery({ appId: router.query.appId as string, - scopes: { - bot: (router.query.botScopes as string).split(','), - user: (router.query.userScopes as string).split(','), - }, - postInstallationRedirect: router.query.postInstallRedirect as string, - redirectUri: router.query.redirectUri as string, }); useEffect(() => { diff --git a/apps/zipper.dev/src/server/routers/app.router.ts b/apps/zipper.dev/src/server/routers/app.router.ts index 3793d6c8e..1a7e3e91d 100644 --- a/apps/zipper.dev/src/server/routers/app.router.ts +++ b/apps/zipper.dev/src/server/routers/app.router.ts @@ -550,6 +550,7 @@ export const appRouter = createTRPCRouter({ const appAuthor: AppletAuthorReturnType = { name: '', + slug: '', organization: '', image: '', orgImage: '', @@ -575,6 +576,7 @@ export const appRouter = createTRPCRouter({ appAuthor.name = authorName?.name || ''; appAuthor.image = authorName?.image || ''; + appAuthor.slug = authorName?.slug || ''; const canEdit = canUserEdit(app, ctx); if (app.isPrivate && !canEdit) @@ -776,7 +778,7 @@ export const appRouter = createTRPCRouter({ console.log('---FILENAME---', script.filename); const inputParams = parseInputForTypes({ code: script.code }); - if (!inputParams) return { ok: false }; + if (!inputParams) return { ok: false } as const; const inputs = getInputsFromFormData(input.formData, inputParams); @@ -816,7 +818,7 @@ export const appRouter = createTRPCRouter({ }, }); - return { ok: true, filename: script.filename, version, result }; + return { ok: true, filename: script.filename, version, result } as const; }), fork: protectedProcedure .input( diff --git a/apps/zipper.dev/src/server/routers/slackConnector.router.ts b/apps/zipper.dev/src/server/routers/slackConnector.router.ts index 4a95360f3..e23f5bb4f 100644 --- a/apps/zipper.dev/src/server/routers/slackConnector.router.ts +++ b/apps/zipper.dev/src/server/routers/slackConnector.router.ts @@ -10,13 +10,14 @@ import { decryptFromBase64, decryptFromHex, encryptToBase64, - encryptToHex, __ZIPPER_TEMP_USER_ID, } from '@zipper/utils'; import fetch from 'node-fetch'; import { AppConnectorUserAuth, Prisma } from '@prisma/client'; import { filterTokenFields } from '~/server/utils/json'; import { createTRPCRouter, publicProcedure } from '../root'; +import { getSlackConfig } from '~/utils/connectors'; +import { initLocalApplet } from '~/utils/local-applet'; export const slackConnectorRouter = createTRPCRouter({ get: publicProcedure @@ -28,57 +29,59 @@ export const slackConnectorRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { await hasAppReadPermission({ ctx, appId: input.appId }); - return prisma.appConnector.findFirst({ + const app = await prisma.app.findFirst({ where: { - appId: input.appId, - type: 'slack', + id: input.appId, + }, + select: { + slug: true, }, }); + if (!app?.slug) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'No app found', + }); + } + const slackConfig = await getSlackConfig(app?.slug); + return slackConfig; }), getAuthUrl: publicProcedure .input( z.object({ appId: z.string(), - scopes: z.object({ - bot: z.array(z.string()), - user: z.array(z.string()), - }), - postInstallationRedirect: z.string().optional(), - redirectUri: z.string().optional(), }), ) .query(async ({ ctx, input }) => { await hasAppReadPermission({ ctx, appId: input.appId }); - const { appId, scopes, postInstallationRedirect, redirectUri } = input; - const state = encryptToHex( - `${appId}::${postInstallationRedirect || ''}`, - process.env.ENCRYPTION_KEY || '', - ); - - // check if we have a client id for this app in the appConnector table - const appConnector = await prisma.appConnector.findFirst({ + const appInfo = await prisma.app.findFirst({ where: { - appId, - type: 'slack', + id: input.appId, + }, + select: { + slug: true, }, }); - const clientId = - appConnector?.clientId || process.env.NEXT_PUBLIC_SLACK_CLIENT_ID!; - - const url = new URL('https://slack.com/oauth/v2/authorize'); - url.searchParams.set('client_id', clientId); - url.searchParams.set('scope', scopes.bot.join(',')); - url.searchParams.set('user_scope', scopes.user.join(',')); - url.searchParams.set('state', state); - if (redirectUri) { - url.searchParams.set('redirect_uri', redirectUri); + if (!appInfo?.slug) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'App not found', + }); } - return { - url: url.toString(), - }; + const url = await initLocalApplet(appInfo!.slug) + .path('slack-connector') + .run({ action: 'get-auth-url' }) + .catch(() => { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: `Something went wrong while trying to get the connector config for ${appInfo.slug}`, + }); + }); + + return { url }; }), delete: publicProcedure .input( @@ -168,13 +171,22 @@ export const slackConnectorRouter = createTRPCRouter({ throw new TRPCError({ code: 'UNAUTHORIZED' }); } - const appConnector = await prisma.appConnector.findFirst({ + const appInfo = await prisma.app.findFirst({ where: { - appId, - type: 'slack', + id: appId, + }, + select: { + slug: true, }, }); + if (!appInfo) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'App not found', + }); + } + const clientSecretRecord = await prisma.secret.findFirst({ where: { appId, @@ -185,8 +197,17 @@ export const slackConnectorRouter = createTRPCRouter({ let clientId = process.env.NEXT_PUBLIC_SLACK_CLIENT_ID!; let clientSecret = process.env.SLACK_CLIENT_SECRET!; - if (appConnector?.clientId && clientSecretRecord) { - clientId = appConnector?.clientId; + const slackConfig = await getSlackConfig(appInfo.slug); + if (!slackConfig) { + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: + 'Something went wrong while trying to get the connector config', + }); + } + + if (slackConfig.clientId && clientSecretRecord) { + clientId = slackConfig.clientId; clientSecret = decryptFromBase64( clientSecretRecord.encryptedValue, process.env.ENCRYPTION_KEY, diff --git a/apps/zipper.dev/src/utils/boot-info-utils.ts b/apps/zipper.dev/src/utils/boot-info-utils.ts index 77043eacb..e59441ac0 100644 --- a/apps/zipper.dev/src/utils/boot-info-utils.ts +++ b/apps/zipper.dev/src/utils/boot-info-utils.ts @@ -266,6 +266,7 @@ export async function getAppAuthor({ organization: '', image: '', orgImage: '', + slug: '', }; const authorName = await prisma.user.findUnique({ @@ -288,6 +289,7 @@ export async function getAppAuthor({ appAuthor.name = authorName?.name || ''; appAuthor.image = authorName?.image || ''; + appAuthor.slug = authorName?.slug || ''; return appAuthor; } diff --git a/apps/zipper.dev/src/utils/connector-codemod.ts b/apps/zipper.dev/src/utils/connector-codemod.ts new file mode 100644 index 000000000..5d360cb79 --- /dev/null +++ b/apps/zipper.dev/src/utils/connector-codemod.ts @@ -0,0 +1,15 @@ +import { withParser, ObjectExpression } from 'jscodeshift'; + +export const updateConnectorConfig = ( + code: string, + configObjectName: string, + newConfig: Record, +) => { + const $j = withParser('tsx')(code); + + return $j + .findVariableDeclarators(configObjectName) + .find(ObjectExpression) + .replaceWith(JSON.stringify(newConfig, null, 2)) + .toSource(); +}; diff --git a/apps/zipper.dev/src/utils/connectors.ts b/apps/zipper.dev/src/utils/connectors.ts new file mode 100644 index 000000000..9ade02223 --- /dev/null +++ b/apps/zipper.dev/src/utils/connectors.ts @@ -0,0 +1,27 @@ +import { initLocalApplet } from './local-applet'; + +type SlackConfig = { + zipperAppId: string; + clientId?: string; + userScopes: string[]; + botScopes: string[]; + postInstallRedirect?: string; +}; +/** + * Retrieves the Slack configuration from the provided script code. + * @param scriptCode The script code containing the Slack configuration. + * @returns The Slack configuration object. + */ +export const getSlackConfig = async ( + appSlug: string, +): Promise => { + // A good otimization step here would be avoid calling the applet if we know that the route is not available + try { + const res = await initLocalApplet(appSlug) + .path('slack-connector') + .run({ action: 'get-config' }); + return res as SlackConfig; + } catch { + return null; + } +}; diff --git a/apps/zipper.dev/src/utils/get-user-info.ts b/apps/zipper.dev/src/utils/get-user-info.ts index 5aa996a85..059682149 100644 --- a/apps/zipper.dev/src/utils/get-user-info.ts +++ b/apps/zipper.dev/src/utils/get-user-info.ts @@ -12,6 +12,7 @@ export type UserInfoReturnType = { export type AppletAuthorReturnType = { name: string; + slug: string; organization: string; image: string; orgImage: string; diff --git a/apps/zipper.dev/src/utils/local-applet.ts b/apps/zipper.dev/src/utils/local-applet.ts new file mode 100644 index 000000000..485e7fbcb --- /dev/null +++ b/apps/zipper.dev/src/utils/local-applet.ts @@ -0,0 +1,12 @@ +import { initApplet, AppletOptions } from '@zipper-inc/client-js'; + +export const initLocalApplet = (slug: string, options?: AppletOptions) => + initApplet( + slug, + process.env.NODE_ENV === 'development' + ? { + ...options, + overrideZipperRunUrl: `http://${process.env.NEXT_PUBLIC_ZIPPER_DOT_RUN_HOST}`, + } + : options, + ); diff --git a/apps/zipper.run/src/utils/relay-middleware.ts b/apps/zipper.run/src/utils/relay-middleware.ts index 39ceb8787..b75b6950b 100644 --- a/apps/zipper.run/src/utils/relay-middleware.ts +++ b/apps/zipper.run/src/utils/relay-middleware.ts @@ -329,6 +329,7 @@ export async function relayRequest( version, url: `https://${getAppLink(app.slug)}`, connectorsWithUserAuth: Object.keys(userConnectorTokens), + author: app.appAuthor, }, inputs: request.method === 'GET' ? queryParameters : body, originalRequest: { diff --git a/package.json b/package.json index 7ab3464bd..924ee95c0 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "build": "yarn install; yarn env:link; turbo run build", "build:production": "yarn install; yarn env:link; turbo run build:production", "dev": "yarn install; yarn env:update; yarn env:link; yarn migrate; turbo run dev --parallel", + "postinstall": "patch-package", "env:link": "sh ./bin/link-env-files.sh", "env:update": "yarn dotenv -c .env.local yarn swc-node ./bin/ts/env-update.ts", "get-started": "yarn install; yarn env:update; yarn env:link; yarn migrate; yarn seed", @@ -36,6 +37,7 @@ "dotenv": "^16.0.3", "dotenv-cli": "^7.2.1", "get-port-please": "^3.0.1", + "patch-package": "^8.0.0", "prettier": "latest", "signale": "^1.4.0", "turbo": "^1.7.3" diff --git a/packages/@zipper-framework/deno/zipper.d.ts b/packages/@zipper-framework/deno/zipper.d.ts index 870924e90..50232bbf3 100644 --- a/packages/@zipper-framework/deno/zipper.d.ts +++ b/packages/@zipper-framework/deno/zipper.d.ts @@ -544,6 +544,13 @@ declare namespace Zipper { version: string; url?: string; connectorsWithUserAuth?: string[]; + author?: { + name: string; + slug: string; + organization: string; + image: string; + orgImage: string; + }; }; /** diff --git a/packages/@zipper-types/src/types/boot-info.ts b/packages/@zipper-types/src/types/boot-info.ts index f2119debb..9670c609e 100644 --- a/packages/@zipper-types/src/types/boot-info.ts +++ b/packages/@zipper-types/src/types/boot-info.ts @@ -15,6 +15,7 @@ export type AppInfo = { editors: { userId: string; appId: string; isOwner: boolean }[]; appAuthor?: { name: string; + slug: string; organization: string; image: string; orgImage: string; diff --git a/packages/@zipper-ui/package.json b/packages/@zipper-ui/package.json index 86b4cb782..cdd283e50 100644 --- a/packages/@zipper-ui/package.json +++ b/packages/@zipper-ui/package.json @@ -19,7 +19,7 @@ "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@uploadthing/react": "6.0.2", - "@zipper-inc/client-js": "^0.1.6", + "@zipper-inc/client-js": "^0.1.7", "@zipper/types": "*", "@zipper/utils": "*", "framer-motion": "^9.0.0", diff --git a/packages/@zipper-utils/src/utils/crypto.test.ts b/packages/@zipper-utils/src/utils/crypto.test.ts new file mode 100644 index 000000000..1342ef418 --- /dev/null +++ b/packages/@zipper-utils/src/utils/crypto.test.ts @@ -0,0 +1,89 @@ +import nodeCrypto from 'node:crypto'; + +const ALGORITHM = { + // 128 bit auth tag is recommended for GCM + AUTH_TAG_BYTE_LEN: 16, + // NIST recommends 96 bits or 12 bytes IV for GCM to promote interoperability, efficiency, and simplicity of design + IV_BYTE_LEN: 12, + // NOTE: 256 (in algorithm name) is key size (block size for AES is always 128) + KEY_BYTE_LEN: 32, + // to prevent rainbow table attacks + SALT_BYTE_LEN: 16, +}; + +const NodeCrypto = (msg: string, key: string) => { + const iv = nodeCrypto.randomBytes(ALGORITHM.IV_BYTE_LEN); + const keyInBytes = Buffer.from(key, 'hex'); + const cipher = nodeCrypto.createCipheriv('aes-256-gcm', keyInBytes, iv, { + authTagLength: ALGORITHM.AUTH_TAG_BYTE_LEN, + }); + let encryptedMessage = cipher.update(msg); + encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()]); + return Buffer.concat([iv, encryptedMessage, cipher.getAuthTag()]); +}; + +const NodeDecrypt = (ciphertext: Buffer, key: string) => { + const authTag = ciphertext.subarray(-16); + const iv = ciphertext.subarray(0, 12); + const encryptedMessage = ciphertext.subarray(12, -16); + const keyInBytes = Buffer.from(key, 'hex'); + const decipher = nodeCrypto.createDecipheriv('aes-256-gcm', keyInBytes, iv, { + authTagLength: ALGORITHM.AUTH_TAG_BYTE_LEN, + }); + decipher.setAuthTag(authTag); + const messagetext = decipher.update(encryptedMessage); + + const bufferOut = Buffer.concat([messagetext, decipher.final()]); + return bufferOut.toString('utf8'); +}; + +const hexToUint8Array = (hexString: string): Uint8Array => { + const result = new Uint8Array( + hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); + return result; +}; + +// Used in `slack-install-link` applet +const WebCrypto = async (msg: string, key: string) => { + const te = new TextEncoder(); + const iv = crypto.getRandomValues(new Uint8Array(12)); + + const keyInBytes = hexToUint8Array(key); + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyInBytes, + { name: 'AES-GCM', length: 256 }, + false, + ['encrypt', 'decrypt'], + ); + + const encrypted = await crypto.subtle.encrypt( + { name: 'AES-GCM', iv, tagLength: 128 }, + cryptoKey, + te.encode(msg), + ); + + const authTag = new Uint8Array(encrypted.slice(-16)); + const ciphertext = new Uint8Array(encrypted.slice(0, -16)); + + return new Uint8Array([...iv, ...ciphertext, ...authTag]); +}; + +const KEY = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; + +describe('crypto', () => { + test('should encrypt and decrypt [NODE]', async () => { + const msg = 'hello::world'; + const encrypted = NodeCrypto(msg, KEY); + const decrypted = NodeDecrypt(encrypted, KEY); + expect(decrypted).toEqual(msg); + }); + + test('should encrypt [WebCrypto] and decrypt [Node]', async () => { + const msg = 'hello::world'; + const encrypted = await WebCrypto(msg, KEY); + const decrypted = NodeDecrypt(Buffer.from(encrypted), KEY); + expect(decrypted).toEqual(msg); + }); +}); diff --git a/packages/@zipper-utils/src/utils/generate-for-framework.ts b/packages/@zipper-utils/src/utils/generate-for-framework.ts index 082b7bb49..c9e2129f5 100644 --- a/packages/@zipper-utils/src/utils/generate-for-framework.ts +++ b/packages/@zipper-utils/src/utils/generate-for-framework.ts @@ -14,7 +14,7 @@ const EXPORTS_REG_EXP = new RegExp( ); const ZIPPER_CLIENT_JS_URL = - 'https://deno.land/x/zipper_client_js@v0.1.6/mod.ts'; + 'https://deno.land/x/zipper_client_js@v0.1.7/mod.ts'; const NEWLINE = '\n'; diff --git a/packages/@zipper-utils/src/utils/safe-json.ts b/packages/@zipper-utils/src/utils/safe-json.ts index f14688130..d8993471b 100644 --- a/packages/@zipper-utils/src/utils/safe-json.ts +++ b/packages/@zipper-utils/src/utils/safe-json.ts @@ -1,9 +1,9 @@ -export function safeJSONParse( +export function safeJSONParse( json = '', reviver?: (key: string, value: any) => any, fallback?: any, verbose = false, -): any { +): T { // Handle trying to parse JSON that's already an object if (typeof json === 'object') { return JSON.parse(JSON.stringify(json), reviver); diff --git a/yarn.lock b/yarn.lock index df8ca84a3..4aed37d80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5893,10 +5893,10 @@ resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/jscodeshift@^0.11.10": - version "0.11.10" - resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.11.10.tgz#f0eadff9cfb3c6099b79d8fab9afb50e5450f1ae" - integrity sha512-F2AAky0XyJPQsKjyBFaaHlecGgwNf1HDj+soOjJYHOP9fw+0cVlZRZfqxuQR8YFQYwXqHuaxSbxktZGoqMoZsw== +"@types/jscodeshift@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.11.11.tgz#30d7c986f372cd63c670017371da8fbced2b7acf" + integrity sha512-d7CAfFGOupj5qCDqMODXxNz2/NwCv/Lha78ZFbnr6qpk3K98iSB8I+ig9ERE2+EeYML352VMRsjPyOpeA+04eQ== dependencies: ast-types "^0.14.1" recast "^0.20.3" @@ -6353,7 +6353,7 @@ "@uploadthing/shared@^6.0.2": version "6.0.2" - resolved "https://registry.npmjs.org/@uploadthing/shared/-/shared-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/@uploadthing/shared/-/shared-6.0.2.tgz#8c804ee087b76254f06d76792967515bd12e6971" integrity sha512-yFOhvzBG8VB2qmEGWDuW/tV/ZcfRZ4a6rX7i9/ZS3IqHA0FO7CKa8yitltAl3yRdSI9it88iMEQg7Ah/ve8D4A== "@upstash/redis@1.24.3": @@ -6370,6 +6370,11 @@ dependencies: "@upstash/redis" "1.24.3" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + "@zag-js/element-size@0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.3.0.tgz" @@ -6380,10 +6385,10 @@ resolved "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.2.1.tgz" integrity sha512-19uTjoZGP4/Ax7kSNhhay9JA83BirKzpqLkeEAilrpdI1hE5xuq6q+tzJOsrMOOqJrm7LkmZp5lbsTQzvK2pYg== -"@zipper-inc/client-js@^0.1.6": - version "0.1.6" - resolved "https://registry.npmjs.org/@zipper-inc/client-js/-/client-js-0.1.6.tgz" - integrity sha512-55u1jG+HslS+Z4nVj3m0M5LFNw2DRVdRsF4kYt8cG7nsadSwxMYeqBUwGhlETWrKXZTR6iR0o5wgD44wnGnmRg== +"@zipper-inc/client-js@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@zipper-inc/client-js/-/client-js-0.1.7.tgz#c32b23797ac6e86649986e0711ec9e35663dbd96" + integrity sha512-ovuWjUeF0KKQRCWrABum0YewfVHJlhq9np9364ic8K60Pzwo5GuFoo7igAKohdq6XnWxSgQGsKTAXfo6kifIyg== abbrev@1, abbrev@^1.0.0: version "1.1.1" @@ -6668,6 +6673,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atomic-sleep@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" @@ -7056,7 +7066,7 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-bind@^1.0.4: +call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== @@ -7285,6 +7295,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz" integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== +ci-info@^3.7.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -9304,6 +9319,13 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" @@ -9441,6 +9463,16 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -10368,6 +10400,11 @@ is-decimal@^2.0.0: resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz" integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-dom@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz" @@ -10634,6 +10671,13 @@ is-window@^1.0.2: resolved "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz" integrity sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg== +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" @@ -11209,6 +11253,16 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.0.tgz#43d39c7c8da34bfaf785a61a56808b0def9f747d" + integrity sha512-zfA+5SuwYN2VWqN1/5HZaDzQKLJHaBVMZIIM+wuYjdptkaQsqzDdqjqf+lZZJUuJq1aanHiY8LhH8LmH+qBYJA== + dependencies: + call-bind "^1.0.5" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" @@ -11247,6 +11301,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + jsonp@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz" @@ -11328,6 +11387,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" @@ -12610,7 +12676,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -13383,6 +13449,14 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + openai-edge@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/openai-edge/-/openai-edge-1.1.1.tgz" @@ -13438,6 +13512,11 @@ ora@5.4.1, ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -13656,6 +13735,27 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" +patch-package@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61" + integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^4.1.2" + ci-info "^3.7.0" + cross-spawn "^7.0.3" + find-yarn-workspace-root "^2.0.0" + fs-extra "^9.0.0" + json-stable-stringify "^1.0.2" + klaw-sync "^6.0.0" + minimist "^1.2.6" + open "^7.4.2" + rimraf "^2.6.3" + semver "^7.5.3" + slash "^2.0.0" + tmp "^0.0.33" + yaml "^2.2.2" + path-browserify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz" @@ -14905,7 +15005,7 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@2: +rimraf@2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -15068,7 +15168,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.4: +semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -15239,6 +15339,11 @@ sisteransi@^1.0.5: resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" @@ -15866,6 +15971,13 @@ titleize@1.0.0: resolved "https://registry.npmjs.org/titleize/-/titleize-1.0.0.tgz" integrity sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" @@ -16875,6 +16987,11 @@ yaml@^2.2.1: resolved "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz" integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw== +yaml@^2.2.2: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== + yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"