Compare commits
6 commits
main
...
introduce-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a260414894 | ||
![]() |
246789cb0c | ||
![]() |
4cb15a8033 | ||
![]() |
6b9f8ac32b | ||
![]() |
bd757776f2 | ||
![]() |
7efa970079 |
27 changed files with 6658 additions and 2892 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.
|
5
.github/dependabot.yaml
vendored
5
.github/dependabot.yaml
vendored
|
@ -29,6 +29,11 @@ updates:
|
|||
interval: "weekly"
|
||||
|
||||
# Apps
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/apps-cli"
|
||||
open-pull-requests-limit: 0
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/cms"
|
||||
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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
8153
pnpm-lock.yaml
8153
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue