Compare commits
8 commits
main
...
bump-rhf-v
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8c3f53e286 | ||
![]() |
88be2b8b18 | ||
![]() |
a260414894 | ||
![]() |
246789cb0c | ||
![]() |
4cb15a8033 | ||
![]() |
6b9f8ac32b | ||
![]() |
bd757776f2 | ||
![]() |
7efa970079 |
45 changed files with 6729 additions and 2981 deletions
6
.changeset/funny-fireants-turn.md
Normal file
6
.changeset/funny-fireants-turn.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
"@saleor/apps-cli": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Introduced Apps CLI.
|
||||||
|
The app is intended to help with common tasks in app development, like installing apps from manifest or debugging webhooks.
|
9
.changeset/healthy-crabs-study.md
Normal file
9
.changeset/healthy-crabs-study.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": patch
|
||||||
|
"saleor-app-monitoring": patch
|
||||||
|
"saleor-app-invoices": patch
|
||||||
|
"saleor-app-cms": patch
|
||||||
|
"saleor-app-crm": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Updated dependencies.
|
5
.changeset/weak-rocks-beg.md
Normal file
5
.changeset/weak-rocks-beg.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@saleor/react-hook-form-macaw": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Applied type fix for Macaw inputs.
|
5
.github/dependabot.yaml
vendored
5
.github/dependabot.yaml
vendored
|
@ -29,6 +29,11 @@ updates:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
|
||||||
# Apps
|
# Apps
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/apps/apps-cli"
|
||||||
|
open-pull-requests-limit: 0
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/apps/cms"
|
directory: "/apps/cms"
|
||||||
open-pull-requests-limit: 0
|
open-pull-requests-limit: 0
|
||||||
|
|
3
apps/apps-cli/.env.example
Normal file
3
apps/apps-cli/.env.example
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
INSTANCE_URL=https://demo.saleor.io/graphql/
|
||||||
|
USER_EMAIL=admin@example.com
|
||||||
|
USER_PASSWORD=password123
|
4
apps/apps-cli/.eslintrc
Normal file
4
apps/apps-cli/.eslintrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["saleor"]
|
||||||
|
}
|
148
apps/apps-cli/.gitignore
vendored
Normal file
148
apps/apps-cli/.gitignore
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# other
|
||||||
|
build/
|
||||||
|
.vscode/
|
||||||
|
binaries/
|
||||||
|
.saleor
|
||||||
|
token.txt
|
||||||
|
vendor/
|
||||||
|
test.js
|
||||||
|
dummy/
|
||||||
|
..bfg-report
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
package/
|
||||||
|
|
||||||
|
.type-coverage/
|
||||||
|
coverage-ts/
|
37
apps/apps-cli/README.md
Normal file
37
apps/apps-cli/README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
This project uses [pnpm](https://pnpm.io) for managing dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Watch Mode
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm watch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run CLI
|
||||||
|
|
||||||
|
```
|
||||||
|
node build/cli.js ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available commands
|
||||||
|
|
||||||
|
List of available commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
node dist/app-cli.js -h
|
||||||
|
```
|
||||||
|
|
||||||
|
Description of available arguments:
|
||||||
|
|
||||||
|
```
|
||||||
|
node dist/app-cli.js [command name] -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
If options are not passed as arguments, cli will try to read environment variables. Example configuration is available in `.env.example` file.
|
18
apps/apps-cli/codegen.ts
Normal file
18
apps/apps-cli/codegen.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/* eslint-disable import/no-default-export */
|
||||||
|
import { CodegenConfig } from "@graphql-codegen/cli";
|
||||||
|
|
||||||
|
const config: CodegenConfig = {
|
||||||
|
schema: "https://demo.saleor.io/graphql/",
|
||||||
|
documents: ["src/saleor-api/operations/*.ts"],
|
||||||
|
ignoreNoDocuments: true, // for better experience with the watcher
|
||||||
|
generates: {
|
||||||
|
"./src/saleor-api/generated/": {
|
||||||
|
preset: "client",
|
||||||
|
presetConfig: {
|
||||||
|
fragmentMasking: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
67
apps/apps-cli/package.json
Normal file
67
apps/apps-cli/package.json
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"name": "@saleor/apps-cli",
|
||||||
|
"description": "",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"author": "Saleor",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm bundle",
|
||||||
|
"generate": "graphql-codegen",
|
||||||
|
"lint": "prettier --write . && eslint src/**/*.ts --cache --fix",
|
||||||
|
"test": "pnpm vitest",
|
||||||
|
"typecov": "type-coverage --cache",
|
||||||
|
"typecov-report": "typescript-coverage-report",
|
||||||
|
"watch": "concurrently \"npm:watch-*\"",
|
||||||
|
"watch-esbuild": "esbuild --watch src/cli.ts --bundle --minify --outfile=dist/apps-cli.js --platform=node --format=esm --target=node18 --banner:js=\"import { createRequire } from 'module';const require = createRequire(import.meta.url);import { dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url));\" --out-extension:.js=.js",
|
||||||
|
"watch-generate": "graphql-codegen -w",
|
||||||
|
"watch-ts": "tsc --noEmit --watch --preserveWatchOutput"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"saleor": "./dist/apps-cli.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
|
"@inquirer/prompts": "^2.1.1",
|
||||||
|
"@oclif/core": "^1.26.2",
|
||||||
|
"@saleor/app-sdk": "0.40.1",
|
||||||
|
"chalk": "^5.2.0",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"enquirer": "^2.3.6",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"graphql": "^16.6.0",
|
||||||
|
"graphql-request": "^6.1.0",
|
||||||
|
"open": "^9.1.0",
|
||||||
|
"ora": "^6.3.1",
|
||||||
|
"semver": "^7.5.1",
|
||||||
|
"slugify": "^1.6.6",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@graphql-codegen/cli": "^4.0.1",
|
||||||
|
"@graphql-codegen/client-preset": "^4.0.1",
|
||||||
|
"@types/fs-extra": "^11.0.1",
|
||||||
|
"@types/node": "^20.3.1",
|
||||||
|
"@types/semver": "^7.5.0",
|
||||||
|
"@types/yargs": "^17.0.24",
|
||||||
|
"concurrently": "^8.2.0",
|
||||||
|
"esbuild": "^0.18.2",
|
||||||
|
"eslint": "^8.42.0",
|
||||||
|
"eslint-config-saleor": "workspace:*",
|
||||||
|
"pkg": "^5.8.1",
|
||||||
|
"prettier": "2.8.8",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsm": "^2.3.0",
|
||||||
|
"type-coverage": "^2.26.0",
|
||||||
|
"typescript": "^5.1.3",
|
||||||
|
"typescript-coverage-report": "^0.7.0",
|
||||||
|
"vitest": "^0.32.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18 || ^20"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/apps-cli.js"
|
||||||
|
],
|
||||||
|
"license": "BSD 3-Clause",
|
||||||
|
"type": "module"
|
||||||
|
}
|
158
apps/apps-cli/src/cli.ts
Normal file
158
apps/apps-cli/src/cli.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { createRequire } from "module";
|
||||||
|
import semver from "semver";
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
import { installAppCommand } from "./commands/install-app-command";
|
||||||
|
import { uninstallAppCommand } from "./commands/uninstall-app-command";
|
||||||
|
import "dotenv/config";
|
||||||
|
import { webhooksCommand } from "./commands/webhooks-command";
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const pkg = require("../package.json");
|
||||||
|
|
||||||
|
if (!semver.satisfies(process.versions.node, ">= 18")) {
|
||||||
|
console.error(`${chalk.red("ERROR")}: CLI requires Node.js 18.x or later`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = yargs(hideBin(process.argv))
|
||||||
|
.scriptName("apps-cli")
|
||||||
|
.version(pkg.version)
|
||||||
|
.alias("V", "version")
|
||||||
|
.usage("Usage: $0 <command> [options]")
|
||||||
|
.config({
|
||||||
|
instanceUrl: process.env.INSTANCE_URL,
|
||||||
|
userEmail: process.env.USER_EMAIL,
|
||||||
|
userPassword: process.env.USER_PASSWORD,
|
||||||
|
})
|
||||||
|
.command(
|
||||||
|
"installApp",
|
||||||
|
"Install an app on a Saleor instance based on provided manifest.",
|
||||||
|
(yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("instanceUrl", {
|
||||||
|
type: "string",
|
||||||
|
desc: "URL to the Saleor GraphQL API. Example: https://example.com/graphql/",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userEmail", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user email",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userPassword", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user password",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("manifestUrl", {
|
||||||
|
type: "string",
|
||||||
|
desc: "URL to the app manifest. Example: https://example.com/api/manifest",
|
||||||
|
demandOption: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(argv) => {
|
||||||
|
installAppCommand({
|
||||||
|
instanceUrl: argv.instanceUrl,
|
||||||
|
userEmail: argv.userEmail,
|
||||||
|
userPassword: argv.userPassword,
|
||||||
|
manifestUrl: argv.manifestUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
"uninstallApp",
|
||||||
|
"If no filter is passed, CLI will display a list of installed apps and ask which one to remove. Otherwise all apps matching the filter will be removed.",
|
||||||
|
(yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("instanceUrl", {
|
||||||
|
type: "string",
|
||||||
|
desc: "URL to the Saleor GraphQL API",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userEmail", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user email",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userPassword", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user password",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("manifestUrl", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Url to the app manifest which you want to remove",
|
||||||
|
})
|
||||||
|
.option("appName", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Name of the app to remove",
|
||||||
|
})
|
||||||
|
.option("appId", {
|
||||||
|
type: "string",
|
||||||
|
desc: "If of the app to remove",
|
||||||
|
})
|
||||||
|
.option("all", {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
desc: "Will remove all apps",
|
||||||
|
})
|
||||||
|
.option("force", {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
desc: "No confirmation",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(argv) => {
|
||||||
|
uninstallAppCommand({
|
||||||
|
instanceUrl: argv.instanceUrl,
|
||||||
|
userEmail: argv.userEmail,
|
||||||
|
userPassword: argv.userPassword,
|
||||||
|
manifestUrl: argv.manifestUrl,
|
||||||
|
appId: argv.appId,
|
||||||
|
all: argv.all,
|
||||||
|
force: argv.force,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command(
|
||||||
|
"webhooks",
|
||||||
|
"Print webhook details of installed app.",
|
||||||
|
(yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("instanceUrl", {
|
||||||
|
type: "string",
|
||||||
|
desc: "URL to the Saleor GraphQL API. Example: https://example.com/graphql/",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userEmail", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user email",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
.option("userPassword", {
|
||||||
|
type: "string",
|
||||||
|
desc: "Dashboard user password",
|
||||||
|
demandOption: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(argv) => {
|
||||||
|
webhooksCommand({
|
||||||
|
instanceUrl: argv.instanceUrl,
|
||||||
|
userEmail: argv.userEmail,
|
||||||
|
userPassword: argv.userPassword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.demandCommand(1, "You need at least one command before moving on")
|
||||||
|
.alias("h", "help")
|
||||||
|
.wrap(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await parser.parse();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("parser error");
|
||||||
|
}
|
35
apps/apps-cli/src/commands/install-app-command.ts
Normal file
35
apps/apps-cli/src/commands/install-app-command.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { installAndWaitForResult } from "../lib/install-and-wait-for-result";
|
||||||
|
import { getAccessTokenMutation } from "../saleor-api/operations/get-access-token-mutation";
|
||||||
|
import ora from "ora";
|
||||||
|
|
||||||
|
interface InstallAppCommandArgs {
|
||||||
|
instanceUrl: string;
|
||||||
|
userEmail: string;
|
||||||
|
userPassword: string;
|
||||||
|
manifestUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const installAppCommand = async ({
|
||||||
|
instanceUrl,
|
||||||
|
manifestUrl,
|
||||||
|
userEmail,
|
||||||
|
userPassword,
|
||||||
|
}: InstallAppCommandArgs) => {
|
||||||
|
const loginSpinner = ora("Logging into Saleor instance").start();
|
||||||
|
|
||||||
|
const token = await getAccessTokenMutation({
|
||||||
|
email: userEmail,
|
||||||
|
password: userPassword,
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
loginSpinner.succeed();
|
||||||
|
|
||||||
|
const installedAppData = await installAndWaitForResult({
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
token,
|
||||||
|
appManifestUrl: manifestUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`App ${installedAppData.name} (${installedAppData.id}) installed!`);
|
||||||
|
};
|
110
apps/apps-cli/src/commands/uninstall-app-command.ts
Normal file
110
apps/apps-cli/src/commands/uninstall-app-command.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { checkbox, confirm } from "@inquirer/prompts";
|
||||||
|
import { getAccessTokenMutation } from "../saleor-api/operations/get-access-token-mutation";
|
||||||
|
import { getAppsListQuery } from "../saleor-api/operations/get-apps-list-query";
|
||||||
|
import { uninstallAppMutation } from "../saleor-api/operations/uninstall-app-mutation";
|
||||||
|
import { filterApps } from "../lib/filter-apps";
|
||||||
|
import ora from "ora";
|
||||||
|
|
||||||
|
interface UninstallAppCommandArgs {
|
||||||
|
instanceUrl: string;
|
||||||
|
userEmail: string;
|
||||||
|
userPassword: string;
|
||||||
|
manifestUrl?: string;
|
||||||
|
appName?: string;
|
||||||
|
appId?: string;
|
||||||
|
all?: boolean;
|
||||||
|
force?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uninstallAppCommand = async ({
|
||||||
|
instanceUrl,
|
||||||
|
manifestUrl,
|
||||||
|
userEmail,
|
||||||
|
userPassword,
|
||||||
|
all,
|
||||||
|
force,
|
||||||
|
appId,
|
||||||
|
appName,
|
||||||
|
}: UninstallAppCommandArgs) => {
|
||||||
|
const loginSpinner = ora("Logging into Saleor instance").start();
|
||||||
|
|
||||||
|
const token = await getAccessTokenMutation({
|
||||||
|
email: userEmail,
|
||||||
|
password: userPassword,
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
loginSpinner.succeed();
|
||||||
|
|
||||||
|
const appIdsToRemove: string[] = [];
|
||||||
|
|
||||||
|
if (appId) {
|
||||||
|
appIdsToRemove.push(appId);
|
||||||
|
} else {
|
||||||
|
const appListSpinner = ora("Fetching installed apps").start();
|
||||||
|
|
||||||
|
const installedApps = await getAppsListQuery({
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
appListSpinner.succeed();
|
||||||
|
|
||||||
|
if (!installedApps.length) {
|
||||||
|
console.log("No apps installed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display CLI interface with multiselect if none of the filters were provided
|
||||||
|
if (appId || appName || manifestUrl) {
|
||||||
|
const filteredApps = filterApps({
|
||||||
|
apps: installedApps,
|
||||||
|
filter: {
|
||||||
|
id: appId,
|
||||||
|
name: appName,
|
||||||
|
manifestUrl: manifestUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
appIdsToRemove.push(...filteredApps.map((app) => app.id));
|
||||||
|
} else if (all) {
|
||||||
|
appIdsToRemove.push(...installedApps.map((app) => app.id));
|
||||||
|
} else {
|
||||||
|
const selectedIds = await checkbox({
|
||||||
|
message: "Select apps to uninstall",
|
||||||
|
choices: installedApps.map((app) => ({
|
||||||
|
name: app.name ? `${app.name} (${app.id}) ${app.type}` : app.id,
|
||||||
|
value: app.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
appIdsToRemove.push(...selectedIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const confirmed = force
|
||||||
|
? true
|
||||||
|
: await confirm({
|
||||||
|
message: `${appIdsToRemove.length} apps will be removed. Continue?`,
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
console.log("Operation aborted - no confirmation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uninstallSpinner = ora("Uninstalling apps").start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
appIdsToRemove.map((appId) =>
|
||||||
|
uninstallAppMutation({ saleorApiUrl: instanceUrl, token, id: appId })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
uninstallSpinner.fail();
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uninstallSpinner.succeed();
|
||||||
|
};
|
120
apps/apps-cli/src/commands/webhooks-command.ts
Normal file
120
apps/apps-cli/src/commands/webhooks-command.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import ora from "ora";
|
||||||
|
import { getAccessTokenMutation } from "../saleor-api/operations/get-access-token-mutation";
|
||||||
|
import { getAppsListQuery } from "../saleor-api/operations/get-apps-list-query";
|
||||||
|
import { select } from "@inquirer/prompts";
|
||||||
|
import { getAppWebhooksQuery } from "../saleor-api/operations/get-app-webhooks-query";
|
||||||
|
import { removeWebhookMutation } from "../saleor-api/operations/remove-webhook-mutation";
|
||||||
|
|
||||||
|
interface DumpMetadataCommandArgs {
|
||||||
|
instanceUrl: string;
|
||||||
|
userEmail: string;
|
||||||
|
userPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webhooksCommand = async ({
|
||||||
|
instanceUrl,
|
||||||
|
userEmail,
|
||||||
|
userPassword,
|
||||||
|
}: DumpMetadataCommandArgs) => {
|
||||||
|
const loginSpinner = ora("Logging into Saleor instance").start();
|
||||||
|
|
||||||
|
const token = await getAccessTokenMutation({
|
||||||
|
email: userEmail,
|
||||||
|
password: userPassword,
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
loginSpinner.succeed();
|
||||||
|
|
||||||
|
const appListSpinner = ora("Fetching installed apps").start();
|
||||||
|
|
||||||
|
const installedApps = await getAppsListQuery({
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
appListSpinner.succeed();
|
||||||
|
|
||||||
|
if (!installedApps.length) {
|
||||||
|
console.log("No apps installed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = await select({
|
||||||
|
message: "Select app",
|
||||||
|
choices: installedApps.map((app) => ({
|
||||||
|
name: app.name ? `${app.name} (${app.id})` : app.id,
|
||||||
|
value: app.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const webhooksData = await getAppWebhooksQuery({
|
||||||
|
appId,
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!webhooksData.length) {
|
||||||
|
console.log("Application has no webhooks configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhook = await select({
|
||||||
|
message: "Select webhook to investigate",
|
||||||
|
choices: webhooksData.map((webhook) => ({
|
||||||
|
name: `${webhook.name} (${[...webhook.syncEvents, ...webhook.asyncEvents]
|
||||||
|
.map((e) => e.name)
|
||||||
|
.join(", ")})`,
|
||||||
|
value: webhook,
|
||||||
|
description: `
|
||||||
|
Target url: ${webhook.targetUrl}
|
||||||
|
Active: ${webhook.isActive}
|
||||||
|
Captured event deliveries count: ${webhook.eventDeliveries?.edges.length}
|
||||||
|
`,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const operation = await select({
|
||||||
|
message: "Operation",
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
name: "List event deliveries",
|
||||||
|
value: "list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remove webhook",
|
||||||
|
value: "remove",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (operation === "list") {
|
||||||
|
console.log("Number of entries: ", webhook.eventDeliveries?.edges.length);
|
||||||
|
for (const deliveryEdge of webhook.eventDeliveries?.edges ?? []) {
|
||||||
|
const delivery = deliveryEdge.node;
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
Event type: ${delivery.eventType}
|
||||||
|
Created at: ${delivery.createdAt}
|
||||||
|
Status: ${delivery.status}`);
|
||||||
|
const attempts = delivery.attempts?.edges ?? [];
|
||||||
|
const lastAttempt = attempts[attempts.length - 1]?.node;
|
||||||
|
|
||||||
|
if (lastAttempt) {
|
||||||
|
console.log(`
|
||||||
|
Date of the last attempt: ${lastAttempt.createdAt}
|
||||||
|
Status: ${lastAttempt.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (operation === "remove") {
|
||||||
|
const removeSpinner = ora("Removing webhook...").start();
|
||||||
|
|
||||||
|
await removeWebhookMutation({
|
||||||
|
saleorApiUrl: instanceUrl,
|
||||||
|
token,
|
||||||
|
webhookId: webhook.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
removeSpinner.succeed();
|
||||||
|
}
|
||||||
|
};
|
21
apps/apps-cli/src/lib/fetch-app-manifest.ts
Normal file
21
apps/apps-cli/src/lib/fetch-app-manifest.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { AppManifest } from "@saleor/app-sdk/types";
|
||||||
|
|
||||||
|
export const fetchAppManifest = async (manifestUrl: string) => {
|
||||||
|
const manifestDataResponse = await fetch(manifestUrl);
|
||||||
|
|
||||||
|
let manifestData: AppManifest;
|
||||||
|
|
||||||
|
if (!manifestDataResponse.ok) {
|
||||||
|
console.log("Error fetching manifest");
|
||||||
|
throw new Error("Error fetching manifest");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
manifestData = (await manifestDataResponse.json()) as AppManifest;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error parsing manifest");
|
||||||
|
throw new Error("Error parsing manifest");
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifestData;
|
||||||
|
};
|
65
apps/apps-cli/src/lib/filter-apps.test.ts
Normal file
65
apps/apps-cli/src/lib/filter-apps.test.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { AppDetailsFragment } from "../saleor-api/generated/graphql";
|
||||||
|
import { filterApps } from "./filter-apps";
|
||||||
|
|
||||||
|
const mockedApp1: AppDetailsFragment = {
|
||||||
|
id: "1",
|
||||||
|
name: "app1",
|
||||||
|
manifestUrl: "https://app1.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedApp1Duplicate: AppDetailsFragment = {
|
||||||
|
id: "2",
|
||||||
|
name: "app1",
|
||||||
|
manifestUrl: "https://app1.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedApp2: AppDetailsFragment = {
|
||||||
|
id: "3",
|
||||||
|
name: "app2",
|
||||||
|
manifestUrl: "https://app2.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockedAppList = [mockedApp1, mockedApp1Duplicate, mockedApp2];
|
||||||
|
|
||||||
|
describe("filterApps", function () {
|
||||||
|
it("Return the same apps, when no filters applied", async () => {
|
||||||
|
expect(
|
||||||
|
filterApps({
|
||||||
|
apps: mockedAppList,
|
||||||
|
filter: {},
|
||||||
|
})
|
||||||
|
).toStrictEqual(mockedAppList);
|
||||||
|
});
|
||||||
|
it("Return all apps with the same name, when filter name is applied", async () => {
|
||||||
|
expect(
|
||||||
|
filterApps({
|
||||||
|
apps: mockedAppList,
|
||||||
|
filter: {
|
||||||
|
name: mockedApp1.name!,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toStrictEqual([mockedApp1, mockedApp1Duplicate]);
|
||||||
|
});
|
||||||
|
it("Return all apps with the same manifest, when filter manifest is applied", async () => {
|
||||||
|
expect(
|
||||||
|
filterApps({
|
||||||
|
apps: mockedAppList,
|
||||||
|
filter: {
|
||||||
|
manifestUrl: mockedApp1.manifestUrl!,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toStrictEqual([mockedApp1, mockedApp1Duplicate]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Return app with given id, when filter id is applied", async () => {
|
||||||
|
expect(
|
||||||
|
filterApps({
|
||||||
|
apps: mockedAppList,
|
||||||
|
filter: {
|
||||||
|
id: mockedApp1.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toStrictEqual([mockedApp1]);
|
||||||
|
});
|
||||||
|
});
|
28
apps/apps-cli/src/lib/filter-apps.ts
Normal file
28
apps/apps-cli/src/lib/filter-apps.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { AppDetailsFragment } from "../saleor-api/generated/graphql";
|
||||||
|
|
||||||
|
interface FilterAppsArgs {
|
||||||
|
apps: AppDetailsFragment[];
|
||||||
|
filter: {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
manifestUrl?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterApps = ({ apps, filter: { id, manifestUrl, name } }: FilterAppsArgs) => {
|
||||||
|
return apps.filter((app) => {
|
||||||
|
if (id && app.id !== id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name && app.name !== name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestUrl && app.manifestUrl !== manifestUrl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
94
apps/apps-cli/src/lib/install-and-wait-for-result.ts
Normal file
94
apps/apps-cli/src/lib/install-and-wait-for-result.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import ora from "ora";
|
||||||
|
import { fetchAppManifest } from "./fetch-app-manifest";
|
||||||
|
import { filterApps } from "./filter-apps";
|
||||||
|
import { getAppInstallationsListQuery } from "../saleor-api/operations/get-app-installations-list-query";
|
||||||
|
import { getAppsListQuery } from "../saleor-api/operations/get-apps-list-query";
|
||||||
|
import { installAppMutation } from "../saleor-api/operations/install-app-mutation";
|
||||||
|
|
||||||
|
interface InstallAndWaitForResultArgs {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
appManifestUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delay(timeMs: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, timeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to install app from the manifest, wait for the operation to complete
|
||||||
|
* and return app installation result.
|
||||||
|
* If will throw error if any of the steps fails.
|
||||||
|
*/
|
||||||
|
export const installAndWaitForResult = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
appManifestUrl,
|
||||||
|
}: InstallAndWaitForResultArgs) => {
|
||||||
|
const manifestSpinner = ora("Fetching app manifest").start();
|
||||||
|
|
||||||
|
const manifestData = await fetchAppManifest(appManifestUrl);
|
||||||
|
|
||||||
|
manifestSpinner.succeed();
|
||||||
|
|
||||||
|
const installSpinner = ora("Installing the app").start();
|
||||||
|
|
||||||
|
const appInstallationJob = await installAppMutation({
|
||||||
|
manifestUrl: appManifestUrl,
|
||||||
|
saleorApiUrl: saleorApiUrl,
|
||||||
|
token,
|
||||||
|
appName: manifestData.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
installSpinner.text = `Installing the app (job id: ${appInstallationJob.id})`;
|
||||||
|
|
||||||
|
// Lets give the API a bit of time to process installation
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
// App installation is on progress, now we have to monitor if it resolved. Wait max 20s for the result
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const currentAppInstallations = await getAppInstallationsListQuery({
|
||||||
|
saleorApiUrl: saleorApiUrl,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
const appInstallation = currentAppInstallations.find((x) => x.id === appInstallationJob.id);
|
||||||
|
|
||||||
|
if (!appInstallation) {
|
||||||
|
// Job has been processed! If not on the list, it means it was successful
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appInstallation.status === "FAILED") {
|
||||||
|
installSpinner.fail("Installation failed");
|
||||||
|
throw new Error("App installation failed: " + appInstallation.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a bit and check again
|
||||||
|
await delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
installSpinner.text = "Confirming the app installed";
|
||||||
|
|
||||||
|
// App should be installed by now, fetch its details
|
||||||
|
const currentAppInstallations = await getAppsListQuery({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
const installedApp = filterApps({
|
||||||
|
apps: currentAppInstallations,
|
||||||
|
filter: {
|
||||||
|
manifestUrl: appManifestUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!installedApp.length) {
|
||||||
|
// Investigate if this can happen - app not in the list of installed apps nor in the list of installations
|
||||||
|
throw new Error("App not found on the list of installed apps");
|
||||||
|
}
|
||||||
|
|
||||||
|
installSpinner.succeed("App installed!");
|
||||||
|
|
||||||
|
return installedApp[0];
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const getAccessTokenMutationDocument = graphql(/* GraphQL */ `
|
||||||
|
mutation GetAccessToken($email: String!, $password: String!) {
|
||||||
|
tokenCreate(email: $email, password: $password) {
|
||||||
|
token
|
||||||
|
refreshToken
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const getAccessTokenMutation = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
password: string;
|
||||||
|
email: string;
|
||||||
|
}) => {
|
||||||
|
const { tokenCreate } = await request(saleorApiUrl, getAccessTokenMutationDocument, {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tokenCreate?.errors.length) {
|
||||||
|
console.log("mutation failed", tokenCreate?.errors);
|
||||||
|
throw new Error(`Get access token mutation failed - API returned errors`);
|
||||||
|
}
|
||||||
|
const token = tokenCreate?.token;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(`Get access token mutation failed - no token in the response`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const getAppInstallationsQueryDocument = graphql(/* GraphQL */ `
|
||||||
|
query GetAppInstallations {
|
||||||
|
appsInstallations {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const getAppInstallationsListQuery = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
}) => {
|
||||||
|
const { appsInstallations } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
getAppInstallationsQueryDocument,
|
||||||
|
{},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
return appsInstallations;
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const getAppMetadataQueryDocument = graphql(/* GraphQL */ `
|
||||||
|
query GetAppMetadata {
|
||||||
|
app(id: "QXBwOjE=") {
|
||||||
|
metadata {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const getAppMetadataQuery = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
}) => {
|
||||||
|
const { app } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
getAppMetadataQueryDocument,
|
||||||
|
{},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
return app?.metadata ?? [];
|
||||||
|
};
|
|
@ -0,0 +1,67 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const getAppWebhooksQueryDocument = graphql(/* GraphQL */ `
|
||||||
|
query GetAppWebhooks($id: ID!) {
|
||||||
|
app(id: $id) {
|
||||||
|
webhooks {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isActive
|
||||||
|
syncEvents {
|
||||||
|
name
|
||||||
|
eventType
|
||||||
|
}
|
||||||
|
asyncEvents {
|
||||||
|
name
|
||||||
|
eventType
|
||||||
|
}
|
||||||
|
targetUrl
|
||||||
|
eventDeliveries(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
status
|
||||||
|
eventType
|
||||||
|
attempts(first: 10) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
taskId
|
||||||
|
duration
|
||||||
|
response
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const getAppWebhooksQuery = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
appId,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
appId: string;
|
||||||
|
}) => {
|
||||||
|
const { app } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
getAppWebhooksQueryDocument,
|
||||||
|
{
|
||||||
|
id: appId,
|
||||||
|
},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
return app?.webhooks ?? [];
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
export const AppDetailsFragment = graphql(/* GraphQL */ `
|
||||||
|
fragment AppDetails on App {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isActive
|
||||||
|
type
|
||||||
|
created
|
||||||
|
manifestUrl
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const getAppsQueryDocument = graphql(/* GraphQL */ `
|
||||||
|
query GetApps {
|
||||||
|
apps(
|
||||||
|
first: 100
|
||||||
|
filter: { type: THIRDPARTY }
|
||||||
|
sortBy: { field: CREATION_DATE, direction: DESC }
|
||||||
|
) {
|
||||||
|
totalCount
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...AppDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const getAppsListQuery = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
}) => {
|
||||||
|
const { apps } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
getAppsQueryDocument,
|
||||||
|
{},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
return apps?.edges.map(({ node }) => node) ?? [];
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const installAppMutationDocument = graphql(/* GraphQL */ `
|
||||||
|
mutation InstallApp($input: AppInstallInput!) {
|
||||||
|
appInstall(input: $input) {
|
||||||
|
appInstallation {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
appName
|
||||||
|
}
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const installAppMutation = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
appName,
|
||||||
|
manifestUrl,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
manifestUrl: string;
|
||||||
|
appName: string;
|
||||||
|
}) => {
|
||||||
|
const { appInstall } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
installAppMutationDocument,
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
manifestUrl,
|
||||||
|
activateAfterInstallation: true,
|
||||||
|
appName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (appInstall?.errors.length) {
|
||||||
|
console.log("Sth went wrong", appInstall.errors);
|
||||||
|
throw new Error(`Install app ${appName} mutation failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!appInstall?.appInstallation) {
|
||||||
|
console.log("App installation not returned");
|
||||||
|
throw new Error(
|
||||||
|
`Install app ${appName} mutation failed - no app installation data in the response`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return appInstall?.appInstallation;
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
|
||||||
|
const removeWebhookMutationDocument = graphql(/* GraphQL */ `
|
||||||
|
mutation RemoveWebhook($webhookId: ID!) {
|
||||||
|
webhookDelete(id: $webhookId) {
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const removeWebhookMutation = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
webhookId,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
webhookId: string;
|
||||||
|
}) => {
|
||||||
|
const { webhookDelete } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
removeWebhookMutationDocument,
|
||||||
|
{
|
||||||
|
webhookId,
|
||||||
|
},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (webhookDelete?.errors.length) {
|
||||||
|
console.log("Sth went wrong", webhookDelete.errors);
|
||||||
|
throw new Error(`Remove webhook mutation failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import request from "graphql-request";
|
||||||
|
|
||||||
|
import { graphql } from "../generated/gql";
|
||||||
|
import { AppErrorCode } from "../generated/graphql";
|
||||||
|
|
||||||
|
const uninstallAppMutationDocument = graphql(/* GraphQL */ `
|
||||||
|
mutation UninstallApp($id: ID!) {
|
||||||
|
appDelete(id: $id) {
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const uninstallAppMutation = async ({
|
||||||
|
saleorApiUrl,
|
||||||
|
token,
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
saleorApiUrl: string;
|
||||||
|
token: string;
|
||||||
|
id: string;
|
||||||
|
}) => {
|
||||||
|
const { appDelete } = await request(
|
||||||
|
saleorApiUrl,
|
||||||
|
uninstallAppMutationDocument,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
{ "Authorization-Bearer": token }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (appDelete?.errors.length) {
|
||||||
|
const error = appDelete.errors[0];
|
||||||
|
|
||||||
|
if (error.code === AppErrorCode.NotFound) {
|
||||||
|
throw new Error(`Uninstall app ${id} mutation failed - no installed app with this ID`);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Uninstall app ${id} mutation failed. API responded with error: ${error.code} - ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
97
apps/apps-cli/tsconfig.json
Normal file
97
apps/apps-cli/tsconfig.json
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
|
"lib": [
|
||||||
|
"es2021"
|
||||||
|
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
/* Modules */
|
||||||
|
"module": "ES2022" /* Specify what module code is generated. */,
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
"resolveJsonModule": true /* Enable importing .json files */,
|
||||||
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./build" /* Specify an output folder for all emitted files. */,
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||||
|
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
17
apps/apps-cli/turbo.json
Normal file
17
apps/apps-cli/turbo.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"//"
|
||||||
|
],
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"env": [
|
||||||
|
"APP_DEBUG",
|
||||||
|
"NODE_ENV",
|
||||||
|
"INSTANCE_URL",
|
||||||
|
"USER_EMAIL",
|
||||||
|
"USER_PASSWORD"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@datocms/cma-client-node": "^1.2.4",
|
"@datocms/cma-client-node": "^1.2.4",
|
||||||
"@hookform/resolvers": "^2.9.10",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@material-ui/core": "^4.12.4",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.3",
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
|
@ -30,13 +30,13 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.39.1",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"@mailchimp/mailchimp_marketing": "^3.0.80",
|
"@mailchimp/mailchimp_marketing": "^3.0.80",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@tanstack/react-query": "^4.28.0",
|
"@tanstack/react-query": "^4.28.0",
|
||||||
"@trpc/client": "^10.18.0",
|
"@trpc/client": "^10.18.0",
|
||||||
|
@ -33,13 +33,13 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.0",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.31.3",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.31.3",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -12,12 +12,12 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sendgrid/client": "^7.7.0",
|
"@sendgrid/client": "^7.7.0",
|
||||||
"@sendgrid/mail": "^7.7.0",
|
"@sendgrid/mail": "^7.7.0",
|
||||||
|
@ -43,14 +43,14 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.31.3",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -81,7 +81,7 @@ export const UniversalChannelsSection = ({
|
||||||
<Switch
|
<Switch
|
||||||
defaultValue={channelConfiguration.mode}
|
defaultValue={channelConfiguration.mode}
|
||||||
__maxWidth="max-content"
|
__maxWidth="max-content"
|
||||||
onValueChange={onChange}
|
onValueChange={(e) => onChange(e as "exclude" | "restrict")}
|
||||||
>
|
>
|
||||||
<Switch.Item id="1" value="restrict">
|
<Switch.Item id="1" value="restrict">
|
||||||
<TableEditIcon size="medium" />
|
<TableEditIcon size="medium" />
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@tanstack/react-query": "^4.24.4",
|
"@tanstack/react-query": "^4.24.4",
|
||||||
"@trpc/client": "^10.10.0",
|
"@trpc/client": "^10.10.0",
|
||||||
|
@ -34,11 +34,11 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.41.0",
|
"react-hook-form": "^7.45.0",
|
||||||
"tiny-invariant": "^1.3.1",
|
"tiny-invariant": "^1.3.1",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.45.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3"
|
"vitest": "0.31.3"
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.332.0",
|
"@aws-sdk/client-s3": "^3.332.0",
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@tanstack/react-query": "^4.24.2",
|
"@tanstack/react-query": "^4.24.2",
|
||||||
|
@ -37,14 +37,14 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.31.3",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@types/debug": "^4.1.7",
|
"@types/debug": "^4.1.7",
|
||||||
|
@ -32,10 +32,10 @@
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^2.9.10",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "^0.8.0-pre.84",
|
"@saleor/macaw-ui": "^0.8.0-pre.98",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@tanstack/react-query": "^4.19.1",
|
"@tanstack/react-query": "^4.19.1",
|
||||||
|
@ -37,13 +37,13 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.45.0",
|
||||||
"taxjar": "^4.0.1",
|
"taxjar": "^4.0.1",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.31.3",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.8",
|
"@babel/core": "^7.21.8",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@storybook/addon-actions": "^7.0.12",
|
"@storybook/addon-actions": "^7.0.12",
|
||||||
"@storybook/addon-essentials": "^7.0.12",
|
"@storybook/addon-essentials": "^7.0.12",
|
||||||
"@storybook/addon-interactions": "^7.0.12",
|
"@storybook/addon-interactions": "^7.0.12",
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.45.0",
|
||||||
"storybook": "^7.0.12",
|
"storybook": "^7.0.12",
|
||||||
"typescript": "5.1.3",
|
"typescript": "5.1.3",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Combobox as $Combobox, type ComboboxProps as $ComboboxProps } from "@saleor/macaw-ui/next";
|
import { Combobox as $Combobox, type ComboboxProps as $ComboboxProps } from "@saleor/macaw-ui/next";
|
||||||
import { Control, Controller, FieldPath, FieldValues } from "react-hook-form";
|
import { Control, Controller, FieldPath, FieldValues, Path, PathValue } from "react-hook-form";
|
||||||
|
|
||||||
export type ComboboxProps<T extends FieldValues = FieldValues> = Omit<$ComboboxProps, "name"> & {
|
export type ComboboxProps<T extends FieldValues = FieldValues> = Omit<$ComboboxProps, "name"> & {
|
||||||
name: FieldPath<T>;
|
name: FieldPath<T>;
|
||||||
|
@ -18,10 +18,12 @@ export function Combobox<TFieldValues extends FieldValues = FieldValues>({
|
||||||
<Controller
|
<Controller
|
||||||
name={name}
|
name={name}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, ...field }, fieldState: { error } }) => (
|
render={({ field: { value, onChange, ...field }, fieldState: { error } }) => (
|
||||||
<$Combobox
|
<$Combobox
|
||||||
{...rest}
|
{...rest}
|
||||||
{...field}
|
{...field}
|
||||||
|
// TODO: write tests to make sure the cast is safe
|
||||||
|
onChange={(e) => onChange(e as PathValue<TFieldValues, Path<TFieldValues>>)}
|
||||||
options={options}
|
options={options}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
Multiselect as $Multiselect,
|
Multiselect as $Multiselect,
|
||||||
type MultiselectProps as $MultiselectProps,
|
type MultiselectProps as $MultiselectProps,
|
||||||
} from "@saleor/macaw-ui/next";
|
} from "@saleor/macaw-ui/next";
|
||||||
import { Control, Controller, FieldPath, FieldValues } from "react-hook-form";
|
import { Control, Controller, FieldPath, FieldValues, Path, PathValue } from "react-hook-form";
|
||||||
|
|
||||||
export type MultiselectProps<T extends FieldValues = FieldValues> = Omit<
|
export type MultiselectProps<T extends FieldValues = FieldValues> = Omit<
|
||||||
$MultiselectProps,
|
$MultiselectProps,
|
||||||
|
@ -24,10 +24,11 @@ export function Multiselect<TFieldValues extends FieldValues = FieldValues>({
|
||||||
<Controller
|
<Controller
|
||||||
name={name}
|
name={name}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, ...field }, fieldState: { error } }) => (
|
render={({ field: { value, onChange, ...field }, fieldState: { error } }) => (
|
||||||
<$Multiselect
|
<$Multiselect
|
||||||
{...rest}
|
{...rest}
|
||||||
{...field}
|
{...field}
|
||||||
|
onChange={(e) => onChange(e as PathValue<TFieldValues, Path<TFieldValues>>)}
|
||||||
options={options}
|
options={options}
|
||||||
value={value || []}
|
value={value || []}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Select as $Select, type SelectProps as $SelectProps } from "@saleor/macaw-ui/next";
|
import { Select as $Select, type SelectProps as $SelectProps } from "@saleor/macaw-ui/next";
|
||||||
import { Control, Controller, FieldPath, FieldValues } from "react-hook-form";
|
import { Control, Controller, FieldPath, FieldValues, Path, PathValue } from "react-hook-form";
|
||||||
|
|
||||||
export type SelectProps<T extends FieldValues = FieldValues> = Omit<$SelectProps, "name"> & {
|
export type SelectProps<T extends FieldValues = FieldValues> = Omit<$SelectProps, "name"> & {
|
||||||
name: FieldPath<T>;
|
name: FieldPath<T>;
|
||||||
|
@ -18,10 +18,11 @@ export function Select<TFieldValues extends FieldValues = FieldValues>({
|
||||||
<Controller
|
<Controller
|
||||||
name={name}
|
name={name}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, ...field }, fieldState: { error } }) => (
|
render={({ field: { value, onChange, ...field }, fieldState: { error } }) => (
|
||||||
<$Select
|
<$Select
|
||||||
{...rest}
|
{...rest}
|
||||||
{...field}
|
{...field}
|
||||||
|
onChange={(e) => onChange(e as PathValue<TFieldValues, Path<TFieldValues>>)}
|
||||||
options={options}
|
options={options}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Toggle as $Toggle } from "@saleor/macaw-ui/next";
|
import { Toggle as $Toggle } from "@saleor/macaw-ui/next";
|
||||||
import { Control, Controller, FieldPath, FieldValues } from "react-hook-form";
|
import { Control, Controller, FieldPath, FieldValues, Path, PathValue } from "react-hook-form";
|
||||||
|
|
||||||
// ! ToggleProps is not exported from macaw-ui
|
// ! ToggleProps is not exported from macaw-ui
|
||||||
type $ToggleProps = React.ComponentProps<typeof $Toggle>;
|
type $ToggleProps = React.ComponentProps<typeof $Toggle>;
|
||||||
|
@ -19,13 +19,14 @@ export function Toggle<TFieldValues extends FieldValues = FieldValues>({
|
||||||
<Controller
|
<Controller
|
||||||
name={name}
|
name={name}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, ...field } }) => {
|
render={({ field: { value, onChange, ...field } }) => {
|
||||||
return (
|
return (
|
||||||
<$Toggle
|
<$Toggle
|
||||||
{...rest}
|
{...rest}
|
||||||
{...field}
|
{...field}
|
||||||
pressed={value}
|
pressed={value}
|
||||||
onPressedChange={(pressed) => field.onChange(pressed)}
|
// TODO: write tests to make sure the cast is safe
|
||||||
|
onPressedChange={(e) => onChange(e as PathValue<TFieldValues, Path<TFieldValues>>)}
|
||||||
name={name}
|
name={name}
|
||||||
type={type}
|
type={type}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.98",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
|
|
8212
pnpm-lock.yaml
8212
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue