From 089beaaff4e5196214a1d998cb5d528412ad57fd Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Thu, 1 Jun 2023 16:52:36 +0100 Subject: [PATCH 01/47] Add plugin to deduplicate React dependency in extensions --- .../app/src/cli/services/extensions/bundle.ts | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index a994701c5d..9a7a85fc80 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -6,7 +6,8 @@ import {joinPath, relativePath} from '@shopify/cli-kit/node/path' import {useThemebundling} from '@shopify/cli-kit/node/context/local' import {Writable} from 'stream' import {createRequire} from 'module' -import type {StdinOptions, build as esBuild} from 'esbuild' +import type {StdinOptions, build as esBuild, Plugin} from 'esbuild' +import {outputDebug} from '@shopify/cli-kit/node/output.js' const require = createRequire(import.meta.url) @@ -127,7 +128,7 @@ function getESBuildOptions(options: BundleOptions): Parameters }, legalComments: 'none', minify: options.minify, - plugins: getPlugins(), + plugins: getPlugins(options.stdin.resolveDir), target: 'es6', resolveExtensions: ['.tsx', '.ts', '.js', '.json', '.esnext', '.mjs', '.ejs'], } @@ -152,7 +153,7 @@ type ESBuildPlugins = Parameters[0]['plugins'] * It returns the plugins that should be used with ESBuild. * @returns List of plugins. */ -function getPlugins(): ESBuildPlugins { +function getPlugins(resolveDir: string | undefined): ESBuildPlugins { const plugins = [] if (isGraphqlPackageAvailable()) { @@ -160,6 +161,31 @@ function getPlugins(): ESBuildPlugins { plugins.push(graphqlLoader()) } + if (resolveDir) { + let resolvedReactPath: string | undefined + try { + resolvedReactPath = require.resolve('react', {paths: [resolveDir]}) + } catch { + // If weren't able to find React, that's fine. Extension may not be using it! + outputDebug(`Unable to load React in ${resolveDir}, skipping React de-duplication`) + } + + if (resolvedReactPath) { + outputDebug(`Deduplicating React dependency for ${resolveDir}, using ${resolvedReactPath}`) + const DeduplicateReactPlugin: Plugin = { + name: 'dedup-react', + setup({onResolve}) { + onResolve({filter: /^react$/}, (args) => { + return { + path: resolvedReactPath, + } + }) + }, + } + plugins.push(DeduplicateReactPlugin) + } + } + return plugins } From 42cfd5a7aad9de1daa4164a955eba1f6c178dc36 Mon Sep 17 00:00:00 2001 From: Ani Tumanyan <99370588+ani-shopify@users.noreply.github.com> Date: Mon, 12 Jun 2023 11:10:17 -0700 Subject: [PATCH 02/47] Revert "Add warning message to deploy" --- packages/app/src/cli/services/deploy.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/app/src/cli/services/deploy.ts b/packages/app/src/cli/services/deploy.ts index 278a895591..705e69d0f0 100644 --- a/packages/app/src/cli/services/deploy.ts +++ b/packages/app/src/cli/services/deploy.ts @@ -43,14 +43,6 @@ interface TasksContext { } export async function deploy(options: DeployOptions) { - renderInfo({ - headline: ['Stay tuned for changes to', {command: 'deploy'}, {char: '.'}], - body: "When you upgrade to the late July CLI version, you'll be able to release all your extensions straight from the CLI. To track changes, you'll have a record of each app version you deploy.", - reference: [ - {link: {url: 'https://shopify.dev/docs/apps/deployment/deployments-2.0', label: 'Introducing Deployments 2.0'}}, - ], - }) - // eslint-disable-next-line prefer-const let {app, identifiers, partnersApp, token} = await ensureDeployContext(options) const apiKey = identifiers.app From a991f6575c351526034477c804432b08c29f785c Mon Sep 17 00:00:00 2001 From: Ryan Bahan Date: Mon, 12 Jun 2023 13:09:32 -0600 Subject: [PATCH 03/47] scaffold new commands --- packages/app/oclif.manifest.json | 38 ++++++++++++++++++- .../app/src/cli/commands/app/config/link.ts | 18 +++++++++ .../app/src/cli/commands/app/config/push.ts | 18 +++++++++ .../app/src/cli/commands/app/config/use.ts | 18 +++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/cli/commands/app/config/link.ts create mode 100644 packages/app/src/cli/commands/app/config/push.ts create mode 100644 packages/app/src/cli/commands/app/config/use.ts diff --git a/packages/app/oclif.manifest.json b/packages/app/oclif.manifest.json index eb2d166e7c..ecfefc4728 100644 --- a/packages/app/oclif.manifest.json +++ b/packages/app/oclif.manifest.json @@ -56,6 +56,42 @@ }, "args": {} }, + "app:config:link": { + "id": "app:config:link", + "description": "Fetch your app's config from the Partner Dashboard.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "aliases": [], + "flags": { + }, + "args": {} + }, + "app:config:push": { + "id": "app:config:push", + "description": "Push your app's config to the Partner Dashboard.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "aliases": [], + "flags": { + }, + "args": {} + }, + "app:config:use": { + "id": "app:config:use", + "description": "Set a particular configuration to use.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "aliases": [], + "flags": { + }, + "args": {} + }, "app:deploy": { "id": "app:deploy", "description": "Deploy your Shopify app.", @@ -930,4 +966,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/app/src/cli/commands/app/config/link.ts b/packages/app/src/cli/commands/app/config/link.ts new file mode 100644 index 0000000000..b1be9b5cdb --- /dev/null +++ b/packages/app/src/cli/commands/app/config/link.ts @@ -0,0 +1,18 @@ +import {appFlags} from '../../../flags.js' +import Command from '../../../utilities/app-command.js' +import {globalFlags} from '@shopify/cli-kit/node/cli' + +export default class ConfigLink extends Command { + static hidden = true + + static description = "Fetch your app's config from the Partner Dashboard." + + static flags = { + ...globalFlags, + ...appFlags, + } + + public async run(): Promise { + const {flags} = await this.parse(ConfigLink) + } +} diff --git a/packages/app/src/cli/commands/app/config/push.ts b/packages/app/src/cli/commands/app/config/push.ts new file mode 100644 index 0000000000..a73d46a827 --- /dev/null +++ b/packages/app/src/cli/commands/app/config/push.ts @@ -0,0 +1,18 @@ +import {appFlags} from '../../../flags.js' +import Command from '../../../utilities/app-command.js' +import {globalFlags} from '@shopify/cli-kit/node/cli' + +export default class ConfigPush extends Command { + static hidden = true + + static description = "Push your app's config to the Partner Dashboard." + + static flags = { + ...globalFlags, + ...appFlags, + } + + public async run(): Promise { + const {flags} = await this.parse(ConfigPush) + } +} diff --git a/packages/app/src/cli/commands/app/config/use.ts b/packages/app/src/cli/commands/app/config/use.ts new file mode 100644 index 0000000000..180aa72aa1 --- /dev/null +++ b/packages/app/src/cli/commands/app/config/use.ts @@ -0,0 +1,18 @@ +import {appFlags} from '../../../flags.js' +import Command from '../../../utilities/app-command.js' +import {globalFlags} from '@shopify/cli-kit/node/cli' + +export default class ConfigUse extends Command { + static hidden = true + + static description = 'Set a particular configuration to use.' + + static flags = { + ...globalFlags, + ...appFlags, + } + + public async run(): Promise { + const {flags} = await this.parse(ConfigUse) + } +} From 9db384a8f2c105595cafe511e382117f52d0a885 Mon Sep 17 00:00:00 2001 From: Ryan Bahan Date: Mon, 12 Jun 2023 13:31:26 -0600 Subject: [PATCH 04/47] update oclif manifest --- packages/app/oclif.manifest.json | 164 ++++++++++++++++++++++++------- 1 file changed, 127 insertions(+), 37 deletions(-) diff --git a/packages/app/oclif.manifest.json b/packages/app/oclif.manifest.json index ecfefc4728..2b826bf743 100644 --- a/packages/app/oclif.manifest.json +++ b/packages/app/oclif.manifest.json @@ -56,42 +56,6 @@ }, "args": {} }, - "app:config:link": { - "id": "app:config:link", - "description": "Fetch your app's config from the Partner Dashboard.", - "strict": true, - "pluginName": "@shopify/app", - "pluginAlias": "@shopify/app", - "pluginType": "core", - "aliases": [], - "flags": { - }, - "args": {} - }, - "app:config:push": { - "id": "app:config:push", - "description": "Push your app's config to the Partner Dashboard.", - "strict": true, - "pluginName": "@shopify/app", - "pluginAlias": "@shopify/app", - "pluginType": "core", - "aliases": [], - "flags": { - }, - "args": {} - }, - "app:config:use": { - "id": "app:config:use", - "description": "Set a particular configuration to use.", - "strict": true, - "pluginName": "@shopify/app", - "pluginAlias": "@shopify/app", - "pluginType": "core", - "aliases": [], - "flags": { - }, - "args": {} - }, "app:deploy": { "id": "app:deploy", "description": "Deploy your Shopify app.", @@ -480,6 +444,132 @@ }, "args": {} }, + "app:config:link": { + "id": "app:config:link", + "description": "Fetch your app's config from the Partner Dashboard.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "hidden": true, + "aliases": [], + "flags": { + "no-color": { + "name": "no-color", + "type": "boolean", + "description": "Disable color output.", + "hidden": false, + "allowNo": false + }, + "verbose": { + "name": "verbose", + "type": "boolean", + "description": "Increase the verbosity of the logs.", + "hidden": false, + "allowNo": false + }, + "path": { + "name": "path", + "type": "option", + "description": "The path to your app directory.", + "multiple": false, + "default": "." + }, + "environment": { + "name": "environment", + "type": "option", + "char": "e", + "description": "The environment to apply to the current command.", + "hidden": true, + "multiple": false + } + }, + "args": {} + }, + "app:config:push": { + "id": "app:config:push", + "description": "Push your app's config to the Partner Dashboard.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "hidden": true, + "aliases": [], + "flags": { + "no-color": { + "name": "no-color", + "type": "boolean", + "description": "Disable color output.", + "hidden": false, + "allowNo": false + }, + "verbose": { + "name": "verbose", + "type": "boolean", + "description": "Increase the verbosity of the logs.", + "hidden": false, + "allowNo": false + }, + "path": { + "name": "path", + "type": "option", + "description": "The path to your app directory.", + "multiple": false, + "default": "." + }, + "environment": { + "name": "environment", + "type": "option", + "char": "e", + "description": "The environment to apply to the current command.", + "hidden": true, + "multiple": false + } + }, + "args": {} + }, + "app:config:use": { + "id": "app:config:use", + "description": "Set a particular configuration to use.", + "strict": true, + "pluginName": "@shopify/app", + "pluginAlias": "@shopify/app", + "pluginType": "core", + "hidden": true, + "aliases": [], + "flags": { + "no-color": { + "name": "no-color", + "type": "boolean", + "description": "Disable color output.", + "hidden": false, + "allowNo": false + }, + "verbose": { + "name": "verbose", + "type": "boolean", + "description": "Increase the verbosity of the logs.", + "hidden": false, + "allowNo": false + }, + "path": { + "name": "path", + "type": "option", + "description": "The path to your app directory.", + "multiple": false, + "default": "." + }, + "environment": { + "name": "environment", + "type": "option", + "char": "e", + "description": "The environment to apply to the current command.", + "hidden": true, + "multiple": false + } + }, + "args": {} + }, "app:env:pull": { "id": "app:env:pull", "description": "Pull app and extensions environment variables.", @@ -966,4 +1056,4 @@ } } } -} +} \ No newline at end of file From 63d9aa45c718668e19f9744b012cfd3e5c507757 Mon Sep 17 00:00:00 2001 From: Ariel Caplan Date: Mon, 12 Jun 2023 12:38:42 +0300 Subject: [PATCH 05/47] Parallelize admin token request --- .../cli-kit/src/private/node/session/exchange.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/cli-kit/src/private/node/session/exchange.ts b/packages/cli-kit/src/private/node/session/exchange.ts index d06aeacd95..8484981e32 100644 --- a/packages/cli-kit/src/private/node/session/exchange.ts +++ b/packages/cli-kit/src/private/node/session/exchange.ts @@ -50,23 +50,19 @@ export async function exchangeAccessForApplicationTokens( ): Promise<{[x: string]: ApplicationToken}> { const token = identityToken.accessToken - const [partners, storefront, businessPlatform] = await Promise.all([ + const [partners, storefront, businessPlatform, admin] = await Promise.all([ requestAppToken('partners', token, scopes.partners), requestAppToken('storefront-renderer', token, scopes.storefront), requestAppToken('business-platform', token, scopes.businessPlatform), + store ? requestAppToken('admin', token, scopes.admin, store) : {}, ]) - const result = { + return { ...partners, ...storefront, ...businessPlatform, + ...admin, } - - if (store) { - const admin = await requestAppToken('admin', token, scopes.admin, store) - Object.assign(result, admin) - } - return result } /** From 2536bceb7d0a9b6b4ed6344536f2bc90f4287f3a Mon Sep 17 00:00:00 2001 From: Ariel Caplan Date: Mon, 12 Jun 2023 23:23:55 +0300 Subject: [PATCH 06/47] Upgrade nx --- .eslintrc.cjs | 4 +- nx.json | 37 ++-- package.json | 12 +- pnpm-lock.yaml | 554 ++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 476 insertions(+), 131 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b9ccd15336..545f67ec41 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,11 +5,11 @@ module.exports = { project: './tsconfig.json', EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true, }, - plugins: ['@nrwl/nx'], + plugins: ['@nx'], // WARNING: If you want to add new rules/plugins, you need to add them to the eslint-plugin-cli package. extends: ['plugin:@shopify/cli/configs'], rules: { - '@nrwl/nx/enforce-module-boundaries': [ + '@nx/enforce-module-boundaries': [ 'error', { allow: [], diff --git a/nx.json b/nx.json index 496385b0ed..c5a639fc56 100644 --- a/nx.json +++ b/nx.json @@ -1,22 +1,4 @@ { - "extends": "@nrwl/workspace/presets/npm.json", - "npmScope": "@shopify", - "tasksRunnerOptions": { - "default": { - "runner": "@nrwl/workspace/tasks-runners/default", - "options": { - "cacheableOperations": [ - "build" - ], - "runtimeCacheInputs": [ - "node bin/cache-inputs.js || node ../../bin/cache-inputs.js || node ../bin/cache-inputs.js" - ] - } - } - }, - "affected": { - "defaultBase": "main" - }, "targetDefaults": { "clean": { "dependsOn": ["^clean"] @@ -27,13 +9,28 @@ "refresh-manifests": { "dependsOn": ["build"] }, - "lint": { - }, + "lint": {}, "lint:fix": { "dependsOn": ["build"] }, "type-check": { "dependsOn": ["^build"] } + }, + "extends": "@nx/workspace/presets/npm.json", + "npmScope": "@shopify", + "tasksRunnerOptions": { + "default": { + "runner": "@nx/workspace/tasks-runners/default", + "options": { + "cacheableOperations": ["build"], + "runtimeCacheInputs": [ + "node bin/cache-inputs.js || node ../../bin/cache-inputs.js || node ../bin/cache-inputs.js" + ] + } + } + }, + "affected": { + "defaultBase": "main" } } diff --git a/package.json b/package.json index 98e2cba9d9..a5b8e46971 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,7 @@ "@babel/core": "^7.22.1", "@bugsnag/source-maps": "^2.3.1", "@changesets/cli": "2.26.1", - "@nrwl/eslint-plugin-nx": "15.9.2", - "@nrwl/js": "15.9.2", - "@nrwl/tao": "15.9.2", - "@nrwl/workspace": "15.9.2", + "@nrwl/tao": "16.3.2", "@octokit/core": "^4.2.1", "@octokit/rest": "^19.0.11", "@shopify/eslint-plugin-cli": "file:packages/eslint-plugin-cli", @@ -59,7 +56,7 @@ "graphql-tag": "^2.12.6", "liquidjs": "^10.7.1", "node-fetch": "^3.3.1", - "nx": "15.9.2", + "nx": "16.3.2", "oclif": "3.9.0", "octokit-plugin-create-pull-request": "^3.12.2", "pathe": "1.1.0", @@ -73,7 +70,10 @@ "tmp": "^0.2.1", "ts-node": "^10.9.1", "tslib": "^2.5.2", - "typescript": "4.9.5" + "typescript": "5.0.4", + "@nx/workspace": "16.3.2", + "@nx/js": "16.3.2", + "@nx/eslint-plugin": "16.3.2" }, "workspaces": { "packages": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9999037814..3735c1afef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,18 +25,18 @@ importers: '@changesets/cli': specifier: 2.26.1 version: 2.26.1 - '@nrwl/eslint-plugin-nx': - specifier: 15.9.2 - version: 15.9.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@15.9.2)(typescript@4.9.5) - '@nrwl/js': - specifier: 15.9.2 - version: 15.9.2(nx@15.9.2)(typescript@4.9.5) '@nrwl/tao': - specifier: 15.9.2 - version: 15.9.2 - '@nrwl/workspace': - specifier: 15.9.2 - version: 15.9.2 + specifier: 16.3.2 + version: 16.3.2 + '@nx/eslint-plugin': + specifier: 16.3.2 + version: 16.3.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@16.3.2)(typescript@5.0.4) + '@nx/js': + specifier: 16.3.2 + version: 16.3.2(nx@16.3.2)(typescript@5.0.4) + '@nx/workspace': + specifier: 16.3.2 + version: 16.3.2 '@octokit/core': specifier: ^4.2.1 version: 4.2.1 @@ -45,7 +45,7 @@ importers: version: 19.0.11 '@shopify/eslint-plugin-cli': specifier: file:packages/eslint-plugin-cli - version: file:packages/eslint-plugin-cli(eslint@8.41.0)(prettier@2.8.8)(typescript@4.9.5) + version: file:packages/eslint-plugin-cli(eslint@8.41.0)(prettier@2.8.8)(typescript@5.0.4) '@shopify/typescript-configs': specifier: ^5.1.0 version: 5.1.0 @@ -60,7 +60,7 @@ importers: version: 0.2.3 '@typescript-eslint/parser': specifier: ^5.59.8 - version: 5.59.8(eslint@8.41.0)(typescript@4.9.5) + version: 5.59.8(eslint@8.41.0)(typescript@5.0.4) ansi-colors: specifier: ^4.1.3 version: 4.1.3 @@ -104,8 +104,8 @@ importers: specifier: ^3.3.1 version: 3.3.1 nx: - specifier: 15.9.2 - version: 15.9.2 + specifier: 16.3.2 + version: 16.3.2 oclif: specifier: 3.9.0 version: 3.9.0 @@ -141,13 +141,13 @@ importers: version: 0.2.1 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@14.18.36)(typescript@4.9.5) + version: 10.9.1(@types/node@14.18.36)(typescript@5.0.4) tslib: specifier: ^2.5.2 version: 2.5.2 typescript: - specifier: 4.9.5 - version: 4.9.5 + specifier: 5.0.4 + version: 5.0.4 packages/app: dependencies: @@ -1374,7 +1374,7 @@ packages: '@babel/helper-create-class-features-plugin': 7.21.8(@babel/core@7.22.1) '@babel/helper-plugin-utils': 7.21.5 '@babel/helper-replace-supers': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-split-export-declaration': 7.22.5 '@babel/plugin-syntax-decorators': 7.21.0(@babel/core@7.22.1) transitivePeerDependencies: - supports-color @@ -2211,7 +2211,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.21.5(@babel/core@7.22.1) '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.22.1) '@babel/preset-modules': 0.1.5(@babel/core@7.22.1) - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.22.1) babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.22.1) babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.22.1) @@ -3359,53 +3359,119 @@ packages: - supports-color dev: true - /@nrwl/cli@15.9.2: - resolution: {integrity: sha512-QoCmyrcGakHAYTJaNBbOerRQAmqJHMYGCdqtQidV+aP9p1Dy33XxDELfhd+IYmGqngutXuEWChNpWNhPloLnoA==} + /@nrwl/devkit@16.3.2(nx@16.3.2): + resolution: {integrity: sha512-EiDwVIvh6AcClXv22Q7auQh7Iy/ONISEFWzTswy/J6ZmVGCQesbiwg4cGV0MKiScr+awdVzqyNey+wD6IR5Lkw==} + dependencies: + '@nx/devkit': 16.3.2(nx@16.3.2) + transitivePeerDependencies: + - nx + dev: true + + /@nrwl/eslint-plugin-nx@16.3.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@16.3.2)(typescript@5.0.4): + resolution: {integrity: sha512-E+X/L8b/GtbAGnwm+WOdAlXNwNYCTb6uodo5yxRrUkkMoVVMkzI7FLOHKGgYiqXH/xbuKE3yAg21EwP0epOi2Q==} + dependencies: + '@nx/eslint-plugin': 16.3.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@16.3.2)(typescript@5.0.4) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - '@typescript-eslint/parser' + - debug + - eslint + - eslint-config-prettier + - nx + - supports-color + - typescript + - verdaccio + dev: true + + /@nrwl/js@16.3.2(nx@16.3.2)(typescript@5.0.4): + resolution: {integrity: sha512-UMmdA4vXy2/VWNMlpBDruT9XwGmLw/MpUaKoN2KLkai/fYN6MvB3mabc9WQ8qsNvDWshmOJ6TqAHReR25BjugQ==} + dependencies: + '@nx/js': 16.3.2(nx@16.3.2)(typescript@5.0.4) + transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug + - nx + - supports-color + - typescript + - verdaccio + dev: true + + /@nrwl/tao@16.3.2: + resolution: {integrity: sha512-2Kg7dtv6JcQagCZPSq+okceI81NqmXGGgbKWqS7sOfdmp1otxS9uiUFNXw+Pdtnw38mdRviMtSOXScntu4sUKg==} + hasBin: true + dependencies: + nx: 16.3.2 + transitivePeerDependencies: + - '@swc-node/register' + - '@swc/core' + - debug + dev: true + + /@nrwl/workspace@16.3.2: + resolution: {integrity: sha512-ORVzEEJIMOFYEOtOQHLU7N4vT4mYZ/JzKiwHZrHkCaVhgkiGBLoX3tOwVZjafKaa/24cGISv0J7WRtnfRKl2cA==} dependencies: - nx: 15.9.2 + '@nx/workspace': 16.3.2 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' - debug dev: true - /@nrwl/devkit@15.9.2(nx@15.9.2): - resolution: {integrity: sha512-2DvTstVZb91m+d4wqUJMBHQ3elxyabdmFE6/3aXmtOGeDxTyXyDzf/1O6JvBBiL8K6XC3ZYchjtxUHgxl/NJ5A==} + /@nx/devkit@16.3.2(nx@16.3.2): + resolution: {integrity: sha512-1ev3EDm2Sx/ibziZroL1SheqxDR7UgC49tkBgJz1GrQLQnfdhBYroCPSyBSWGPMLHjIuHb3+hyGSV1Bz+BIYOA==} peerDependencies: - nx: '>= 14.1 <= 16' + nx: '>= 15 <= 17' dependencies: + '@nrwl/devkit': 16.3.2(nx@16.3.2) ejs: 3.1.9 ignore: 5.2.4 - nx: 15.9.2 + nx: 16.3.2 semver: 7.3.4 tmp: 0.2.1 tslib: 2.5.2 dev: true - /@nrwl/eslint-plugin-nx@15.9.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@15.9.2)(typescript@4.9.5): - resolution: {integrity: sha512-WeR+/mjzteBz9401mZroyML7sgnxF32FjMBcmVjuG5a5Eji36ChXn8Vtzm3IhfAY3k2sFbANxYLSNQYf5JJyqw==} + /@nx/eslint-plugin@16.3.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@16.3.2)(typescript@5.0.4): + resolution: {integrity: sha512-9KMiDEvsHPlLm9wrG3qUl68veNFLbFglD5XGKmBXA07tHISWo5eqNIML5/Y5cwsRufUcQFe21V+6FxrbVQ24CQ==} peerDependencies: - '@typescript-eslint/parser': ^5.29.0 + '@typescript-eslint/parser': ^5.58.0 eslint-config-prettier: ^8.1.0 peerDependenciesMeta: eslint-config-prettier: optional: true dependencies: - '@nrwl/devkit': 15.9.2(nx@15.9.2) - '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.59.7(eslint@8.41.0)(typescript@4.9.5) + '@nrwl/eslint-plugin-nx': 16.3.2(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(nx@16.3.2)(typescript@5.0.4) + '@nx/devkit': 16.3.2(nx@16.3.2) + '@nx/js': 16.3.2(nx@16.3.2)(typescript@5.0.4) + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/type-utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) chalk: 4.1.2 confusing-browser-globals: 1.0.11 semver: 7.3.4 transitivePeerDependencies: + - '@babel/traverse' + - '@swc-node/register' + - '@swc/core' + - debug - eslint - nx - supports-color - typescript + - verdaccio dev: true - /@nrwl/js@15.9.2(nx@15.9.2)(typescript@4.9.5): - resolution: {integrity: sha512-ioYnV9N02/6kTSGzo/GlX0BCFLe1rhaYVxxza/ciUtI33sej8GTe33jm69Y3t1dwxk7K1aZ1g6LPOYA5L4SYJw==} + /@nx/js@16.3.2(nx@16.3.2)(typescript@5.0.4): + resolution: {integrity: sha512-bumLGMduNm221Sh3/wkEMEkJOC1kTlqmpx6wamDSsPlAFq0ePgoaNJjoYqC9XH7n7wXtgy9bgKhHJPnek8NKow==} + peerDependencies: + verdaccio: ^5.0.4 + peerDependenciesMeta: + verdaccio: + optional: true dependencies: '@babel/core': 7.22.1 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1) @@ -3414,9 +3480,10 @@ packages: '@babel/preset-env': 7.21.5(@babel/core@7.22.1) '@babel/preset-typescript': 7.21.5(@babel/core@7.22.1) '@babel/runtime': 7.21.5 - '@nrwl/devkit': 15.9.2(nx@15.9.2) - '@nrwl/workspace': 15.9.2 - '@phenomnomnominal/tsquery': 4.1.1(typescript@4.9.5) + '@nrwl/js': 16.3.2(nx@16.3.2)(typescript@5.0.4) + '@nx/devkit': 16.3.2(nx@16.3.2) + '@nx/workspace': 16.3.2 + '@phenomnomnominal/tsquery': 5.0.1(typescript@5.0.4) babel-plugin-const-enum: 1.2.0(@babel/core@7.22.1) babel-plugin-macros: 2.8.0 babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.22.1) @@ -3426,8 +3493,8 @@ packages: ignore: 5.2.4 js-tokens: 4.0.0 minimatch: 3.0.5 + semver: 7.3.4 source-map-support: 0.5.19 - tree-kill: 1.2.2 tslib: 2.5.2 transitivePeerDependencies: - '@babel/traverse' @@ -3439,8 +3506,8 @@ packages: - typescript dev: true - /@nrwl/nx-darwin-arm64@15.9.2: - resolution: {integrity: sha512-Yv+OVsQt3C/hmWOC+YhJZQlsyph5w1BHfbp4jyCvV1ZXBbb8NdvwxgDHPWXxKPTc1EXuB7aEX3qzxM3/OWEUJg==} + /@nx/nx-darwin-arm64@16.3.2: + resolution: {integrity: sha512-YfYVNfsJBzBcBnJUU4AcA6A4QMkgnVlETfp4KGL36Otq542mRY1ISGHdox63ocI5AKh5gay5AaGcR4wR9PU9Vg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -3448,8 +3515,8 @@ packages: dev: true optional: true - /@nrwl/nx-darwin-x64@15.9.2: - resolution: {integrity: sha512-qHfdluHlPzV0UHOwj1ZJ+qNEhzfLGiBuy1cOth4BSzDlvMnkuqBWoprfaXoztzYcus2NSILY1/7b3Jw4DAWmMw==} + /@nx/nx-darwin-x64@16.3.2: + resolution: {integrity: sha512-bJtpozz0zSRVRrcQ76GrlT3TWEGTymLYWrVG51bH5KZ46t6/a4EQBI3uL3vubMmOZ0jR4ywybOcPBBhxmBJ68w==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -3457,8 +3524,17 @@ packages: dev: true optional: true - /@nrwl/nx-linux-arm-gnueabihf@15.9.2: - resolution: {integrity: sha512-0GzwbablosnYnnJDCJvAeZv8LlelSrNwUnGhe43saeoZdAew35Ay1E34zBrg/GCGTASuz+knEEYFM+gDD9Mc6A==} + /@nx/nx-freebsd-x64@16.3.2: + resolution: {integrity: sha512-ZvufI0bWqT67nLbBo6ejrIGxypdoedRQTP/tudWbs/4isvxLe1uVku1BfKCTQUsJG367SqNOU1H5kzI/MRr3ow==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@nx/nx-linux-arm-gnueabihf@16.3.2: + resolution: {integrity: sha512-IQL4kxdiZLvifar7+SIum3glRuVsxtE0dL8RvteSDXrxDQnaTUrjILC+VGhalRmk7ngBbGKNrhWOeeL7390CzQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -3466,8 +3542,8 @@ packages: dev: true optional: true - /@nrwl/nx-linux-arm64-gnu@15.9.2: - resolution: {integrity: sha512-3mFIY7iUTPG45hSIRaM2DmraCy8W6hNoArAGRrTgYw40BIJHtLrW+Rt7DLyvVXaYCvrKugWOKtxC+jG7kpIZVA==} + /@nx/nx-linux-arm64-gnu@16.3.2: + resolution: {integrity: sha512-f6AWgPVu3mfUEoOBa0rY2/7QY0Or9eR0KtLFpcPh7RUpxPw2EXzIbjD/0RGipdpspSrgiMKbZpsUjo6mXBFsQA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3475,8 +3551,8 @@ packages: dev: true optional: true - /@nrwl/nx-linux-arm64-musl@15.9.2: - resolution: {integrity: sha512-FNBnXEtockwxZa4I3NqggrJp0YIbNokJvt/clrICP+ijOacdUDkv8mJedavobkFsRsNq9gzCbRbUScKymrOLrg==} + /@nx/nx-linux-arm64-musl@16.3.2: + resolution: {integrity: sha512-AvrWcYz7021E3b5P9/0i26p60XMZfw86Epks51L6AhlflarlOH4AcEChc7APMtb1ELAIbDWx2S6oIDRbQ7rtVA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3484,8 +3560,8 @@ packages: dev: true optional: true - /@nrwl/nx-linux-x64-gnu@15.9.2: - resolution: {integrity: sha512-gHWsP5lbe4FNQCa1Q/VLxIuik+BqAOcSzyPjdUa4gCDcbxPa8xiE57PgXB5E1XUzOWNnDTlXa/Ll07/TIuKuog==} + /@nx/nx-linux-x64-gnu@16.3.2: + resolution: {integrity: sha512-K2pWGAcbCNm6b7UZI9cc8z4Rb540QcuepBXD7akjPjWerzXriT6VCn4i9mVKsCg2mwSfknTJJVJ1PZwJSmTl/Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3493,8 +3569,8 @@ packages: dev: true optional: true - /@nrwl/nx-linux-x64-musl@15.9.2: - resolution: {integrity: sha512-EaFUukCbmoHsYECX2AS4pxXH933yesBFVvBgD38DkoFDxDoJMVt6JqYwm+d5R7S4R2P9U3l++aurljQTRq567Q==} + /@nx/nx-linux-x64-musl@16.3.2: + resolution: {integrity: sha512-sY1QDuQlqyYiRPJZanrtV07tU0DOXiCrWb0pDsGiO0qHuUSmW5Vw17GWEY4z3rt0/5U8fJ+/9WQrneviOmsOKg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3502,8 +3578,8 @@ packages: dev: true optional: true - /@nrwl/nx-win32-arm64-msvc@15.9.2: - resolution: {integrity: sha512-PGAe7QMr51ivx1X3avvs8daNlvv1wGo3OFrobjlu5rSyjC1Y3qHwT9+wdlwzNZ93FIqWOq09s+rE5gfZRfpdAg==} + /@nx/nx-win32-arm64-msvc@16.3.2: + resolution: {integrity: sha512-wBfohT2hjrLKn9WFHvG0MFVk7uYhgYNiptnTLdTouziHgFyZ08vyl7XYBq55BwHPMQ5iswVoEfjn/5ZBfCPscg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -3511,8 +3587,8 @@ packages: dev: true optional: true - /@nrwl/nx-win32-x64-msvc@15.9.2: - resolution: {integrity: sha512-Q8onNzhuAZ0l9DNkm8D4Z1AEIzJr8JiT4L2fVBLYrV/R75C2HS3q7lzvfo6oqMY6mXge1cFPcrTtg3YXBQaSWA==} + /@nx/nx-win32-x64-msvc@16.3.2: + resolution: {integrity: sha512-QC0sWrfQm0/WdvvM//7UAgm+otbak6bznZ0zawTeqmLBh1hLjNeweyzSVKQEtZtlzDMKpzCVuuwkJq+VKBLvmw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3520,21 +3596,11 @@ packages: dev: true optional: true - /@nrwl/tao@15.9.2: - resolution: {integrity: sha512-+LqNC37w9c6q6Ukdpf0z0tt1PQFNi4gwhHpJvkYQiKRETHjyrrlyqTNEPEyA7PI62RuYC6VrpVw2gzI7ufqZEA==} - hasBin: true - dependencies: - nx: 15.9.2 - transitivePeerDependencies: - - '@swc-node/register' - - '@swc/core' - - debug - dev: true - - /@nrwl/workspace@15.9.2: - resolution: {integrity: sha512-4e3p1EtJKvvZfH5ghLT3PtPfdr21WfN1LjctMaFAaqwb7jMos0jQIZlLotDPvc9BD8zzyljniE6BijDSZOWncg==} + /@nx/workspace@16.3.2: + resolution: {integrity: sha512-gFrJEv3+Jn2leu3RKFTakPHY8okI8hjOg8RO4OWA2ZemFXRyh9oIm/xsCsOyqYlGt06eqV2mD3GUun/05z1nhg==} dependencies: - '@nrwl/devkit': 15.9.2(nx@15.9.2) + '@nrwl/workspace': 16.3.2 + '@nx/devkit': 16.3.2(nx@16.3.2) '@parcel/watcher': 2.0.4 chalk: 4.1.2 chokidar: 3.5.3 @@ -3543,13 +3609,12 @@ packages: dotenv: 10.0.0 figures: 3.2.0 flat: 5.0.2 - glob: 7.1.4 ignore: 5.2.4 minimatch: 3.0.5 npm-run-path: 4.0.1 - nx: 15.9.2 + nx: 16.3.2 open: 8.4.2 - rxjs: 6.6.7 + rxjs: 7.8.1 tmp: 0.2.1 tslib: 2.5.2 yargs: 17.7.2 @@ -3898,13 +3963,13 @@ packages: node-gyp-build: 4.6.0 dev: true - /@phenomnomnominal/tsquery@4.1.1(typescript@4.9.5): - resolution: {integrity: sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ==} + /@phenomnomnominal/tsquery@5.0.1(typescript@5.0.4): + resolution: {integrity: sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==} peerDependencies: - typescript: ^3 || ^4 + typescript: ^3 || ^4 || ^5 dependencies: esquery: 1.5.0 - typescript: 4.9.5 + typescript: 5.0.4 dev: true /@pkgjs/parseargs@0.11.0: @@ -3999,6 +4064,47 @@ packages: - prettier - supports-color - typescript + dev: false + + /@shopify/eslint-plugin@42.1.0(@babel/core@7.22.1)(eslint@8.41.0)(prettier@2.8.8)(typescript@5.0.4): + resolution: {integrity: sha512-b45SXfXoE9+BvQjHrhInWlOMhsXrqIzts+setaXecR5WW6NcEKeeSfHvTvLVk231NHnrE5h+MuHp1Ci1pR5nfA==} + peerDependencies: + eslint: ^8.3.0 + dependencies: + '@babel/eslint-parser': 7.21.8(@babel/core@7.22.1)(eslint@8.41.0) + '@babel/eslint-plugin': 7.19.1(@babel/eslint-parser@7.21.8)(eslint@8.41.0) + '@typescript-eslint/eslint-plugin': 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + change-case: 4.1.2 + common-tags: 1.8.2 + doctrine: 2.1.0 + eslint: 8.41.0 + eslint-config-prettier: 8.8.0(eslint@8.41.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.41.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.59.8)(eslint@8.41.0)(typescript@5.0.4) + eslint-plugin-jest-formatting: 3.1.0(eslint@8.41.0) + eslint-plugin-jsx-a11y: 6.7.1(eslint@8.41.0) + eslint-plugin-node: 11.1.0(eslint@8.41.0) + eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.41.0)(prettier@2.8.8) + eslint-plugin-promise: 6.1.1(eslint@8.41.0) + eslint-plugin-react: 7.32.2(eslint@8.41.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.41.0) + eslint-plugin-sort-class-members: 1.18.0(eslint@8.41.0) + jsx-ast-utils: 3.3.3 + pkg-dir: 5.0.0 + pluralize: 8.0.0 + transitivePeerDependencies: + - '@babel/core' + - eslint-import-resolver-node + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - jest + - prettier + - supports-color + - typescript + dev: true /@shopify/function-enhancers@2.0.8: resolution: {integrity: sha512-/nv59+ycOVV2ZKixl6V1d+xJmfMN40qUEmpFgbXhCnNjAE/vz3nJPal70Esp4Li2NR3GzKVJklZk3Y3pG+W1vw==} @@ -4492,6 +4598,34 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.5.1 + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/type-utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/experimental-utils@5.59.7(eslint@8.41.0)(typescript@4.9.5): resolution: {integrity: sha512-jqM0Cjfvta/sBlY1MxdXYv853/dJUC2wmUWnKoG2srwp0njNGQ6Zu/XLWoRFiLvocQbzBbpHkPFwKgC2UqyovA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4503,6 +4637,20 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false + + /@typescript-eslint/experimental-utils@5.59.7(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-jqM0Cjfvta/sBlY1MxdXYv853/dJUC2wmUWnKoG2srwp0njNGQ6Zu/XLWoRFiLvocQbzBbpHkPFwKgC2UqyovA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.59.7(eslint@8.41.0)(typescript@5.0.4) + eslint: 8.41.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true /@typescript-eslint/parser@5.59.8(eslint@8.41.0)(typescript@4.9.5): resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==} @@ -4523,6 +4671,26 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/parser@5.59.8(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.59.7: resolution: {integrity: sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4556,6 +4724,26 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/type-utils@5.59.8(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.41.0 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@5.59.7: resolution: {integrity: sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4583,6 +4771,28 @@ packages: typescript: 4.9.5 transitivePeerDependencies: - supports-color + dev: false + + /@typescript-eslint/typescript-estree@5.59.7(typescript@5.0.4): + resolution: {integrity: sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.7 + '@typescript-eslint/visitor-keys': 5.59.7 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true /@typescript-eslint/typescript-estree@5.59.8(typescript@4.9.5): resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==} @@ -4604,6 +4814,27 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/typescript-estree@5.59.8(typescript@5.0.4): + resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/visitor-keys': 5.59.8 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.1 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@5.59.7(eslint@8.41.0)(typescript@4.9.5): resolution: {integrity: sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4622,6 +4853,27 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false + + /@typescript-eslint/utils@5.59.7(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@types/json-schema': 7.0.11 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.59.7 + '@typescript-eslint/types': 5.59.7 + '@typescript-eslint/typescript-estree': 5.59.7(typescript@5.0.4) + eslint: 8.41.0 + eslint-scope: 5.1.1 + semver: 7.5.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: true /@typescript-eslint/utils@5.59.8(eslint@8.41.0)(typescript@4.9.5): resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==} @@ -4642,6 +4894,26 @@ packages: - supports-color - typescript + /@typescript-eslint/utils@5.59.8(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@types/json-schema': 7.0.11 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.59.8 + '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) + eslint: 8.41.0 + eslint-scope: 5.1.1 + semver: 7.5.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.59.7: resolution: {integrity: sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6860,6 +7132,28 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false + + /eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.59.8)(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/experimental-utils': 5.59.7(eslint@8.41.0)(typescript@5.0.4) + eslint: 8.41.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true /eslint-plugin-jsdoc@39.3.6(eslint@8.41.0): resolution: {integrity: sha512-R6dZ4t83qPdMhIOGr7g2QII2pwCjYyKP+z0tPOfO1bbAbQyKC20Y2Rd6z1te86Lq3T7uM8bNo+VD9YFpE8HU/g==} @@ -7017,6 +7311,20 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: false + + /eslint-plugin-vitest@0.2.5(eslint@8.41.0)(typescript@5.0.4): + resolution: {integrity: sha512-b15PJg7pzGd2X/RK6yRIHhA0sPJnjwNHytMFpqL8Xhil4C7sMz3bVhsTSisBsZR7AvYcJZotVgwQoVGgk8Sm3Q==} + engines: {node: 14.x || >= 16} + peerDependencies: + eslint: '>=8.0.0' + dependencies: + '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.0.4) + eslint: 8.41.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true /eslint-rule-composer@0.3.0: resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} @@ -9933,8 +10241,8 @@ packages: resolution: {integrity: sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==} dev: true - /nx@15.9.2: - resolution: {integrity: sha512-wtcs+wsuplSckvgk+bV+/XuGlo+sVWzSG0RpgWBjQYeqA3QsVFEAPVY66Z5cSoukDbTV77ddcAjEw+Rz8oOR1A==} + /nx@16.3.2: + resolution: {integrity: sha512-fOzCVL7qoCJAcYTJwvJ9j+PSaL791ro4AICWuLxaphZsp2jcLoav4Ev7ONPks2Wlkt8FS9bee3nqQ3w1ya36Og==} hasBin: true requiresBuild: true peerDependencies: @@ -9946,8 +10254,7 @@ packages: '@swc/core': optional: true dependencies: - '@nrwl/cli': 15.9.2 - '@nrwl/tao': 15.9.2 + '@nrwl/tao': 16.3.2 '@parcel/watcher': 2.0.4 '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.44 @@ -9982,15 +10289,16 @@ packages: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nrwl/nx-darwin-arm64': 15.9.2 - '@nrwl/nx-darwin-x64': 15.9.2 - '@nrwl/nx-linux-arm-gnueabihf': 15.9.2 - '@nrwl/nx-linux-arm64-gnu': 15.9.2 - '@nrwl/nx-linux-arm64-musl': 15.9.2 - '@nrwl/nx-linux-x64-gnu': 15.9.2 - '@nrwl/nx-linux-x64-musl': 15.9.2 - '@nrwl/nx-win32-arm64-msvc': 15.9.2 - '@nrwl/nx-win32-x64-msvc': 15.9.2 + '@nx/nx-darwin-arm64': 16.3.2 + '@nx/nx-darwin-x64': 16.3.2 + '@nx/nx-freebsd-x64': 16.3.2 + '@nx/nx-linux-arm-gnueabihf': 16.3.2 + '@nx/nx-linux-arm64-gnu': 16.3.2 + '@nx/nx-linux-arm64-musl': 16.3.2 + '@nx/nx-linux-x64-gnu': 16.3.2 + '@nx/nx-linux-x64-musl': 16.3.2 + '@nx/nx-win32-arm64-msvc': 16.3.2 + '@nx/nx-win32-x64-msvc': 16.3.2 transitivePeerDependencies: - debug dev: true @@ -11273,13 +11581,6 @@ packages: dependencies: queue-microtask: 1.2.3 - /rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} - dependencies: - tslib: 1.14.1 - dev: true - /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: @@ -12291,6 +12592,37 @@ packages: yn: 3.1.1 dev: true + /ts-node@10.9.1(@types/node@14.18.36)(typescript@5.0.4): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 14.18.36 + acorn: 8.8.2 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.0.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -12323,6 +12655,16 @@ packages: tslib: 1.14.1 typescript: 4.9.5 + /tsutils@3.21.0(typescript@5.0.4): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.0.4 + dev: true + /tty-table@4.2.1: resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} engines: {node: '>=8.0.0'} @@ -12457,6 +12799,12 @@ packages: engines: {node: '>=4.2.0'} hasBin: true + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + dev: true + /typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -13422,7 +13770,7 @@ packages: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false - file:packages/eslint-plugin-cli(eslint@8.41.0)(prettier@2.8.8)(typescript@4.9.5): + file:packages/eslint-plugin-cli(eslint@8.41.0)(prettier@2.8.8)(typescript@5.0.4): resolution: {directory: packages/eslint-plugin-cli, type: directory} id: file:packages/eslint-plugin-cli name: '@shopify/eslint-plugin-cli' @@ -13431,9 +13779,9 @@ packages: eslint: ^8.41.0 dependencies: '@babel/core': 7.22.1 - '@shopify/eslint-plugin': 42.1.0(@babel/core@7.22.1)(eslint@8.41.0)(prettier@2.8.8)(typescript@4.9.5) - '@typescript-eslint/eslint-plugin': 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@4.9.5) - '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@4.9.5) + '@shopify/eslint-plugin': 42.1.0(@babel/core@7.22.1)(eslint@8.41.0)(prettier@2.8.8)(typescript@5.0.4) + '@typescript-eslint/eslint-plugin': 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.0.4) eslint: 8.41.0 eslint-config-prettier: 8.8.0(eslint@8.41.0) eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0) @@ -13445,7 +13793,7 @@ packages: eslint-plugin-react-hooks: 4.6.0(eslint@8.41.0) eslint-plugin-tsdoc: 0.2.17 eslint-plugin-unused-imports: 2.0.0(@typescript-eslint/eslint-plugin@5.59.8)(eslint@8.41.0) - eslint-plugin-vitest: 0.2.5(eslint@8.41.0)(typescript@4.9.5) + eslint-plugin-vitest: 0.2.5(eslint@8.41.0)(typescript@5.0.4) execa: 5.1.1 transitivePeerDependencies: - eslint-import-resolver-node From cec7629c84968a65d3f9179a0d0904f639956321 Mon Sep 17 00:00:00 2001 From: Ariel Caplan Date: Mon, 12 Jun 2023 12:55:41 +0300 Subject: [PATCH 07/47] Remove --all from "affected" scripts nx no longer supports affected --all, so it's either affected or run-many --all. Given these are all specifically "affected" scripts, we opt to remove --all. --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a5b8e46971..e52d164cdb 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,18 @@ "clean": "nx run-many --target=clean --all --skip-nx-cache && nx reset", "docs:generate": "nx run-many --target=docs:generate --all --skip-nx-cache", "lint": "nx run-many --target=lint --all --skip-nx-cache", - "lint:affected": "nx affected --target=lint --all", + "lint:affected": "nx affected --target=lint", "lint:fix": "nx run-many --target=lint:fix --all --skip-nx-cache", - "lint:fix:affected": "nx affected --target=lint:fix --all", + "lint:fix:affected": "nx affected --target=lint:fix", "test": "nx run-many --target=test --all --skip-nx-cache", "test:unit": "nx run-many --target=test --all --skip-nx-cache --exclude=features", "test:features": "pnpm nx run features:test", - "test:affected": "nx affected --target=test --all", + "test:affected": "nx affected --target=test", "test:regenerate-snapshots": "nx build cli && packages/features/snapshots/regenerate.sh", "type-check": "nx run-many --target=type-check --all --skip-nx-cache", - "type-check:affected": "nx affected --target=type-check --all", + "type-check:affected": "nx affected --target=type-check", "build": "nx run-many --target=build --all --skip-nx-cache", - "build:affected": "nx affected --target=build --all", + "build:affected": "nx affected --target=build", "refresh-templates": "nx run-many --target=refresh-templates --all --skip-nx-cache", "refresh-manifests": "nx run-many --target=refresh-manifests --all --skip-nx-cache && bin/prettify-manifests.js", "changeset-manifests": "changeset version && pnpm install --no-frozen-lockfile && pnpm refresh-manifests && bin/update-cli-kit-version.js && pnpm docs:generate", From c7a6eac9b7f84e4b022027020f1c4d695f99a557 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 9 Jun 2023 13:12:53 +0200 Subject: [PATCH 08/47] Fix token refresh when no password is provided --- packages/theme/src/cli/commands/theme/dev.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 85291a3918..2bf0ee2264 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -113,6 +113,7 @@ export default class Dev extends ThemeCommand { let {flags} = await this.parse(Dev) const store = ensureThemeStore(flags) const adminSession = await refreshTokens(store, flags.password) + const adminToken = flags.password ? adminSession.token : undefined if (flags.theme) { const filter = {filter: {theme: flags.theme}} @@ -135,12 +136,15 @@ export default class Dev extends ThemeCommand { renderLinks(store, flags.theme!, flags.host, flags.port) - setInterval(() => { - outputDebug('Refreshing theme session tokens...') - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refreshTokens(store, flags.password) - }, this.ThemeRefreshTimeoutInMs) - await execCLI2(command, {store, adminToken: adminSession.token}) + if (!flags.password) { + setInterval(() => { + outputDebug('Refreshing theme session tokens...') + // eslint-disable-next-line @typescript-eslint/no-floating-promises + refreshTokens(store, flags.password) + }, this.ThemeRefreshTimeoutInMs) + } + + await execCLI2(command, {store, adminToken}) } } From 2cd8262d73abd80b9b6749e803ae770a8cf7e58c Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 9 Jun 2023 17:18:59 +0200 Subject: [PATCH 09/47] Extract logic from the command to the service --- packages/theme/src/cli/commands/theme/dev.ts | 50 ++++++-------------- packages/theme/src/cli/services/dev.test.ts | 26 +++++++++- packages/theme/src/cli/services/dev.ts | 46 ++++++++++++++++++ 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 2bf0ee2264..ca2573ead6 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -1,19 +1,11 @@ import {themeFlags} from '../../flags.js' import {ensureThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' +import {dev, refreshTokens, showDeprecationWarnings} from '../../services/dev.js' import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' -import { - showDeprecationWarnings, - currentDirectoryConfirmed, - renderLinks, - validThemeDirectory, -} from '../../services/dev.js' import {findOrSelectTheme} from '../../utilities/theme-selector.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' -import {execCLI2} from '@shopify/cli-kit/node/ruby' -import {ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' -import {outputDebug} from '@shopify/cli-kit/node/output' export default class Dev extends ThemeCommand { static description = @@ -101,19 +93,17 @@ export default class Dev extends ThemeCommand { 'notify', ] - // Tokens are valid for 120 min, better to be safe and refresh every 110 min - ThemeRefreshTimeoutInMs = 110 * 60 * 1000 - /** * Executes the theme serve command. * Every 110 minutes, it will refresh the session token. */ async run(): Promise { showDeprecationWarnings(this.argv) + let {flags} = await this.parse(Dev) const store = ensureThemeStore(flags) + const adminSession = await refreshTokens(store, flags.password) - const adminToken = flags.password ? adminSession.token : undefined if (flags.theme) { const filter = {filter: {theme: flags.theme}} @@ -128,29 +118,17 @@ export default class Dev extends ThemeCommand { } const flagsToPass = this.passThroughFlags(flags, {allowedFlags: Dev.cli2Flags}) - const command = ['theme', 'serve', flags.path, ...flagsToPass] - - if (!(await validThemeDirectory(flags.path)) && !(await currentDirectoryConfirmed(flags.force))) { - return - } - renderLinks(store, flags.theme!, flags.host, flags.port) - - if (!flags.password) { - setInterval(() => { - outputDebug('Refreshing theme session tokens...') - // eslint-disable-next-line @typescript-eslint/no-floating-promises - refreshTokens(store, flags.password) - }, this.ThemeRefreshTimeoutInMs) - } - - await execCLI2(command, {store, adminToken}) + await dev({ + adminSession, + directory: flags.path, + store: ensureThemeStore(flags), + password: flags.password, + theme: flags.theme!, + host: flags.host, + port: flags.port, + force: flags.force, + flagsToPass, + }) } } - -async function refreshTokens(store: string, password: string | undefined) { - const adminSession = await ensureAuthenticatedThemes(store, password, [], true) - const storefrontToken = await ensureAuthenticatedStorefront([], password) - await execCLI2(['theme', 'token', '--admin', adminSession.token, '--sfr', storefrontToken]) - return adminSession -} diff --git a/packages/theme/src/cli/services/dev.test.ts b/packages/theme/src/cli/services/dev.test.ts index 4b7d17cdfe..dcf3bd54a2 100644 --- a/packages/theme/src/cli/services/dev.test.ts +++ b/packages/theme/src/cli/services/dev.test.ts @@ -1,8 +1,11 @@ -import {showDeprecationWarnings, REQUIRED_FOLDERS, validThemeDirectory} from './dev.js' -import {describe, expect, test} from 'vitest' +import {showDeprecationWarnings, REQUIRED_FOLDERS, validThemeDirectory, refreshTokens} from './dev.js' +import {describe, expect, test, vi} from 'vitest' import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' import {joinPath} from '@shopify/cli-kit/node/path' import {inTemporaryDirectory, mkdir} from '@shopify/cli-kit/node/fs' +import {execCLI2} from '@shopify/cli-kit/node/ruby' + +vi.mock('@shopify/cli-kit/node/ruby') describe('validThemeDirectory', () => { test('should not consider an empty directory to be a valid theme directory', async () => { @@ -60,3 +63,22 @@ describe('showDeprecationWarnings', () => { expect(outputMock.output()).toMatch(/reserved for environments/) }) }) + +describe('refreshTokens', () => { + test('returns the admin token and store', async () => { + // When + const result = await refreshTokens('my-store', 'my-password') + + // Then + expect(result).toEqual({storeFqdn: 'my-store.myshopify.com', token: 'my-password'}) + }) + + test('refreshes CLI2 cache with theme token command', async () => { + // When + await refreshTokens('my-store', 'my-password') + + // Then + const expectedParams = ['theme', 'token', '--admin', 'my-password', '--sfr', 'my-password'] + expect(execCLI2).toHaveBeenCalledWith(expectedParams) + }) +}) diff --git a/packages/theme/src/cli/services/dev.ts b/packages/theme/src/cli/services/dev.ts index 914cefb729..261cf8462d 100644 --- a/packages/theme/src/cli/services/dev.ts +++ b/packages/theme/src/cli/services/dev.ts @@ -1,10 +1,49 @@ import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui' import {joinPath} from '@shopify/cli-kit/node/path' +import {AdminSession, ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' +import {execCLI2} from '@shopify/cli-kit/node/ruby' +import {outputDebug} from '@shopify/cli-kit/node/output' import {access} from 'node:fs/promises' const DEFAULT_HOST = '127.0.0.1' const DEFAULT_PORT = '9292' +// Tokens are valid for 120 min, better to be safe and refresh every 110 min +const THEME_REFRESH_TIMEOUT_IN_MS = 110 * 60 * 1000 + +export interface DevOptions { + adminSession: AdminSession + directory: string + store: string + password?: string + theme: string + host?: string + port?: string + force: boolean + flagsToPass: string[] +} + +export async function dev(options: DevOptions) { + const command = ['theme', 'serve', options.directory, ...options.flagsToPass] + + if (!(await validThemeDirectory(options.directory)) && !(await currentDirectoryConfirmed(options.force))) { + return + } + + renderLinks(options.store, options.theme, options.host, options.port) + + if (!options.password) { + setInterval(() => { + outputDebug('Refreshing theme session tokens...') + // eslint-disable-next-line @typescript-eslint/no-floating-promises + refreshTokens(options.store, options.password) + }, THEME_REFRESH_TIMEOUT_IN_MS) + } + + const adminToken = options.password ? options.adminSession.token : undefined + await execCLI2(command, {store: options.store, adminToken}) +} + export function renderLinks(store: string, themeId: string, host = DEFAULT_HOST, port = DEFAULT_PORT) { renderSuccess({ body: [ @@ -84,3 +123,10 @@ export function showDeprecationWarnings(args: string[]) { }) } } + +export async function refreshTokens(store: string, password: string | undefined) { + const adminSession = await ensureAuthenticatedThemes(store, password, [], true) + const storefrontToken = await ensureAuthenticatedStorefront([], password) + await execCLI2(['theme', 'token', '--admin', adminSession.token, '--sfr', storefrontToken]) + return adminSession +} From 22d30f4addf22bf5b4a7fa80637b81bfaf5a2b3d Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 9 Jun 2023 17:20:41 +0200 Subject: [PATCH 10/47] Add changeset --- .changeset/clean-rats-talk.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-rats-talk.md diff --git a/.changeset/clean-rats-talk.md b/.changeset/clean-rats-talk.md new file mode 100644 index 0000000000..7dc08b8a9d --- /dev/null +++ b/.changeset/clean-rats-talk.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Fix session refresh for theme dev without password From 00257ca593d34e34ae2466237790dd965e3ee023 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 9 Jun 2023 17:48:09 +0200 Subject: [PATCH 11/47] Add tests for dev --- packages/theme/src/cli/services/dev.test.ts | 47 ++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/theme/src/cli/services/dev.test.ts b/packages/theme/src/cli/services/dev.test.ts index dcf3bd54a2..b3217ff8ad 100644 --- a/packages/theme/src/cli/services/dev.test.ts +++ b/packages/theme/src/cli/services/dev.test.ts @@ -1,4 +1,4 @@ -import {showDeprecationWarnings, REQUIRED_FOLDERS, validThemeDirectory, refreshTokens} from './dev.js' +import {showDeprecationWarnings, REQUIRED_FOLDERS, validThemeDirectory, refreshTokens, dev} from './dev.js' import {describe, expect, test, vi} from 'vitest' import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' import {joinPath} from '@shopify/cli-kit/node/path' @@ -7,6 +7,51 @@ import {execCLI2} from '@shopify/cli-kit/node/ruby' vi.mock('@shopify/cli-kit/node/ruby') +describe('dev', () => { + test('runs theme serve on CLI2 without passing a token when no password is used', async () => { + // When + const adminSession = {storeFqdn: 'my-store.myshopify.com', token: 'my-token'} + const options = { + adminSession, + directory: 'my-directory', + store: 'my-store', + theme: '123', + force: false, + flagsToPass: [], + } + await dev(options) + + // Then + const expectedParams = ['theme', 'serve', 'my-directory'] + expect(execCLI2).toHaveBeenCalledWith(expectedParams, { + store: 'my-store', + adminToken: undefined, + }) + }) + + test('runs theme serve on CLI2 passing a token when a password is used', async () => { + // When + const adminSession = {storeFqdn: 'my-store.myshopify.com', token: 'my-token'} + const options = { + adminSession, + directory: 'my-directory', + store: 'my-store', + theme: '123', + force: false, + flagsToPass: [], + password: 'my-token', + } + await dev(options) + + // Then + const expectedParams = ['theme', 'serve', 'my-directory'] + expect(execCLI2).toHaveBeenCalledWith(expectedParams, { + store: 'my-store', + adminToken: 'my-token', + }) + }) +}) + describe('validThemeDirectory', () => { test('should not consider an empty directory to be a valid theme directory', async () => { await inTemporaryDirectory(async (tmpDir) => { From 7f578d64b92623a16e2f29e105055d2bd299bfa1 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Mon, 12 Jun 2023 12:14:43 +0200 Subject: [PATCH 12/47] Do not refresh tokens when using bundled CLI --- packages/cli-kit/src/private/node/constants.ts | 1 + packages/cli-kit/src/public/node/context/local.ts | 10 ++++++++++ packages/cli-kit/src/public/node/ruby.ts | 4 ++-- packages/theme/src/cli/commands/theme/dev.ts | 7 +++++-- packages/theme/src/cli/services/dev.test.ts | 12 ++++-------- packages/theme/src/cli/services/dev.ts | 14 ++++++++++---- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/cli-kit/src/private/node/constants.ts b/packages/cli-kit/src/private/node/constants.ts index 8e108147f5..f7eaa922c9 100644 --- a/packages/cli-kit/src/private/node/constants.ts +++ b/packages/cli-kit/src/private/node/constants.ts @@ -24,6 +24,7 @@ export const environmentVariables = { unitTest: 'SHOPIFY_UNIT_TEST', verbose: 'SHOPIFY_FLAG_VERBOSE', noThemeBundling: 'SHOPIFY_CLI_NO_THEME_BUNDLING', + bundledThemeCLI: 'SHOPIFY_CLI_BUNDLED_THEME_CLI', // Variables to detect if the CLI is running in a cloud environment codespaceName: 'CODESPACE_NAME', codespaces: 'CODESPACES', diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index ee2dd38686..ba081cff51 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -122,6 +122,16 @@ export function useThemebundling(env = process.env): boolean { return !isTruthy(env[environmentVariables.noThemeBundling]) } +/** + * Returns true if the embedded CLI will be used for theme commands. + * + * @param env - The environment variables from the environment of the current process. + * @returns False if SHOPIFY_CLI_BUNDLED_THEME_CLI is truthy. + */ +export function useEmbeddedThemeCLI(env = process.env): boolean { + return !isTruthy(env[environmentVariables.bundledThemeCLI]) +} + /** * Return gitpodURL if we are running in gitpod. * Https://www.gitpod.io/docs/environment-variables#default-environment-variables. diff --git a/packages/cli-kit/src/public/node/ruby.ts b/packages/cli-kit/src/public/node/ruby.ts index aded7fa926..fee12cdeb2 100644 --- a/packages/cli-kit/src/public/node/ruby.ts +++ b/packages/cli-kit/src/public/node/ruby.ts @@ -6,7 +6,7 @@ import {joinPath, dirname, cwd} from './path.js' import {AbortError, AbortSilentError} from './error.js' import {getEnvironmentVariables} from './environment.js' import {isSpinEnvironment, spinFqdn} from './context/spin.js' -import {firstPartyDev} from './context/local.js' +import {firstPartyDev, useEmbeddedThemeCLI} from './context/local.js' import {pathConstants} from '../../private/node/constants.js' import {outputContent, outputToken} from '../../public/node/output.js' import {isTruthy} from '../../private/node/context/utilities.js' @@ -51,7 +51,7 @@ interface ExecCLI2Options { */ export async function execCLI2(args: string[], options: ExecCLI2Options = {}): Promise { const currentEnv = getEnvironmentVariables() - const embedded = !isTruthy(currentEnv.SHOPIFY_CLI_BUNDLED_THEME_CLI) && !currentEnv.SHOPIFY_CLI_2_0_DIRECTORY + const embedded = useEmbeddedThemeCLI(currentEnv) && !currentEnv.SHOPIFY_CLI_2_0_DIRECTORY await installCLIDependencies(options.stdout ?? process.stdout, embedded) const env: NodeJS.ProcessEnv = { diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index ca2573ead6..1fc9a45ad8 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -1,11 +1,12 @@ import {themeFlags} from '../../flags.js' import {ensureThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' -import {dev, refreshTokens, showDeprecationWarnings} from '../../services/dev.js' +import {dev, showDeprecationWarnings} from '../../services/dev.js' import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' import {findOrSelectTheme} from '../../utilities/theme-selector.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' +import {ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' export default class Dev extends ThemeCommand { static description = @@ -103,7 +104,8 @@ export default class Dev extends ThemeCommand { let {flags} = await this.parse(Dev) const store = ensureThemeStore(flags) - const adminSession = await refreshTokens(store, flags.password) + const adminSession = await ensureAuthenticatedThemes(store, flags.password, [], true) + const storefrontToken = await ensureAuthenticatedStorefront([], flags.password) if (flags.theme) { const filter = {filter: {theme: flags.theme}} @@ -121,6 +123,7 @@ export default class Dev extends ThemeCommand { await dev({ adminSession, + storefrontToken, directory: flags.path, store: ensureThemeStore(flags), password: flags.password, diff --git a/packages/theme/src/cli/services/dev.test.ts b/packages/theme/src/cli/services/dev.test.ts index b3217ff8ad..2a4a06cdba 100644 --- a/packages/theme/src/cli/services/dev.test.ts +++ b/packages/theme/src/cli/services/dev.test.ts @@ -13,6 +13,7 @@ describe('dev', () => { const adminSession = {storeFqdn: 'my-store.myshopify.com', token: 'my-token'} const options = { adminSession, + storefrontToken: 'my-storefront-token', directory: 'my-directory', store: 'my-store', theme: '123', @@ -26,6 +27,7 @@ describe('dev', () => { expect(execCLI2).toHaveBeenCalledWith(expectedParams, { store: 'my-store', adminToken: undefined, + storefrontToken: undefined, }) }) @@ -34,6 +36,7 @@ describe('dev', () => { const adminSession = {storeFqdn: 'my-store.myshopify.com', token: 'my-token'} const options = { adminSession, + storefrontToken: 'my-storefront-token', directory: 'my-directory', store: 'my-store', theme: '123', @@ -48,6 +51,7 @@ describe('dev', () => { expect(execCLI2).toHaveBeenCalledWith(expectedParams, { store: 'my-store', adminToken: 'my-token', + storefrontToken: 'my-storefront-token', }) }) }) @@ -110,14 +114,6 @@ describe('showDeprecationWarnings', () => { }) describe('refreshTokens', () => { - test('returns the admin token and store', async () => { - // When - const result = await refreshTokens('my-store', 'my-password') - - // Then - expect(result).toEqual({storeFqdn: 'my-store.myshopify.com', token: 'my-password'}) - }) - test('refreshes CLI2 cache with theme token command', async () => { // When await refreshTokens('my-store', 'my-password') diff --git a/packages/theme/src/cli/services/dev.ts b/packages/theme/src/cli/services/dev.ts index 261cf8462d..233055ca98 100644 --- a/packages/theme/src/cli/services/dev.ts +++ b/packages/theme/src/cli/services/dev.ts @@ -3,6 +3,7 @@ import {joinPath} from '@shopify/cli-kit/node/path' import {AdminSession, ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' import {execCLI2} from '@shopify/cli-kit/node/ruby' import {outputDebug} from '@shopify/cli-kit/node/output' +import {useEmbeddedThemeCLI} from '@shopify/cli-kit/node/context/local' import {access} from 'node:fs/promises' const DEFAULT_HOST = '127.0.0.1' @@ -13,6 +14,7 @@ const THEME_REFRESH_TIMEOUT_IN_MS = 110 * 60 * 1000 export interface DevOptions { adminSession: AdminSession + storefrontToken: string directory: string store: string password?: string @@ -32,7 +34,13 @@ export async function dev(options: DevOptions) { renderLinks(options.store, options.theme, options.host, options.port) - if (!options.password) { + let adminToken: string | undefined = options.adminSession.token + let storefrontToken: string | undefined = options.storefrontToken + + if (!options.password && useEmbeddedThemeCLI()) { + adminToken = undefined + storefrontToken = undefined + setInterval(() => { outputDebug('Refreshing theme session tokens...') // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -40,8 +48,7 @@ export async function dev(options: DevOptions) { }, THEME_REFRESH_TIMEOUT_IN_MS) } - const adminToken = options.password ? options.adminSession.token : undefined - await execCLI2(command, {store: options.store, adminToken}) + await execCLI2(command, {store: options.store, adminToken, storefrontToken}) } export function renderLinks(store: string, themeId: string, host = DEFAULT_HOST, port = DEFAULT_PORT) { @@ -128,5 +135,4 @@ export async function refreshTokens(store: string, password: string | undefined) const adminSession = await ensureAuthenticatedThemes(store, password, [], true) const storefrontToken = await ensureAuthenticatedStorefront([], password) await execCLI2(['theme', 'token', '--admin', adminSession.token, '--sfr', storefrontToken]) - return adminSession } From ad60b8f477992db0c74f3c125f43e789fc8f9e0d Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Mon, 12 Jun 2023 19:33:59 +0200 Subject: [PATCH 13/47] Fix clean login on theme dev --- packages/theme/src/cli/commands/theme/dev.ts | 8 +++----- packages/theme/src/cli/services/dev.test.ts | 11 +++++++++++ packages/theme/src/cli/services/dev.ts | 5 ++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 1fc9a45ad8..2c16ca7f70 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -1,12 +1,11 @@ import {themeFlags} from '../../flags.js' import {ensureThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' -import {dev, showDeprecationWarnings} from '../../services/dev.js' +import {dev, refreshTokens, showDeprecationWarnings} from '../../services/dev.js' import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' import {findOrSelectTheme} from '../../utilities/theme-selector.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' -import {ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' export default class Dev extends ThemeCommand { static description = @@ -104,8 +103,7 @@ export default class Dev extends ThemeCommand { let {flags} = await this.parse(Dev) const store = ensureThemeStore(flags) - const adminSession = await ensureAuthenticatedThemes(store, flags.password, [], true) - const storefrontToken = await ensureAuthenticatedStorefront([], flags.password) + const {adminSession, storefrontToken} = await refreshTokens(store, flags.password) if (flags.theme) { const filter = {filter: {theme: flags.theme}} @@ -125,7 +123,7 @@ export default class Dev extends ThemeCommand { adminSession, storefrontToken, directory: flags.path, - store: ensureThemeStore(flags), + store, password: flags.password, theme: flags.theme!, host: flags.host, diff --git a/packages/theme/src/cli/services/dev.test.ts b/packages/theme/src/cli/services/dev.test.ts index 2a4a06cdba..17a0efeac4 100644 --- a/packages/theme/src/cli/services/dev.test.ts +++ b/packages/theme/src/cli/services/dev.test.ts @@ -114,6 +114,17 @@ describe('showDeprecationWarnings', () => { }) describe('refreshTokens', () => { + test('returns the admin session and storefront token', async () => { + // When + const result = await refreshTokens('my-store', 'my-password') + + // Then + expect(result).toEqual({ + adminSession: {storeFqdn: 'my-store.myshopify.com', token: 'my-password'}, + storefrontToken: 'my-password', + }) + }) + test('refreshes CLI2 cache with theme token command', async () => { // When await refreshTokens('my-store', 'my-password') diff --git a/packages/theme/src/cli/services/dev.ts b/packages/theme/src/cli/services/dev.ts index 233055ca98..3688a40a77 100644 --- a/packages/theme/src/cli/services/dev.ts +++ b/packages/theme/src/cli/services/dev.ts @@ -134,5 +134,8 @@ export function showDeprecationWarnings(args: string[]) { export async function refreshTokens(store: string, password: string | undefined) { const adminSession = await ensureAuthenticatedThemes(store, password, [], true) const storefrontToken = await ensureAuthenticatedStorefront([], password) - await execCLI2(['theme', 'token', '--admin', adminSession.token, '--sfr', storefrontToken]) + if (useEmbeddedThemeCLI()) { + await execCLI2(['theme', 'token', '--admin', adminSession.token, '--sfr', storefrontToken]) + } + return {adminSession, storefrontToken} } From 76694a35f9dd779ae95d2eea3b197e5f519e1329 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Mon, 12 Jun 2023 19:34:29 +0200 Subject: [PATCH 14/47] Add changeset --- .changeset/silly-shoes-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silly-shoes-visit.md diff --git a/.changeset/silly-shoes-visit.md b/.changeset/silly-shoes-visit.md new file mode 100644 index 0000000000..2d48ee7a47 --- /dev/null +++ b/.changeset/silly-shoes-visit.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Fix clean login on theme dev From dad44212c303cc32c21cae03ee0afcd6990d8d07 Mon Sep 17 00:00:00 2001 From: Alvaro Gutierrez Date: Tue, 13 Jun 2023 11:49:59 +0200 Subject: [PATCH 15/47] Skip .DS_Dtore files when the theme app extension bundle content is created --- .changeset/fair-rockets-flow.md | 5 ++ .../cli/services/extensions/bundle.test.ts | 72 ++++++++++++++++++- .../app/src/cli/services/extensions/bundle.ts | 2 +- 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 .changeset/fair-rockets-flow.md diff --git a/.changeset/fair-rockets-flow.md b/.changeset/fair-rockets-flow.md new file mode 100644 index 0000000000..14ffe93767 --- /dev/null +++ b/.changeset/fair-rockets-flow.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': patch +--- + +Skip .DS_Dtore files when the theme app extension bundle content is created diff --git a/packages/app/src/cli/services/extensions/bundle.test.ts b/packages/app/src/cli/services/extensions/bundle.test.ts index f05c5d3084..3c82b0b544 100644 --- a/packages/app/src/cli/services/extensions/bundle.test.ts +++ b/packages/app/src/cli/services/extensions/bundle.test.ts @@ -1,8 +1,12 @@ -import {bundleExtension} from './bundle.js' +import {bundleExtension, bundleThemeExtension} from './bundle.js' import {testApp, testUIExtension} from '../../models/app/app.test-data.js' +import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' +import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {describe, expect, test, vi} from 'vitest' import {context as esContext} from 'esbuild' import {AbortController} from '@shopify/cli-kit/node/abort' +import {glob, inTemporaryDirectory, mkdir, touchFile} from '@shopify/cli-kit/node/fs' +import {basename, joinPath} from '@shopify/cli-kit/node/path' vi.mock('esbuild', async () => { const esbuild: any = await vi.importActual('esbuild') @@ -192,4 +196,70 @@ describe('bundleExtension()', () => { mangleCache: {}, } } + describe('bundleThemeExtension()', () => { + test('should skip all ignored file patterns', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'theme')! + const themeExtension = new ExtensionInstance({ + configuration: { + name: 'theme extension name', + type: 'theme' as const, + metafields: [], + }, + configurationPath: '', + directory: tmpDir, + specification, + }) + + const outputPath = joinPath(tmpDir, 'dist') + await mkdir(outputPath) + themeExtension.outputPath = outputPath + + const app = testApp({ + directory: '/project', + dotenv: { + path: '/project/.env', + variables: { + FOO: 'BAR', + }, + }, + allExtensions: [themeExtension], + }) + + const stdout: any = { + write: vi.fn(), + } + const stderr: any = { + write: vi.fn(), + } + + const blocksPath = joinPath(tmpDir, 'blocks') + await mkdir(blocksPath) + + const ignoredFiles = ['.gitkeep', '.DS_Store', '.shopify.theme.extension.toml'] + await Promise.all( + ['test.liquid', ...ignoredFiles].map(async (filename) => { + touchFile(joinPath(blocksPath, filename)) + touchFile(joinPath(tmpDir, filename)) + }), + ) + + // When + await bundleThemeExtension(themeExtension, { + app, + stdout, + stderr, + }) + + // Then + const filePaths = await glob(joinPath(themeExtension.outputPath, '/**/*')) + const hasFiles = filePaths + .map((filePath) => basename(filePath)) + .some((filename) => ignoredFiles.includes(filename)) + expect(hasFiles).toEqual(false) + }) + }) + }) }) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index 5b0cd8ef8f..e4e9fa6bea 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -73,7 +73,7 @@ export async function bundleThemeExtension( await Promise.all( files.map(function (filepath) { - if (!(filepath.includes('.gitkeep') || filepath.includes('.toml'))) { + if (!(filepath.includes('.gitkeep') || filepath.includes('.toml') || filepath.includes('.DS_Store'))) { const relativePathName = relativePath(extension.directory, filepath) const outputFile = joinPath(extension.outputPath, relativePathName) return copyFile(filepath, outputFile) From 501c21e6ca035fd1af640b1f0713547625961bfb Mon Sep 17 00:00:00 2001 From: Alvaro Gutierrez Date: Tue, 13 Jun 2023 12:19:43 +0200 Subject: [PATCH 16/47] Fixed lint errors --- packages/app/src/cli/services/extensions/bundle.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.test.ts b/packages/app/src/cli/services/extensions/bundle.test.ts index 3c82b0b544..892cd57706 100644 --- a/packages/app/src/cli/services/extensions/bundle.test.ts +++ b/packages/app/src/cli/services/extensions/bundle.test.ts @@ -5,7 +5,7 @@ import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {describe, expect, test, vi} from 'vitest' import {context as esContext} from 'esbuild' import {AbortController} from '@shopify/cli-kit/node/abort' -import {glob, inTemporaryDirectory, mkdir, touchFile} from '@shopify/cli-kit/node/fs' +import {glob, inTemporaryDirectory, mkdir, touchFileSync} from '@shopify/cli-kit/node/fs' import {basename, joinPath} from '@shopify/cli-kit/node/path' vi.mock('esbuild', async () => { @@ -241,8 +241,8 @@ describe('bundleExtension()', () => { const ignoredFiles = ['.gitkeep', '.DS_Store', '.shopify.theme.extension.toml'] await Promise.all( ['test.liquid', ...ignoredFiles].map(async (filename) => { - touchFile(joinPath(blocksPath, filename)) - touchFile(joinPath(tmpDir, filename)) + touchFileSync(joinPath(blocksPath, filename)) + touchFileSync(joinPath(tmpDir, filename)) }), ) From 2f51fff039d83aafd679e193cfc5a51addfb3db2 Mon Sep 17 00:00:00 2001 From: Jimmy Bourassa Date: Fri, 9 Jun 2023 10:54:59 -0400 Subject: [PATCH 17/47] Support Function `[[targets]]` --- .../specifications/function.test.ts | 46 +++++++++++++++---- .../extensions/specifications/function.ts | 35 ++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/packages/app/src/cli/models/extensions/specifications/function.test.ts b/packages/app/src/cli/models/extensions/specifications/function.test.ts index 5a33f0c7a5..618b7364ef 100644 --- a/packages/app/src/cli/models/extensions/specifications/function.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/function.test.ts @@ -1,16 +1,19 @@ import {FunctionConfigType} from './function.js' -import {Identifiers} from '../../app/identifiers.js' import {testFunctionExtension} from '../../app/app.test-data.js' import {ExtensionInstance} from '../extension-instance.js' import {inTemporaryDirectory, touchFile, writeFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' +import {AbortError} from '@shopify/cli-kit/node/error' import {beforeEach, describe, expect, test} from 'vitest' describe('functionConfiguration', () => { let extension: ExtensionInstance - let identifiers: Identifiers - let token: string + let moduleId: string + let appKey: string beforeEach(async () => { + moduleId = 'module_id' + appKey = 'app-key' extension = await testFunctionExtension({ dir: '/function', config: { @@ -44,8 +47,6 @@ describe('functionConfiguration', () => { test('returns a snake_case object with all possible fields', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given - const moduleId = 'module_id' - const appKey = 'app-key' const inputQuery = 'inputQuery' extension.directory = tmpDir await touchFile(extension.inputQueryPath) @@ -81,10 +82,8 @@ describe('functionConfiguration', () => { }) test('returns a snake_case object with only required fields', async () => { - await inTemporaryDirectory(async (tmpDir) => { + await inTemporaryDirectory(async (_tmpDir) => { // Given - const moduleId = 'module_id' - const appKey = 'app-key' extension.configuration.input = undefined extension.configuration.ui = undefined @@ -106,4 +105,35 @@ describe('functionConfiguration', () => { }) }) }) + + test('parses targets', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + extension.directory = tmpDir + const inputQuery = 'query { f }' + const inputQueryFileName = 'target1.graphql' + extension.configuration.targets = [ + {target: 'some.api.target1', input_query: inputQueryFileName}, + {target: 'some.api.target2', export: 'run_target2'}, + ] + await writeFile(joinPath(extension.directory, inputQueryFileName), inputQuery) + + // When + const got = await extension.deployConfig(appKey, moduleId) + + // Then + expect(got!.targets).toEqual([ + {handle: 'some.api.target1', input_query: inputQuery}, + {handle: 'some.api.target2', export: 'run_target2'}, + ]) + }) + }) + + test('aborts when an target input query file is missing', async () => { + // Given + extension.configuration.targets = [{target: 'some.api.target1', input_query: 'this-is-not-a-file.graphql'}] + + // When & Then + await expect(() => extension.deployConfig(appKey, moduleId)).rejects.toThrowError(AbortError) + }) }) diff --git a/packages/app/src/cli/models/extensions/specifications/function.ts b/packages/app/src/cli/models/extensions/specifications/function.ts index 81b8a0968b..2c032ef877 100644 --- a/packages/app/src/cli/models/extensions/specifications/function.ts +++ b/packages/app/src/cli/models/extensions/specifications/function.ts @@ -39,6 +39,15 @@ export const FunctionExtensionSchema = BaseSchema.extend({ .optional(), }) .optional(), + targets: zod + .array( + zod.object({ + target: zod.string(), + input_query: zod.string().optional(), + export: zod.string().optional(), + }), + ) + .optional(), }) const spec = createExtensionSpecification({ @@ -67,6 +76,20 @@ const spec = createExtensionSpecification({ inputQuery = await readFile(inputQueryPath) } + const targets = + config.targets && + (await Promise.all( + config.targets.map(async (config) => { + let inputQuery + + if (config.input_query) { + inputQuery = await readInputQuery(joinPath(directory, config.input_query)) + } + + return {handle: config.target, export: config.export, input_query: inputQuery} + }), + )) + return { title: config.name, module_id: moduleId, @@ -89,6 +112,7 @@ const spec = createExtensionSpecification({ } : undefined, enable_creation_ui: config.ui?.enable_create ?? true, + targets, } }, preDeployValidation: async (extension) => { @@ -102,4 +126,15 @@ const spec = createExtensionSpecification({ }, }) +async function readInputQuery(path: string): Promise { + if (await fileExists(path)) { + return readFile(path) + } else { + throw new AbortError( + `No input query file at ${path}.`, + `Create the file or remove the line referencing it in the extension's TOML.`, + ) + } +} + export default spec From 44ed9e2416300e6281126dc36bff08216e08c7d7 Mon Sep 17 00:00:00 2001 From: Jimmy Bourassa Date: Fri, 9 Jun 2023 12:54:58 -0400 Subject: [PATCH 18/47] Rename targets field to targeting --- .../cli/models/extensions/specifications/function.test.ts | 6 +++--- .../src/cli/models/extensions/specifications/function.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/app/src/cli/models/extensions/specifications/function.test.ts b/packages/app/src/cli/models/extensions/specifications/function.test.ts index 618b7364ef..124ddf2641 100644 --- a/packages/app/src/cli/models/extensions/specifications/function.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/function.test.ts @@ -106,13 +106,13 @@ describe('functionConfiguration', () => { }) }) - test('parses targets', async () => { + test('parses targeting array', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given extension.directory = tmpDir const inputQuery = 'query { f }' const inputQueryFileName = 'target1.graphql' - extension.configuration.targets = [ + extension.configuration.targeting = [ {target: 'some.api.target1', input_query: inputQueryFileName}, {target: 'some.api.target2', export: 'run_target2'}, ] @@ -131,7 +131,7 @@ describe('functionConfiguration', () => { test('aborts when an target input query file is missing', async () => { // Given - extension.configuration.targets = [{target: 'some.api.target1', input_query: 'this-is-not-a-file.graphql'}] + extension.configuration.targeting = [{target: 'some.api.target1', input_query: 'this-is-not-a-file.graphql'}] // When & Then await expect(() => extension.deployConfig(appKey, moduleId)).rejects.toThrowError(AbortError) diff --git a/packages/app/src/cli/models/extensions/specifications/function.ts b/packages/app/src/cli/models/extensions/specifications/function.ts index 2c032ef877..8274383168 100644 --- a/packages/app/src/cli/models/extensions/specifications/function.ts +++ b/packages/app/src/cli/models/extensions/specifications/function.ts @@ -39,7 +39,7 @@ export const FunctionExtensionSchema = BaseSchema.extend({ .optional(), }) .optional(), - targets: zod + targeting: zod .array( zod.object({ target: zod.string(), @@ -77,9 +77,9 @@ const spec = createExtensionSpecification({ } const targets = - config.targets && + config.targeting && (await Promise.all( - config.targets.map(async (config) => { + config.targeting.map(async (config) => { let inputQuery if (config.input_query) { From 51ef2105a4063e596d1f8699d4f113c8c96bb1ef Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Mon, 5 Jun 2023 19:41:18 -0700 Subject: [PATCH 19/47] Allow extensions to have a .shopifyignorefile --- packages/app/src/cli/services/extensions/bundle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index e4e9fa6bea..ee58fb2819 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -73,7 +73,7 @@ export async function bundleThemeExtension( await Promise.all( files.map(function (filepath) { - if (!(filepath.includes('.gitkeep') || filepath.includes('.toml') || filepath.includes('.DS_Store'))) { + if (!(filepath.includes('.gitkeep') || filepath.includes('.toml') || filepath.includes('.DS_Store') || filepath.includes('.shopifyignore'))) { const relativePathName = relativePath(extension.directory, filepath) const outputFile = joinPath(extension.outputPath, relativePathName) return copyFile(filepath, outputFile) From 0c4e48a586ed1ad6aac5fd7a9eacaf49232108ed Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Mon, 5 Jun 2023 22:13:33 -0700 Subject: [PATCH 20/47] Exclude files from reading --- .../app/src/cli/services/extensions/bundle.ts | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index ee58fb2819..8d9946520b 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -2,10 +2,11 @@ import {ExtensionBuildOptions} from '../build/extension.js' import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {context as esContext, BuildResult, formatMessagesSync} from 'esbuild' import {AbortSignal} from '@shopify/cli-kit/node/abort' -import {copyFile, glob} from '@shopify/cli-kit/node/fs' +import {copyFile, glob, fileExistsSync, createFileReadStream} from '@shopify/cli-kit/node/fs' import {joinPath, relativePath} from '@shopify/cli-kit/node/path' import {Writable} from 'stream' import {createRequire} from 'module' +import {createInterface} from 'readline' import type {StdinOptions, build as esBuild} from 'esbuild' const require = createRequire(import.meta.url) @@ -69,7 +70,13 @@ export async function bundleThemeExtension( options: ExtensionBuildOptions, ): Promise { options.stdout.write(`Bundling theme extension ${extension.localIdentifier}...`) - const files = await glob(joinPath(extension.directory, '/**/*')) + const filepath = joinPath(extension.directory, '.shopifyignore') + const ignore = fileExistsSync(joinPath(filepath)) ? await parseIgnoreFile(filepath) : [] + const files = await glob('**/*', { + absolute: true, + cwd: extension.directory, + ignore, + }) await Promise.all( files.map(function (filepath) { @@ -172,3 +179,34 @@ function isGraphqlPackageAvailable(): boolean { return false } } + +/** + * Parses the ignore file and returns the patterns that should be ignored. + * @param filepath - Filepath to the ignore file. + * @returns Returns the patterns that should be ignored. + */ +export function parseIgnoreFile(filepath: string): Promise { + return new Promise((resolve, reject) => { + const patterns: string[] = [] + + const readLineInterface = createInterface({ + input: createFileReadStream(filepath), + crlfDelay: Infinity, + }) + + readLineInterface.on('line', (line: string) => { + const trimmedLine = line.trim() + if (trimmedLine.length > 0 && !trimmedLine.startsWith('#')) { + patterns.push(trimmedLine) + } + }) + + readLineInterface.on('close', () => { + resolve(patterns) + }) + + readLineInterface.on('error', (error: Error) => { + reject(error) + }) + }) +} From 8fbecf024756c7226420bfdca2c281492c2e47e9 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Tue, 6 Jun 2023 11:23:58 -0700 Subject: [PATCH 21/47] Add unit tests --- .../cli/services/extensions/bundle.test.ts | 21 +++++++++++++++++-- .../app/src/cli/services/extensions/bundle.ts | 9 +++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.test.ts b/packages/app/src/cli/services/extensions/bundle.test.ts index 892cd57706..9c546cf554 100644 --- a/packages/app/src/cli/services/extensions/bundle.test.ts +++ b/packages/app/src/cli/services/extensions/bundle.test.ts @@ -1,11 +1,11 @@ -import {bundleExtension, bundleThemeExtension} from './bundle.js' +import {bundleExtension, bundleThemeExtension, parseIgnoreFile} from './bundle.js' import {testApp, testUIExtension} from '../../models/app/app.test-data.js' import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {describe, expect, test, vi} from 'vitest' import {context as esContext} from 'esbuild' import {AbortController} from '@shopify/cli-kit/node/abort' -import {glob, inTemporaryDirectory, mkdir, touchFileSync} from '@shopify/cli-kit/node/fs' +import {glob, inTemporaryDirectory, mkdir, touchFileSync, writeFile} from '@shopify/cli-kit/node/fs' import {basename, joinPath} from '@shopify/cli-kit/node/path' vi.mock('esbuild', async () => { @@ -263,3 +263,20 @@ describe('bundleExtension()', () => { }) }) }) + +describe('parseIgnoreFile()', () => { + test('returns the patterns that should be ignored', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const filePath = joinPath(tmpDir, '.shopifyignore') + const content = '#foo\nbar\nbaz\n' + await writeFile(filePath, content) + + // When + const patterns = await parseIgnoreFile(filePath) + + // Then + expect(patterns).toEqual(['bar', 'baz']) + }) + }) +}) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index 8d9946520b..cfb347a048 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -80,7 +80,14 @@ export async function bundleThemeExtension( await Promise.all( files.map(function (filepath) { - if (!(filepath.includes('.gitkeep') || filepath.includes('.toml') || filepath.includes('.DS_Store') || filepath.includes('.shopifyignore'))) { + if ( + !( + filepath.includes('.gitkeep') || + filepath.includes('.toml') || + filepath.includes('.DS_Store') || + filepath.includes('.shopifyignore') + ) + ) { const relativePathName = relativePath(extension.directory, filepath) const outputFile = joinPath(extension.outputPath, relativePathName) return copyFile(filepath, outputFile) From 826b6166853035501a5a54acb9c5f51d2e5d1709 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Tue, 6 Jun 2023 11:38:20 -0700 Subject: [PATCH 22/47] Update JSDocs --- packages/app/src/cli/services/extensions/bundle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index cfb347a048..19d424dd6a 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -190,7 +190,7 @@ function isGraphqlPackageAvailable(): boolean { /** * Parses the ignore file and returns the patterns that should be ignored. * @param filepath - Filepath to the ignore file. - * @returns Returns the patterns that should be ignored. + * @returns A promise that resolves with the patterns that should be ignored. */ export function parseIgnoreFile(filepath: string): Promise { return new Promise((resolve, reject) => { From d855427c758f215d71422fff6964f2d80ed75306 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Wed, 7 Jun 2023 06:31:19 -0700 Subject: [PATCH 23/47] Clean up --- packages/app/src/cli/services/extensions/bundle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index 19d424dd6a..45311d96a6 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -71,7 +71,7 @@ export async function bundleThemeExtension( ): Promise { options.stdout.write(`Bundling theme extension ${extension.localIdentifier}...`) const filepath = joinPath(extension.directory, '.shopifyignore') - const ignore = fileExistsSync(joinPath(filepath)) ? await parseIgnoreFile(filepath) : [] + const ignore = fileExistsSync(filepath) ? await parseIgnoreFile(filepath) : [] const files = await glob('**/*', { absolute: true, cwd: extension.directory, From d88380d70154cfe4d934f5a080ed4d823b2450f3 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Wed, 7 Jun 2023 23:54:29 -0700 Subject: [PATCH 24/47] Add release note --- .changeset/heavy-glasses-lick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-glasses-lick.md diff --git a/.changeset/heavy-glasses-lick.md b/.changeset/heavy-glasses-lick.md new file mode 100644 index 0000000000..cdc00de081 --- /dev/null +++ b/.changeset/heavy-glasses-lick.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': minor +--- + +Allow theme app extensions to exclude files from deploying via `.shopifyignore` file From bd7ac92dcbe7add493e582377532e5d2b9537bf0 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Thu, 8 Jun 2023 06:57:08 -0700 Subject: [PATCH 25/47] Update release note --- .changeset/heavy-glasses-lick.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/heavy-glasses-lick.md b/.changeset/heavy-glasses-lick.md index cdc00de081..8bda00453b 100644 --- a/.changeset/heavy-glasses-lick.md +++ b/.changeset/heavy-glasses-lick.md @@ -1,5 +1,5 @@ --- -'@shopify/app': minor +'@shopify/app': patch --- Allow theme app extensions to exclude files from deploying via `.shopifyignore` file From 1895b80533aa1afab42df296b505b7f4dc0e0ca8 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Sat, 10 Jun 2023 01:23:44 -0700 Subject: [PATCH 26/47] Skip files that are ignored by the ignore filter --- .../theme_app_extension.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb b/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb index d5c931fcb9..be70181a4a 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb @@ -2,6 +2,7 @@ require "base64" require "json" require "shopify_cli/theme/extension/dev_server" +require "shopify_cli/theme/ignore_filter" module Extension module Models @@ -25,6 +26,7 @@ def create(directory_name, context, getting_started: false) end def config(context) + @ignore_filter ||= ShopifyCLI::Theme::IgnoreFilter.from_path(context.root) current_size = 0 current_liquid_size = 0 Dir.chdir(context.root) do @@ -90,13 +92,30 @@ def serve(**options) ShopifyCLI::Theme::Extension::DevServer.start(@ctx, root, **properties) end + def ignore_path?(path) + is_ignored = ignored_by_ignore_filter?(path) + + if is_ignored && @ctx + @ctx.debug("ignore #{path}") + end + + is_ignored + end + private + def ignored_by_ignore_filter?(path) + @ignore_filter&.ignore?(path) + end + def validate(filename) dirname = File.dirname(filename) # Skip files in the root of the directory tree return false if dirname == "." + # Skip files that are ignored by the ignore filter + return false if ignore_path?(filename) + unless SUPPORTED_BUCKETS.include?(dirname) raise Extension::Errors::InvalidFilenameError, "Invalid directory: #{dirname}" end From 4938fc99318e3ef9808dfee03afb089367bf80a8 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Sun, 11 Jun 2023 23:15:10 -0700 Subject: [PATCH 27/47] Skip operations that are ignored by the ignore filter --- .../theme_app_extension.rb | 19 +++------- .../shopify_cli/theme/extension/dev_server.rb | 8 ++++- .../theme/extension/ignore_helper.rb | 35 +++++++++++++++++++ .../lib/shopify_cli/theme/extension/syncer.rb | 25 ++++++++++--- .../theme/extension/syncer/operation.rb | 4 +++ 5 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/ignore_helper.rb diff --git a/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb b/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb index be70181a4a..39ba9bd59d 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb @@ -3,11 +3,16 @@ require "json" require "shopify_cli/theme/extension/dev_server" require "shopify_cli/theme/ignore_filter" +require "shopify_cli/theme/extension/ignore_helper" module Extension module Models module SpecificationHandlers class ThemeAppExtension < Default + include ShopifyCLI::Theme::Extension::IgnoreHelper + + attr_reader :ignore_filter + SUPPORTED_BUCKETS = %w(assets blocks snippets locales) BUNDLE_SIZE_LIMIT = 10 * 1024 * 1024 # 10MB LIQUID_SIZE_LIMIT = 100 * 1024 # 100kb @@ -92,22 +97,8 @@ def serve(**options) ShopifyCLI::Theme::Extension::DevServer.start(@ctx, root, **properties) end - def ignore_path?(path) - is_ignored = ignored_by_ignore_filter?(path) - - if is_ignored && @ctx - @ctx.debug("ignore #{path}") - end - - is_ignored - end - private - def ignored_by_ignore_filter?(path) - @ignore_filter&.ignore?(path) - end - def validate(filename) dirname = File.dirname(filename) # Skip files in the root of the directory tree diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb index 47f4bb3b41..97ea8d9387 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb @@ -7,6 +7,7 @@ require "shopify_cli/theme/extension/host_theme" require "shopify_cli/theme/syncer" require "shopify_cli/theme/notifier" +require "shopify_cli/theme/ignore_filter" require_relative "dev_server/local_assets" require_relative "dev_server/proxy_param_builder" @@ -63,7 +64,8 @@ def syncer ctx, extension: extension, project: project, - specification_handler: specification_handler + specification_handler: specification_handler, + ignore_filter: ignore_filter ) end @@ -124,6 +126,10 @@ def param_builder .with_syncer(syncer) end + def ignore_filter + @ignore_filter ||= ShopifyCLI::Theme::IgnoreFilter.from_path(root) + end + def setup_server ctx.puts("\n--------- #{frame_title}") ctx.puts(preview_message) diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/ignore_helper.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/ignore_helper.rb new file mode 100644 index 0000000000..448b801b55 --- /dev/null +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/ignore_helper.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ShopifyCLI + module Theme + module Extension + module IgnoreHelper + def ignore_operation?(operation) + path = operation.file_path + ignore_path?(path) + end + + def ignore_file?(file) + path = file.relative_path + ignore_path?(path) + end + + def ignore_path?(path) + is_ignored = ignored_by_ignore_filter?(path) + + if is_ignored && @ctx + @ctx.debug("ignore #{path}") + end + + is_ignored + end + + private + + def ignored_by_ignore_filter?(path) + ignore_filter&.ignore?(path) + end + end + end + end +end diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer.rb index e048f28a1c..621c401930 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer.rb @@ -2,6 +2,7 @@ require_relative "syncer/extension_serve_job" require_relative "syncer/operation" +require_relative "ignore_helper" require "shopify_cli/thread_pool" @@ -9,16 +10,25 @@ module ShopifyCLI module Theme module Extension class Syncer - attr_accessor :pending_operations, :latest_sync + include ShopifyCLI::Theme::Extension::IgnoreHelper - def initialize(ctx, extension:, project:, specification_handler:) + attr_accessor :pending_operations, :latest_sync, :ignore_filter + + def initialize(ctx, extension:, project:, specification_handler:, ignore_filter: nil) @ctx = ctx @extension = extension @project = project @specification_handler = specification_handler + @ignore_filter = ignore_filter @pool = ThreadPool.new(pool_size: 1) - @pending_operations = extension.extension_files.map { |file| Operation.new(file, :update) } + @pending_operations = [] + + extension.extension_files.each do |file| + operation = Operation.new(file, :update) + @pending_operations << operation if enqueueable?(operation) + end + @pending_operations_mutex = Mutex.new @latest_sync = Time.now - ExtensionServeJob::PUSH_INTERVAL end @@ -60,9 +70,16 @@ def any_blocking_operation? private + def enqueueable?(operation) + # Already enqueued or ignored + return false if @pending_operations.include?(operation) || ignore_operation?(operation) + + true + end + def enqueue_operations(operations) @pending_operations_mutex.synchronize do - operations.each { |f| @pending_operations << f unless @pending_operations.include?(f) } + operations.each { |f| @pending_operations << f if enqueueable?(f) } end end diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer/operation.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer/operation.rb index f9e9c93315..c627d76295 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer/operation.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/syncer/operation.rb @@ -14,6 +14,10 @@ def delete? def create? kind == :create end + + def file_path + file.relative_path + end end end end From 54ea1794cb2e49f5e3597898d0ac689a79d2ee99 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Sun, 11 Jun 2023 23:47:52 -0700 Subject: [PATCH 28/47] Prevent ignored files from hot reloading --- .../shopify_cli/theme/extension/dev_server.rb | 3 ++- .../dev_server/hooks/file_change_hook.rb | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb index 97ea8d9387..9c8d1bb125 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb @@ -142,7 +142,8 @@ def setup_server # Hooks def broadcast_hooks - file_handler = Hooks::FileChangeHook.new(ctx, extension: extension, syncer: syncer, notifier: notifier) + file_handler = Hooks::FileChangeHook.new(ctx, extension: extension, syncer: syncer, notifier: notifier, + ignore_filter: ignore_filter) [file_handler] end diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb index fbee2652c7..7794c9ba65 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require "shopify_cli/theme/extension/ignore_helper" module ShopifyCLI module Theme @@ -6,20 +7,27 @@ module Extension class DevServer module Hooks class FileChangeHook - attr_reader :ctx, :extension, :syncer, :streams, :notifier + include ShopifyCLI::Theme::Extension::IgnoreHelper - def initialize(ctx, extension:, syncer:, notifier:) + attr_reader :ctx, :extension, :syncer, :streams, :notifier, :ignore_filter + + def initialize(ctx, extension:, syncer:, notifier:, ignore_filter: nil) @ctx = ctx @extension = extension @syncer = syncer + @ignore_filter = ignore_filter @notifier = notifier end def call(modified, added, removed, streams: nil) @streams = streams - modified = paths(modified).select { |file| @extension.extension_file?(file) } - added = paths(added).select { |file| @extension.extension_file?(file) } + modified = paths(modified) + .select { |file| @extension.extension_file?(file) } + .reject { |file| ignore_path?(file) } + added = paths(added) + .select { |file| @extension.extension_file?(file) } + .reject { |file| ignore_path?(file) } removed = paths(removed) hot_reload(modified) unless modified.empty? From 577c108046bd7cbd90524615911640ab70561ddc Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Mon, 12 Jun 2023 17:46:46 -0700 Subject: [PATCH 29/47] Add unit tests --- .../theme/extension/ignore_helper_test.rb | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 packages/cli-kit/assets/cli-ruby/test/shopify-cli/theme/extension/ignore_helper_test.rb diff --git a/packages/cli-kit/assets/cli-ruby/test/shopify-cli/theme/extension/ignore_helper_test.rb b/packages/cli-kit/assets/cli-ruby/test/shopify-cli/theme/extension/ignore_helper_test.rb new file mode 100644 index 0000000000..4a130a0c2e --- /dev/null +++ b/packages/cli-kit/assets/cli-ruby/test/shopify-cli/theme/extension/ignore_helper_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "test_helper" +require "shopify_cli/theme/extension/ignore_helper" + +module ShopifyCLI + module Theme + class IgnoreHelperTest < Minitest::Test + include ShopifyCLI::Theme::Extension::IgnoreHelper + + attr_reader :ignore_filter + + def test_ignore_operation_when_the_path_is_ignored + path = mock + operation = stub(file_path: path) + + expects(:ignore_path?).with(path).returns(true) + + assert(ignore_operation?(operation)) + end + + def test_ignore_operation_when_the_path_is_not_ignored + path = mock + operation = stub(file_path: path) + + expects(:ignore_path?).with(path).returns(false) + + refute(ignore_operation?(operation)) + end + + def test_ignore_file_when_the_path_is_ignored + relative_path = mock + file = stub(relative_path: relative_path) + + expects(:ignore_path?).with(relative_path).returns(true) + + assert(ignore_file?(file)) + end + + def test_ignore_file_when_the_path_is_not_ignored + relative_path = mock + file = stub(relative_path: relative_path) + + expects(:ignore_path?).with(relative_path).returns(false) + + refute(ignore_file?(file)) + end + end + end +end From a14779e9bd3bd80388b37f759ce7ebb71d640413 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Tue, 13 Jun 2023 18:12:50 -0700 Subject: [PATCH 30/47] Make themeExtensionFiles() return a filtered list of files --- .../cli/services/extensions/bundle.test.ts | 21 +------ .../app/src/cli/services/extensions/bundle.ts | 58 ++----------------- .../cli/utilities/extensions/theme.test.ts | 19 +++++- .../app/src/cli/utilities/extensions/theme.ts | 51 +++++++++++++++- 4 files changed, 74 insertions(+), 75 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.test.ts b/packages/app/src/cli/services/extensions/bundle.test.ts index 9c546cf554..892cd57706 100644 --- a/packages/app/src/cli/services/extensions/bundle.test.ts +++ b/packages/app/src/cli/services/extensions/bundle.test.ts @@ -1,11 +1,11 @@ -import {bundleExtension, bundleThemeExtension, parseIgnoreFile} from './bundle.js' +import {bundleExtension, bundleThemeExtension} from './bundle.js' import {testApp, testUIExtension} from '../../models/app/app.test-data.js' import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {describe, expect, test, vi} from 'vitest' import {context as esContext} from 'esbuild' import {AbortController} from '@shopify/cli-kit/node/abort' -import {glob, inTemporaryDirectory, mkdir, touchFileSync, writeFile} from '@shopify/cli-kit/node/fs' +import {glob, inTemporaryDirectory, mkdir, touchFileSync} from '@shopify/cli-kit/node/fs' import {basename, joinPath} from '@shopify/cli-kit/node/path' vi.mock('esbuild', async () => { @@ -263,20 +263,3 @@ describe('bundleExtension()', () => { }) }) }) - -describe('parseIgnoreFile()', () => { - test('returns the patterns that should be ignored', async () => { - await inTemporaryDirectory(async (tmpDir) => { - // Given - const filePath = joinPath(tmpDir, '.shopifyignore') - const content = '#foo\nbar\nbaz\n' - await writeFile(filePath, content) - - // When - const patterns = await parseIgnoreFile(filePath) - - // Then - expect(patterns).toEqual(['bar', 'baz']) - }) - }) -}) diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index 45311d96a6..7d9dd86f04 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -1,12 +1,12 @@ import {ExtensionBuildOptions} from '../build/extension.js' import {ExtensionInstance} from '../../models/extensions/extension-instance.js' +import {themeExtensionFiles} from '../../utilities/extensions/theme.js' import {context as esContext, BuildResult, formatMessagesSync} from 'esbuild' import {AbortSignal} from '@shopify/cli-kit/node/abort' -import {copyFile, glob, fileExistsSync, createFileReadStream} from '@shopify/cli-kit/node/fs' +import {copyFile} from '@shopify/cli-kit/node/fs' import {joinPath, relativePath} from '@shopify/cli-kit/node/path' import {Writable} from 'stream' import {createRequire} from 'module' -import {createInterface} from 'readline' import type {StdinOptions, build as esBuild} from 'esbuild' const require = createRequire(import.meta.url) @@ -70,28 +70,13 @@ export async function bundleThemeExtension( options: ExtensionBuildOptions, ): Promise { options.stdout.write(`Bundling theme extension ${extension.localIdentifier}...`) - const filepath = joinPath(extension.directory, '.shopifyignore') - const ignore = fileExistsSync(filepath) ? await parseIgnoreFile(filepath) : [] - const files = await glob('**/*', { - absolute: true, - cwd: extension.directory, - ignore, - }) + const files = await themeExtensionFiles(extension) await Promise.all( files.map(function (filepath) { - if ( - !( - filepath.includes('.gitkeep') || - filepath.includes('.toml') || - filepath.includes('.DS_Store') || - filepath.includes('.shopifyignore') - ) - ) { - const relativePathName = relativePath(extension.directory, filepath) - const outputFile = joinPath(extension.outputPath, relativePathName) - return copyFile(filepath, outputFile) - } + const relativePathName = relativePath(extension.directory, filepath) + const outputFile = joinPath(extension.outputPath, relativePathName) + return copyFile(filepath, outputFile) }), ) } @@ -186,34 +171,3 @@ function isGraphqlPackageAvailable(): boolean { return false } } - -/** - * Parses the ignore file and returns the patterns that should be ignored. - * @param filepath - Filepath to the ignore file. - * @returns A promise that resolves with the patterns that should be ignored. - */ -export function parseIgnoreFile(filepath: string): Promise { - return new Promise((resolve, reject) => { - const patterns: string[] = [] - - const readLineInterface = createInterface({ - input: createFileReadStream(filepath), - crlfDelay: Infinity, - }) - - readLineInterface.on('line', (line: string) => { - const trimmedLine = line.trim() - if (trimmedLine.length > 0 && !trimmedLine.startsWith('#')) { - patterns.push(trimmedLine) - } - }) - - readLineInterface.on('close', () => { - resolve(patterns) - }) - - readLineInterface.on('error', (error: Error) => { - reject(error) - }) - }) -} diff --git a/packages/app/src/cli/utilities/extensions/theme.test.ts b/packages/app/src/cli/utilities/extensions/theme.test.ts index 1f2da1d413..f83969b3f8 100644 --- a/packages/app/src/cli/utilities/extensions/theme.test.ts +++ b/packages/app/src/cli/utilities/extensions/theme.test.ts @@ -1,4 +1,4 @@ -import {themeExtensionFiles} from './theme.js' +import {themeExtensionFiles, parseIgnoreFile} from './theme.js' import {ExtensionInstance} from '../../models/extensions/extension-instance.js' import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' import {inTemporaryDirectory, writeFile, mkdir} from '@shopify/cli-kit/node/fs' @@ -38,3 +38,20 @@ describe('themeExtensionConfig', () => { }) }) }) + +describe('parseIgnoreFile()', () => { + test('returns the patterns that should be ignored', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const filePath = joinPath(tmpDir, '.shopifyignore') + const content = '#foo\nbar\nbaz\n' + await writeFile(filePath, content) + + // When + const patterns = await parseIgnoreFile(filePath) + + // Then + expect(patterns).toEqual(['bar', 'baz']) + }) + }) +}) diff --git a/packages/app/src/cli/utilities/extensions/theme.ts b/packages/app/src/cli/utilities/extensions/theme.ts index 615a0c1d78..82a17df9f4 100644 --- a/packages/app/src/cli/utilities/extensions/theme.ts +++ b/packages/app/src/cli/utilities/extensions/theme.ts @@ -1,6 +1,7 @@ import {ExtensionInstance} from '../../models/extensions/extension-instance.js' -import {glob} from '@shopify/cli-kit/node/fs' +import {glob, createFileReadStream, fileExistsSync} from '@shopify/cli-kit/node/fs' import {joinPath} from '@shopify/cli-kit/node/path' +import {createInterface} from 'readline' const ignoredFilePatterns = [ '.git', @@ -17,10 +18,54 @@ const ignoredFilePatterns = [ 'config.yml', 'node_modules', '.gitkeep', + '.shopifyignore', + '*.toml', ] export async function themeExtensionFiles(themeExtension: ExtensionInstance): Promise { - return glob(joinPath(themeExtension.directory, '*/*'), { - ignore: ignoredFilePatterns.map((pattern) => joinPath(themeExtension.directory, '*', pattern)), + const filename = '.shopifyignore' + const filepath = joinPath(themeExtension.directory, filename) + const ignore = ignoredFilePatterns.map((pattern) => joinPath('*', pattern)) + + if (fileExistsSync(filepath)) { + const patterns = await parseIgnoreFile(filepath) + ignore.push(...patterns) + } + + return glob('*/*', { + absolute: true, + cwd: themeExtension.directory, + ignore, + }) +} + +/** + * Parses the ignore file and returns the patterns that should be ignored. + * @param filepath - Filepath to the ignore file. + * @returns A promise that resolves with the patterns that should be ignored. + */ +export function parseIgnoreFile(filepath: string): Promise { + return new Promise((resolve, reject) => { + const patterns: string[] = [] + + const readLineInterface = createInterface({ + input: createFileReadStream(filepath), + crlfDelay: Infinity, + }) + + readLineInterface.on('line', (line: string) => { + const trimmedLine = line.trim() + if (trimmedLine.length > 0 && !trimmedLine.startsWith('#')) { + patterns.push(trimmedLine) + } + }) + + readLineInterface.on('close', () => { + resolve(patterns) + }) + + readLineInterface.on('error', (error: Error) => { + reject(error) + }) }) } From 7687577ffd9c09fd5c944e67aa3f5ce8dcafc390 Mon Sep 17 00:00:00 2001 From: Miguel Montalvo Date: Wed, 14 Jun 2023 00:35:43 -0700 Subject: [PATCH 31/47] Prevent files matching default regexes from being watched --- .../cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb | 2 +- .../lib/shopify_cli/theme/extension/dev_server/watcher.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb index 9c8d1bb125..2122ed7824 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb @@ -116,7 +116,7 @@ def fetch_theme_app_extension_info end def watcher - @watcher ||= Watcher.new(ctx, syncer: syncer, extension: extension, poll: poll) + @watcher ||= Watcher.new(ctx, syncer: syncer, extension: extension, poll: poll, ignore_filter: ignore_filter) end def param_builder diff --git a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/watcher.rb b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/watcher.rb index 55c21bdcfc..52b807a0b1 100644 --- a/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/watcher.rb +++ b/packages/cli-kit/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server/watcher.rb @@ -14,11 +14,13 @@ class Watcher def_delegators :@listener, :add_observer, :changed, :notify_observers - def initialize(ctx, extension:, syncer:, poll: false) + def initialize(ctx, extension:, syncer:, poll: false, ignore_filter: nil) @ctx = ctx @extension = extension @syncer = syncer - @listener = FileSystemListener.new(root: @extension.root.to_s, force_poll: poll, ignore_regex: nil) + @ignore_filter = ignore_filter + @listener = FileSystemListener.new(root: @extension.root.to_s, force_poll: poll, + ignore_regex: @ignore_filter&.regexes) add_observer(self, :notify_updates) end From ef2ab878fee3af031bb82462b728aee0d1efd1fa Mon Sep 17 00:00:00 2001 From: Madhav Makkena Date: Tue, 30 May 2023 13:31:05 -0400 Subject: [PATCH 32/47] updated app generate schema to output to either to schema.graphql or stdout based on --stdout flag --- packages/app/oclif.manifest.json | 14 ++++++++++++++ .../app/src/cli/commands/app/function/schema.ts | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/app/oclif.manifest.json b/packages/app/oclif.manifest.json index 2b826bf743..f64e0399f3 100644 --- a/packages/app/oclif.manifest.json +++ b/packages/app/oclif.manifest.json @@ -773,6 +773,13 @@ "description": "The API key to fetch the schema with.", "required": false, "multiple": false + }, + "stdout": { + "name": "stdout", + "type": "boolean", + "description": "Output the schema to stdout instead of writing to a file.", + "required": false, + "allowNo": false } }, "args": {} @@ -949,6 +956,13 @@ "description": "The API key to fetch the schema with.", "required": false, "multiple": false + }, + "stdout": { + "name": "stdout", + "type": "boolean", + "description": "Output the schema to stdout instead of writing to a file.", + "required": false, + "allowNo": false } }, "args": {} diff --git a/packages/app/src/cli/commands/app/function/schema.ts b/packages/app/src/cli/commands/app/function/schema.ts index 9815c5c13b..35865223b2 100644 --- a/packages/app/src/cli/commands/app/function/schema.ts +++ b/packages/app/src/cli/commands/app/function/schema.ts @@ -4,6 +4,8 @@ import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import Command from '@shopify/cli-kit/node/base-command' import {outputInfo} from '@shopify/cli-kit/node/output' +import {writeFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' export default class FetchSchema extends Command { static description = 'Fetch the latest GraphQL schema for a Function.' @@ -17,12 +19,23 @@ export default class FetchSchema extends Command { required: false, env: 'SHOPIFY_FLAG_APP_API_KEY', }), + stdout: Flags.boolean({ + description: 'Output the schema to stdout instead of writing to a file.', + required: false, + default: false, + env: 'SHOPIFY_FLAG_STDOUT', + }), } public async run(): Promise { const {flags, args} = await this.parse(FetchSchema) await inFunctionContext(this.config, flags.path, async (app, ourFunction) => { - outputInfo(await generateSchemaService({app, extension: ourFunction, apiKey: flags['api-key']})) + const outputSchema = await generateSchemaService({app, extension: ourFunction, apiKey: flags['api-key']}) + if (flags.stdout) { + outputInfo(outputSchema) + } else { + await writeFile(joinPath(flags.path, 'schema.graphql'), outputSchema) + } }) } } From b128f6e1211af7c27a38253e5344c68364a6df9a Mon Sep 17 00:00:00 2001 From: Madhav Makkena Date: Thu, 1 Jun 2023 13:39:07 -0400 Subject: [PATCH 33/47] Added test for schema.ts --- .../cli/commands/app/function/schema.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/app/src/cli/commands/app/function/schema.test.ts diff --git a/packages/app/src/cli/commands/app/function/schema.test.ts b/packages/app/src/cli/commands/app/function/schema.test.ts new file mode 100644 index 0000000000..650d1a3862 --- /dev/null +++ b/packages/app/src/cli/commands/app/function/schema.test.ts @@ -0,0 +1,59 @@ +import FetchSchema from './schema.js' +import {testApp, testFunctionExtension} from '../../../models/app/app.test-data.js' +import {generateSchemaService} from '../../../services/generate-schema.js' +import {load as loadApp} from '../../../models/app/loader.js' +import {describe, expect, test, vi} from 'vitest' +import {inTemporaryDirectory, readFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' +import * as output from '@shopify/cli-kit/node/output' + +vi.mock('../../../services/generate-schema.js') +vi.mock('../../../models/app/loader.ts') + +describe('FetchSchema', async () => { + test('Save the latest GraphQL schema to ./[extension]/schema.graphql when stdout flag is ABSENT', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const testOutput = 'my_output' + const mockExtension = await testFunctionExtension({ + dir: tmpDir, + }) + const app = testApp({allExtensions: [mockExtension]}) + const apiKey = 'api-key' + + vi.mocked(generateSchemaService).mockResolvedValue(testOutput) + vi.mocked(loadApp).mockResolvedValue(app) + + // When + await FetchSchema.run(['--api-key', apiKey, '--path', mockExtension.directory]) + + // Then + const outputFile = await readFile(joinPath(tmpDir, 'schema.graphql')) + expect(outputFile).toEqual(testOutput) + }) + }) + + test('Print the latest GraphQL schema to stdout when stdout flag is PRESENT', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const testOutput = 'my_output' + const mockOutput = vi.fn() + vi.spyOn(output, 'outputInfo').mockImplementation(mockOutput) + + const mockExtension = await testFunctionExtension({ + dir: tmpDir, + }) + const app = testApp({allExtensions: [mockExtension]}) + const apiKey = 'api-key' + + vi.mocked(generateSchemaService).mockResolvedValue(testOutput) + vi.mocked(loadApp).mockResolvedValue(app) + + // When + await FetchSchema.run(['--api-key', apiKey, '--path', mockExtension.directory, '--stdout']) + + // Then + expect(mockOutput).toHaveBeenCalledWith(testOutput) + }) + }) +}) From 5984ebca6e681e5e4443c55d8e3c07e62029e7ae Mon Sep 17 00:00:00 2001 From: Madhav Makkena Date: Mon, 5 Jun 2023 12:20:57 -0400 Subject: [PATCH 34/47] move the logic into generateSchemaService --- .../cli/commands/app/function/schema.test.ts | 59 ------------ .../src/cli/commands/app/function/schema.ts | 16 ++-- .../src/cli/services/generate-schema.test.ts | 92 ++++++++++++++++--- .../app/src/cli/services/generate-schema.ts | 13 ++- 4 files changed, 96 insertions(+), 84 deletions(-) delete mode 100644 packages/app/src/cli/commands/app/function/schema.test.ts diff --git a/packages/app/src/cli/commands/app/function/schema.test.ts b/packages/app/src/cli/commands/app/function/schema.test.ts deleted file mode 100644 index 650d1a3862..0000000000 --- a/packages/app/src/cli/commands/app/function/schema.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import FetchSchema from './schema.js' -import {testApp, testFunctionExtension} from '../../../models/app/app.test-data.js' -import {generateSchemaService} from '../../../services/generate-schema.js' -import {load as loadApp} from '../../../models/app/loader.js' -import {describe, expect, test, vi} from 'vitest' -import {inTemporaryDirectory, readFile} from '@shopify/cli-kit/node/fs' -import {joinPath} from '@shopify/cli-kit/node/path' -import * as output from '@shopify/cli-kit/node/output' - -vi.mock('../../../services/generate-schema.js') -vi.mock('../../../models/app/loader.ts') - -describe('FetchSchema', async () => { - test('Save the latest GraphQL schema to ./[extension]/schema.graphql when stdout flag is ABSENT', async () => { - await inTemporaryDirectory(async (tmpDir) => { - // Given - const testOutput = 'my_output' - const mockExtension = await testFunctionExtension({ - dir: tmpDir, - }) - const app = testApp({allExtensions: [mockExtension]}) - const apiKey = 'api-key' - - vi.mocked(generateSchemaService).mockResolvedValue(testOutput) - vi.mocked(loadApp).mockResolvedValue(app) - - // When - await FetchSchema.run(['--api-key', apiKey, '--path', mockExtension.directory]) - - // Then - const outputFile = await readFile(joinPath(tmpDir, 'schema.graphql')) - expect(outputFile).toEqual(testOutput) - }) - }) - - test('Print the latest GraphQL schema to stdout when stdout flag is PRESENT', async () => { - await inTemporaryDirectory(async (tmpDir) => { - // Given - const testOutput = 'my_output' - const mockOutput = vi.fn() - vi.spyOn(output, 'outputInfo').mockImplementation(mockOutput) - - const mockExtension = await testFunctionExtension({ - dir: tmpDir, - }) - const app = testApp({allExtensions: [mockExtension]}) - const apiKey = 'api-key' - - vi.mocked(generateSchemaService).mockResolvedValue(testOutput) - vi.mocked(loadApp).mockResolvedValue(app) - - // When - await FetchSchema.run(['--api-key', apiKey, '--path', mockExtension.directory, '--stdout']) - - // Then - expect(mockOutput).toHaveBeenCalledWith(testOutput) - }) - }) -}) diff --git a/packages/app/src/cli/commands/app/function/schema.ts b/packages/app/src/cli/commands/app/function/schema.ts index 35865223b2..02693ca4f8 100644 --- a/packages/app/src/cli/commands/app/function/schema.ts +++ b/packages/app/src/cli/commands/app/function/schema.ts @@ -3,9 +3,6 @@ import {functionFlags, inFunctionContext} from '../../../services/function/commo import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import Command from '@shopify/cli-kit/node/base-command' -import {outputInfo} from '@shopify/cli-kit/node/output' -import {writeFile} from '@shopify/cli-kit/node/fs' -import {joinPath} from '@shopify/cli-kit/node/path' export default class FetchSchema extends Command { static description = 'Fetch the latest GraphQL schema for a Function.' @@ -30,12 +27,13 @@ export default class FetchSchema extends Command { public async run(): Promise { const {flags, args} = await this.parse(FetchSchema) await inFunctionContext(this.config, flags.path, async (app, ourFunction) => { - const outputSchema = await generateSchemaService({app, extension: ourFunction, apiKey: flags['api-key']}) - if (flags.stdout) { - outputInfo(outputSchema) - } else { - await writeFile(joinPath(flags.path, 'schema.graphql'), outputSchema) - } + await generateSchemaService({ + app, + extension: ourFunction, + apiKey: flags['api-key'], + stdout: flags.stdout, + path: flags.path, + }) }) } } diff --git a/packages/app/src/cli/services/generate-schema.test.ts b/packages/app/src/cli/services/generate-schema.test.ts index 7382209d90..49b93cc5fd 100644 --- a/packages/app/src/cli/services/generate-schema.test.ts +++ b/packages/app/src/cli/services/generate-schema.test.ts @@ -8,11 +8,16 @@ import {partnersRequest} from '@shopify/cli-kit/node/api/partners' import {ensureAuthenticatedPartners} from '@shopify/cli-kit/node/session' import {isTerminalInteractive} from '@shopify/cli-kit/node/context/local' import {AbortError} from '@shopify/cli-kit/node/error' +import {inTemporaryDirectory, readFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' +import * as output from '@shopify/cli-kit/node/output' vi.mock('@shopify/cli-kit/node/api/partners') vi.mock('@shopify/cli-kit/node/session') vi.mock('@shopify/cli-kit/node/context/local') +vi.mock('../../../models/app/loader.ts') + vi.mock('../models/app/identifiers.js', async () => { const identifiers: any = await vi.importActual('../models/app/identifiers.js') return { @@ -37,17 +42,51 @@ describe('generateSchemaService', () => { request.mockImplementation(() => Promise.resolve({definition: 'schema'})) }) - test('performs GraphQL query to fetch the schema', async () => { - // Given - const app = testApp() - const extension = await testFunctionExtension() - const apiKey = 'api-key' + test('Save the latest GraphQL schema to ./[extension]/schema.graphql when stdout flag is ABSENT', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const app = testApp() + const extension = await testFunctionExtension({}) + const apiKey = 'api-key' + const path = tmpDir - // When - const result = await generateSchemaService({app, extension, apiKey}) + // When + await generateSchemaService({ + app, + extension, + apiKey, + path, + }) - // Then - expect(result).toBe('schema') + // Then + const outputFile = await readFile(joinPath(tmpDir, 'schema.graphql')) + expect(outputFile).toEqual('schema') + }) + }) + + test('Print the latest GraphQL schema to stdout when stdout flag is PRESENT', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const app = testApp() + const extension = await testFunctionExtension() + const apiKey = 'api-key' + const path = tmpDir + const stdout = true + const mockOutput = vi.fn() + vi.spyOn(output, 'outputInfo').mockImplementation(mockOutput) + + // When + await generateSchemaService({ + app, + extension, + apiKey, + path, + stdout, + }) + + // Then + expect(mockOutput).toHaveBeenCalledWith('schema') + }) }) test('aborts if a schema could not be generated', async () => { @@ -58,7 +97,12 @@ describe('generateSchemaService', () => { request.mockImplementation(() => Promise.resolve({definition: null})) // When - const result = generateSchemaService({app, extension, apiKey}) + const result = generateSchemaService({ + app, + extension, + apiKey, + path: '', + }) // Then await expect(result).rejects.toThrow(AbortError) @@ -97,7 +141,13 @@ describe('generateSchemaService', () => { } = extension // When - await generateSchemaService({app, extension, apiKey}) + await generateSchemaService({ + app, + extension, + apiKey, + path: '', + stdout: true, + }) // Then expect(request).toHaveBeenCalledWith(ApiSchemaDefinitionQuery, token, { @@ -117,7 +167,12 @@ describe('generateSchemaService', () => { } = extension // When - await generateSchemaService({app, extension}) + await generateSchemaService({ + app, + extension, + path: '', + stdout: true, + }) // Then expect(request).toHaveBeenCalledWith(ApiSchemaDefinitionQuery, token, { @@ -138,7 +193,12 @@ describe('generateSchemaService', () => { getAppIdentifiers.mockReturnValue({app: undefined}) // When - await generateSchemaService({app, extension}) + await generateSchemaService({ + app, + extension, + path: '', + stdout: true, + }) // Then expect(request).toHaveBeenCalledWith(ApiSchemaDefinitionQuery, token, { @@ -156,7 +216,11 @@ describe('generateSchemaService', () => { vi.mocked(isTerminalInteractive).mockReturnValue(false) // When - const result = generateSchemaService({app, extension}) + const result = generateSchemaService({ + app, + extension, + path: '', + }) await expect(result).rejects.toThrow() expect(request).not.toHaveBeenCalled() diff --git a/packages/app/src/cli/services/generate-schema.ts b/packages/app/src/cli/services/generate-schema.ts index 02752d5c83..d7938f61b6 100644 --- a/packages/app/src/cli/services/generate-schema.ts +++ b/packages/app/src/cli/services/generate-schema.ts @@ -12,12 +12,16 @@ import {partnersRequest} from '@shopify/cli-kit/node/api/partners' import {ensureAuthenticatedPartners} from '@shopify/cli-kit/node/session' import {isTerminalInteractive} from '@shopify/cli-kit/node/context/local' import {AbortError} from '@shopify/cli-kit/node/error' -import {outputContent} from '@shopify/cli-kit/node/output' +import {outputContent, outputInfo} from '@shopify/cli-kit/node/output' +import {writeFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' interface GenerateSchemaOptions { app: AppInterface extension: ExtensionInstance apiKey?: string + stdout?: boolean + path: string } export async function generateSchemaService(options: GenerateSchemaOptions) { @@ -25,6 +29,7 @@ export async function generateSchemaService(options: GenerateSchemaOptions) { const token = await ensureAuthenticatedPartners() const {apiVersion: version, type} = extension.configuration let apiKey = options.apiKey || getAppIdentifiers({app}).app + const stdout = options.stdout || false if (!apiKey) { if (!isTerminalInteractive()) { @@ -52,5 +57,9 @@ export async function generateSchemaService(options: GenerateSchemaOptions) { ) } - return response.definition + if (stdout) { + outputInfo(response.definition) + } else { + await writeFile(joinPath(options.path, 'schema.graphql'), response.definition) + } } From a750c9c1b5c72d69c0d113e00d1276ae1c1e750d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Wed, 14 Jun 2023 17:04:13 +0200 Subject: [PATCH 35/47] Show new error if rosetta 2 is not installed --- packages/app/src/cli/services/dev/urls.ts | 2 +- packages/cli-kit/src/public/node/plugins/tunnel.ts | 2 +- packages/plugin-cloudflare/src/tunnel.ts | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/app/src/cli/services/dev/urls.ts b/packages/app/src/cli/services/dev/urls.ts index aee7ae48a3..5deb6318f2 100644 --- a/packages/app/src/cli/services/dev/urls.ts +++ b/packages/app/src/cli/services/dev/urls.ts @@ -103,7 +103,7 @@ async function pollTunnelURL(tunnelClient: TunnelClient): Promise { const pollTunnelStatus = async () => { const result = tunnelClient.getTunnelStatus() outputDebug(`Polling tunnel status for ${tunnelClient.provider} (attempt ${retries}): ${result.status}`) - if (result.status === 'error') return reject(new BugError(result.message)) + if (result.status === 'error') return reject(new BugError(result.message, result.tryMessage)) if (result.status === 'connected') { resolve(result.url) } else { diff --git a/packages/cli-kit/src/public/node/plugins/tunnel.ts b/packages/cli-kit/src/public/node/plugins/tunnel.ts index 177c703343..045addbb55 100644 --- a/packages/cli-kit/src/public/node/plugins/tunnel.ts +++ b/packages/cli-kit/src/public/node/plugins/tunnel.ts @@ -13,7 +13,7 @@ export type TunnelStatusType = | {status: 'not-started'} | {status: 'starting'} | {status: 'connected'; url: string} - | {status: 'error'; message: string} + | {status: 'error'; message: string; tryMessage?: string} export class TunnelError extends ExtendableError { type: TunnelErrorType diff --git a/packages/plugin-cloudflare/src/tunnel.ts b/packages/plugin-cloudflare/src/tunnel.ts index cdb75a1517..c8290ff409 100644 --- a/packages/plugin-cloudflare/src/tunnel.ts +++ b/packages/plugin-cloudflare/src/tunnel.ts @@ -109,7 +109,17 @@ class TunnelClientInstance implements TunnelClient { stdout: customStdout, stderr: customStdout, signal: this.abortController.signal, - externalErrorHandler: async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + externalErrorHandler: async (error: any) => { + if (error.message.includes('Unknown system error -86')) { + // Cloudflare crashed because Rosetta 2 is not installed + this.currentStatus = { + status: 'error', + message: `Error starting cloudflared tunnel. Missing Rosetta 2.`, + tryMessage: "Install it by running 'softwareupdate --install-rosetta' and try again", + } + return + } // If already resolved, means that the CLI already received the tunnel URL. // Can't retry because the CLI is running with an invalid URL if (resolved) throw processCrashed() From 9c23a22e4aa098a7d8f79cebdb8b134835901db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Wed, 14 Jun 2023 17:07:58 +0200 Subject: [PATCH 36/47] typo --- packages/plugin-cloudflare/src/tunnel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-cloudflare/src/tunnel.ts b/packages/plugin-cloudflare/src/tunnel.ts index c8290ff409..a346198735 100644 --- a/packages/plugin-cloudflare/src/tunnel.ts +++ b/packages/plugin-cloudflare/src/tunnel.ts @@ -115,7 +115,7 @@ class TunnelClientInstance implements TunnelClient { // Cloudflare crashed because Rosetta 2 is not installed this.currentStatus = { status: 'error', - message: `Error starting cloudflared tunnel. Missing Rosetta 2.`, + message: `Error starting cloudflared tunnel: Missing Rosetta 2.`, tryMessage: "Install it by running 'softwareupdate --install-rosetta' and try again", } return From 79288e004a044f8063da8917b1a9e5f9fe20111a Mon Sep 17 00:00:00 2001 From: Madhav Makkena Date: Wed, 14 Jun 2023 12:02:04 -0400 Subject: [PATCH 37/47] Updated changeset, GenerateSchemaOptions interface --- .changeset/thin-islands-matter.md | 5 +++++ packages/app/src/cli/services/generate-schema.test.ts | 3 +++ packages/app/src/cli/services/generate-schema.ts | 8 +++++--- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .changeset/thin-islands-matter.md diff --git a/.changeset/thin-islands-matter.md b/.changeset/thin-islands-matter.md new file mode 100644 index 0000000000..dba296bf5e --- /dev/null +++ b/.changeset/thin-islands-matter.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': major +--- + +Updated app generate schema to output to either to schema.graphql (by default) or stdout with --stdout flag diff --git a/packages/app/src/cli/services/generate-schema.test.ts b/packages/app/src/cli/services/generate-schema.test.ts index 49b93cc5fd..d6b79955fa 100644 --- a/packages/app/src/cli/services/generate-schema.test.ts +++ b/packages/app/src/cli/services/generate-schema.test.ts @@ -56,6 +56,7 @@ describe('generateSchemaService', () => { extension, apiKey, path, + stdout: false, }) // Then @@ -102,6 +103,7 @@ describe('generateSchemaService', () => { extension, apiKey, path: '', + stdout: true, }) // Then @@ -220,6 +222,7 @@ describe('generateSchemaService', () => { app, extension, path: '', + stdout: true, }) await expect(result).rejects.toThrow() diff --git a/packages/app/src/cli/services/generate-schema.ts b/packages/app/src/cli/services/generate-schema.ts index d7938f61b6..87f35041b4 100644 --- a/packages/app/src/cli/services/generate-schema.ts +++ b/packages/app/src/cli/services/generate-schema.ts @@ -20,7 +20,7 @@ interface GenerateSchemaOptions { app: AppInterface extension: ExtensionInstance apiKey?: string - stdout?: boolean + stdout: boolean path: string } @@ -29,7 +29,7 @@ export async function generateSchemaService(options: GenerateSchemaOptions) { const token = await ensureAuthenticatedPartners() const {apiVersion: version, type} = extension.configuration let apiKey = options.apiKey || getAppIdentifiers({app}).app - const stdout = options.stdout || false + const stdout = options.stdout if (!apiKey) { if (!isTerminalInteractive()) { @@ -60,6 +60,8 @@ export async function generateSchemaService(options: GenerateSchemaOptions) { if (stdout) { outputInfo(response.definition) } else { - await writeFile(joinPath(options.path, 'schema.graphql'), response.definition) + const outputPath = joinPath(options.path, 'schema.graphql') + await writeFile(outputPath, response.definition) + outputInfo(`GraphQL Schema for ${extension.localIdentifier} written to ${outputPath}`) } } From 2341c1f2f17be96076171ea1db4d7c0d673c3f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Wed, 14 Jun 2023 18:04:58 +0200 Subject: [PATCH 38/47] Update cloudflared version and remove checksum validation --- .../plugin-cloudflare/scripts/postinstall.js | 57 ++++--------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/packages/plugin-cloudflare/scripts/postinstall.js b/packages/plugin-cloudflare/scripts/postinstall.js index 1e42c49b06..8d6af01b07 100644 --- a/packages/plugin-cloudflare/scripts/postinstall.js +++ b/packages/plugin-cloudflare/scripts/postinstall.js @@ -9,60 +9,30 @@ import {chmodSync, existsSync, mkdirSync, renameSync, unlinkSync, createWriteStr import fetch from 'node-fetch' import semver from 'semver' -const CLOUDFLARE_VERSION = '2023.4.2' +const CLOUDFLARE_VERSION = '2023.5.1' const CLOUDFLARE_REPO = `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARE_VERSION}/` const LINUX_URL = { - arm64: { - filename: 'cloudflared-linux-arm64', - checksum: 'e453b576d0db95e4e9b7f511bb379f6b0b0a73924da678655875c2c295b95627', - }, - arm: { - filename: 'cloudflared-linux-arm', - checksum: 'f3c4698aca3fff4f94a455cbf1f9c0e1cd81498e67d0decb73d63b6a41337f43', - }, - x64: { - filename: 'cloudflared-linux-amd64', - checksum: '7e48b3d91f44badc1b4c2bd446ef1c4ae4c824840d594bd353cf20cba5fd1cef', - }, - ia32: { - filename: 'cloudflared-linux-386', - checksum: '576955db7b44e1d997a22bb07eebb58001bd56956351142da504d80c07663153', - }, + arm64: 'cloudflared-linux-arm64', + arm: 'cloudflared-linux-arm', + x64: 'cloudflared-linux-amd64', + ia32: 'cloudflared-linux-386', } const MACOS_URL = { - arm64: { - filename: 'cloudflared-darwin-amd64.tgz', - checksum: '1154f3b2c31f4727c076c3e08024887be0e0a0b68a89e4f88f286f6f6196ac74', - }, - x64: { - filename: 'cloudflared-darwin-amd64.tgz', - checksum: '1154f3b2c31f4727c076c3e08024887be0e0a0b68a89e4f88f286f6f6196ac74', - }, + arm64: 'cloudflared-darwin-amd64.tgz', + x64: 'cloudflared-darwin-amd64.tgz', } const WINDOWS_URL = { - x64: { - filename: 'cloudflared-windows-amd64.exe', - checksum: '53f8adbd76c0eb16f5e43cadde422474d8a06f9c8f959389c1930042ad8beaa5', - }, - ia32: { - filename: 'cloudflared-windows-386.exe', - checksum: 'c2cfd23fdc6c0e1b1ffa0e545cbe556f18d11b362b4a89ba0713f6ab01c4827f', - }, + x64: 'cloudflared-windows-amd64.exe', + ia32: 'cloudflared-windows-386.exe', } const URL = { - linux: CLOUDFLARE_REPO + LINUX_URL[process.arch]?.filename, - darwin: CLOUDFLARE_REPO + MACOS_URL[process.arch]?.filename, - win32: CLOUDFLARE_REPO + WINDOWS_URL[process.arch]?.filename, -} - -const CHECKSUM = { - linux: LINUX_URL[process.arch]?.checksum, - darwin: MACOS_URL[process.arch]?.checksum, - win32: WINDOWS_URL[process.arch]?.checksum, + linux: CLOUDFLARE_REPO + LINUX_URL[process.arch], + darwin: CLOUDFLARE_REPO + MACOS_URL[process.arch], + win32: CLOUDFLARE_REPO + WINDOWS_URL[process.arch], } /** @@ -113,13 +83,11 @@ export default async function install() { async function installLinux(file, binTarget) { await downloadFile(file, binTarget) - if (sha256(binTarget) !== CHECKSUM.linux) throw new Error('Checksum mismatch') chmodSync(binTarget, '755') } async function installWindows(file, binTarget) { await downloadFile(file, binTarget) - if (sha256(binTarget) !== CHECKSUM.win32) throw new Error('Checksum mismatch') } async function installMacos(file, binTarget) { @@ -128,7 +96,6 @@ async function installMacos(file, binTarget) { execSync(`tar -xzf ${filename}`, {cwd: path.dirname(binTarget)}) unlinkSync(`${binTarget}.tgz`) renameSync(`${path.dirname(binTarget)}/cloudflared`, binTarget) - if (sha256(binTarget) !== CHECKSUM.darwin) throw new Error('Checksum mismatch') } async function downloadFile(url, to) { From 8345516911b2a8254d5bbef939b47f7576d90f14 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Thu, 15 Jun 2023 14:22:43 +0200 Subject: [PATCH 39/47] Fix link on README --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 1049bfb356..79ece2970f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ The list below contains valuable resources for people interested in contributing * [Performance](./cli/performance.md) * [Debugging](./cli/debugging.md) * [ESLint rules](./cli/eslint-rules.md) -* [Release process](./cli/release.md) +* [Release process](./release.md) * [Testing strategy](./cli/testing-strategy.md) * [Cross-OS compatibility](./cli/cross-os-compatibility.md) * [Troubleshooting](./cli/troubleshooting.md) From c6f82996c95b7d31bf1a30e384ad691e3a40bb93 Mon Sep 17 00:00:00 2001 From: Michael Rokas Date: Wed, 14 Jun 2023 14:42:14 +0000 Subject: [PATCH 40/47] Add schema and type ref to flow action --- .../extensions/specifications/flow_action.ts | 27 ++++++++++++++++++- .../flow_action/shopify.extension.toml.liquid | 2 ++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/app/src/cli/models/extensions/specifications/flow_action.ts b/packages/app/src/cli/models/extensions/specifications/flow_action.ts index bf92c187c0..a33ad09be4 100644 --- a/packages/app/src/cli/models/extensions/specifications/flow_action.ts +++ b/packages/app/src/cli/models/extensions/specifications/flow_action.ts @@ -1,5 +1,7 @@ import {BaseSchema} from '../schemas.js' import {createExtensionSpecification} from '../specification.js' +import {joinPath} from '@shopify/cli-kit/node/path' +import {glob, readFile} from '@shopify/cli-kit/node/fs' import {zod} from '@shopify/cli-kit/node/schema' @@ -13,6 +15,8 @@ const FlowActionExtensionSchema = BaseSchema.extend({ validationUrl: zod.string().optional(), customConfigurationPageUrl: zod.string().optional(), customConfigurationPagePreviewUrl: zod.string().optional(), + schema: zod.string().optional(), + return_type_ref: zod.string().optional(), fields: zod .array( zod.object({ @@ -28,6 +32,25 @@ const FlowActionExtensionSchema = BaseSchema.extend({ }), }) +/** + * Loads the schema from the partner defined file. + */ +const loadSchemaPatchFromPath = async (extensionPath: string, patchPath: string | undefined) => { + if (!patchPath) { + return '' + } + + const path = await glob(joinPath(extensionPath, patchPath)) + + if (path.length > 1) { + throw new Error('Multiple files found for schema patch path') + } else if (path.length === 0) { + return '' + } + + return readFile(path[0] as string) +} + /** * Extension specification with all properties and methods needed to load a Flow Action. */ @@ -36,7 +59,7 @@ const flowActionSpecification = createExtensionSpecification({ schema: FlowActionExtensionSchema, singleEntryPath: false, appModuleFeatures: (_) => [], - deployConfig: async (config, _) => { + deployConfig: async (config, extensionPath) => { return { title: config.task.title, description: config.task.description, @@ -45,6 +68,8 @@ const flowActionSpecification = createExtensionSpecification({ validation_url: config.task.validationUrl, custom_configuration_page_url: config.task.customConfigurationPageUrl, custom_configuration_page_preview_url: config.task.customConfigurationPagePreviewUrl, + return_type_ref: config.task.return_type_ref, + schema_patch: await loadSchemaPatchFromPath(extensionPath, config.task.schema), } }, }) diff --git a/packages/app/templates/extensions/projects/flow_action/shopify.extension.toml.liquid b/packages/app/templates/extensions/projects/flow_action/shopify.extension.toml.liquid index 6c0225d627..93fb080a39 100644 --- a/packages/app/templates/extensions/projects/flow_action/shopify.extension.toml.liquid +++ b/packages/app/templates/extensions/projects/flow_action/shopify.extension.toml.liquid @@ -8,6 +8,8 @@ url = "https://runtime-endpoint.com" validation_url = "https://validation-url" custom_configuration_page_url = "https://custom-configuration-page-url" custom_configuration_page_preview_url = "https://custom-configuration-page-preview-url" +return_type_ref = "SomeTypeFromSchema" +schema = "./schema.graphql" [[task.fields]] name = "customer_id" From aada657ab73d04b853ada5196b593e1f26dcfff7 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Thu, 15 Jun 2023 20:07:19 +0100 Subject: [PATCH 41/47] Add an escape hatch to switch off deduplication --- packages/app/src/cli/constants.ts | 4 ++ .../app/src/cli/services/extensions/bundle.ts | 38 ++++++++++++------- packages/cli-kit/package.json | 2 +- .../src/private/node/context/utilities.ts | 10 ----- .../cli-kit/src/private/node/demo-recorder.ts | 2 +- .../cli-kit/src/private/node/testing/ui.ts | 2 +- .../cli-kit/src/public/node/base-command.ts | 2 +- packages/cli-kit/src/public/node/cli.ts | 2 +- .../cli-kit/src/public/node/context/local.ts | 3 +- .../cli-kit/src/public/node/context/spin.ts | 2 +- .../src/public/node/context/utilities.ts | 12 ++++++ packages/cli-kit/src/public/node/output.ts | 2 +- packages/cli-kit/src/public/node/ruby.ts | 2 +- 13 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 packages/cli-kit/src/public/node/context/utilities.ts diff --git a/packages/app/src/cli/constants.ts b/packages/app/src/cli/constants.ts index 04161a6462..225b4ab9cc 100644 --- a/packages/app/src/cli/constants.ts +++ b/packages/app/src/cli/constants.ts @@ -1,5 +1,9 @@ import {ExtensionFlavor} from './models/app/extensions.js' +export const environmentVariableNames = { + skipEsbuildReactDedeuplication: 'SHOPIFY_CLI_SKIP_ESBUILD_REACT_DEDUPLICATION', +} + export const configurationFileNames = { app: 'shopify.app.toml', extension: { diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index 9a7a85fc80..dae3a72fe3 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -1,13 +1,16 @@ import {buildThemeExtensions, ThemeExtensionBuildOptions} from '../build/extension.js' +import {environmentVariableNames} from '../../constants.js' import {context as esContext, BuildResult, formatMessagesSync} from 'esbuild' import {AbortSignal} from '@shopify/cli-kit/node/abort' import {copyFile, glob} from '@shopify/cli-kit/node/fs' import {joinPath, relativePath} from '@shopify/cli-kit/node/path' import {useThemebundling} from '@shopify/cli-kit/node/context/local' +import {outputDebug} from '@shopify/cli-kit/node/output' +import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment' +import {isTruthy} from '@shopify/cli-kit/node/context/utilities' import {Writable} from 'stream' import {createRequire} from 'module' import type {StdinOptions, build as esBuild, Plugin} from 'esbuild' -import {outputDebug} from '@shopify/cli-kit/node/output.js' const require = createRequire(import.meta.url) @@ -161,34 +164,41 @@ function getPlugins(resolveDir: string | undefined): ESBuildPlugins { plugins.push(graphqlLoader()) } - if (resolveDir) { + const skipReactDeduplication = isTruthy( + getEnvironmentVariables()[environmentVariableNames.skipEsbuildReactDedeuplication], + ) + if (resolveDir && !skipReactDeduplication) { let resolvedReactPath: string | undefined try { resolvedReactPath = require.resolve('react', {paths: [resolveDir]}) + // eslint-disable-next-line no-catch-all/no-catch-all } catch { - // If weren't able to find React, that's fine. Extension may not be using it! + // If weren't able to find React, that's fine. It might not be used. outputDebug(`Unable to load React in ${resolveDir}, skipping React de-duplication`) } if (resolvedReactPath) { outputDebug(`Deduplicating React dependency for ${resolveDir}, using ${resolvedReactPath}`) - const DeduplicateReactPlugin: Plugin = { - name: 'dedup-react', - setup({onResolve}) { - onResolve({filter: /^react$/}, (args) => { - return { - path: resolvedReactPath, - } - }) - }, - } - plugins.push(DeduplicateReactPlugin) + plugins.push(deduplicateReactPlugin(resolvedReactPath)) } } return plugins } +function deduplicateReactPlugin(resolvedReactPath: string): Plugin { + return { + name: 'shopify:deduplicate-react', + setup({onResolve}) { + onResolve({filter: /^react$/}, (args) => { + return { + path: resolvedReactPath, + } + }) + }, + } +} + /** * Returns true if the "graphql" and "graphql-tag" packages can be * resolved. This information is used to determine whether we should diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 9ce8e8e21c..392f1615e8 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -86,7 +86,7 @@ ], "static": [ "@oclif/core", - "../../private/node/context/utilities.js", + "../../public/node/context/utilities.js", "../../private/node/demo-recorder.js" ] } diff --git a/packages/cli-kit/src/private/node/context/utilities.ts b/packages/cli-kit/src/private/node/context/utilities.ts index 065df0a0b6..8befdb772c 100644 --- a/packages/cli-kit/src/private/node/context/utilities.ts +++ b/packages/cli-kit/src/private/node/context/utilities.ts @@ -1,13 +1,3 @@ -/** - * Returns whether an environment variable value represents a truthy value. - */ -export function isTruthy(variable: string | undefined): boolean { - if (!variable) { - return false - } - return ['1', 'true', 'TRUE', 'yes', 'YES'].includes(variable) -} - /** * Returns whether an environment variable has been set and is non-empty */ diff --git a/packages/cli-kit/src/private/node/demo-recorder.ts b/packages/cli-kit/src/private/node/demo-recorder.ts index da7a8d9626..12515dc67d 100644 --- a/packages/cli-kit/src/private/node/demo-recorder.ts +++ b/packages/cli-kit/src/private/node/demo-recorder.ts @@ -1,5 +1,5 @@ -import {isTruthy} from './context/utilities.js' import {ConcurrentOutputProps} from './ui/components/ConcurrentOutput.js' +import {isTruthy} from '../../public/node/context/utilities.js' interface Event { type: string diff --git a/packages/cli-kit/src/private/node/testing/ui.ts b/packages/cli-kit/src/private/node/testing/ui.ts index 56551926ca..8f86ef5a94 100644 --- a/packages/cli-kit/src/private/node/testing/ui.ts +++ b/packages/cli-kit/src/private/node/testing/ui.ts @@ -1,4 +1,4 @@ -import {isTruthy} from '../context/utilities.js' +import {isTruthy} from '../../../public/node/context/utilities.js' import {Stdout} from '../ui.js' import {ReactElement} from 'react' import {render as inkRender} from 'ink' diff --git a/packages/cli-kit/src/public/node/base-command.ts b/packages/cli-kit/src/public/node/base-command.ts index a55a8b01f8..a2023381b6 100644 --- a/packages/cli-kit/src/public/node/base-command.ts +++ b/packages/cli-kit/src/public/node/base-command.ts @@ -7,7 +7,7 @@ import {renderInfo} from './ui.js' import {JsonMap} from '../../private/common/json.js' import {outputContent, outputInfo, outputToken} from '../../public/node/output.js' import {hashString} from '../../public/node/crypto.js' -import {isTruthy} from '../../private/node/context/utilities.js' +import {isTruthy} from '../../public/node/context/utilities.js' import {Command} from '@oclif/core' import {FlagOutput, Input, ParserOutput, FlagInput, ArgOutput} from '@oclif/core/lib/interfaces/parser.js' diff --git a/packages/cli-kit/src/public/node/cli.ts b/packages/cli-kit/src/public/node/cli.ts index d7fb69ed36..e400fe79c5 100644 --- a/packages/cli-kit/src/public/node/cli.ts +++ b/packages/cli-kit/src/public/node/cli.ts @@ -1,4 +1,4 @@ -import {isTruthy} from '../../private/node/context/utilities.js' +import {isTruthy} from '../../public/node/context/utilities.js' import {printEventsJson} from '../../private/node/demo-recorder.js' import {Flags} from '@oclif/core' diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index ee2dd38686..1b3305d02e 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -1,5 +1,6 @@ import {isSpin} from './spin.js' -import {getCIMetadata, isTruthy, isSet, Metadata} from '../../../private/node/context/utilities.js' +import {getCIMetadata, isSet, Metadata} from '../../../private/node/context/utilities.js' +import {isTruthy} from '../../../public/node/context/utilities.js' import {environmentVariables, pathConstants} from '../../../private/node/constants.js' import {fileExists} from '../fs.js' import {exec} from '../system.js' diff --git a/packages/cli-kit/src/public/node/context/spin.ts b/packages/cli-kit/src/public/node/context/spin.ts index 1842ad1e07..03168decef 100644 --- a/packages/cli-kit/src/public/node/context/spin.ts +++ b/packages/cli-kit/src/public/node/context/spin.ts @@ -1,5 +1,5 @@ import {fileExists, readFileSync} from '../fs.js' -import {isTruthy} from '../../../private/node/context/utilities.js' +import {isTruthy} from '../../../public/node/context/utilities.js' import {environmentVariables} from '../../../private/node/constants.js' import {captureOutput} from '../system.js' import {outputContent, outputToken} from '../../../public/node/output.js' diff --git a/packages/cli-kit/src/public/node/context/utilities.ts b/packages/cli-kit/src/public/node/context/utilities.ts new file mode 100644 index 0000000000..79c847b67a --- /dev/null +++ b/packages/cli-kit/src/public/node/context/utilities.ts @@ -0,0 +1,12 @@ +/** + * Returns whether an environment variable value represents a truthy value. + * + * @param variable - Environment variable value to check. + * @returns True when the value is truthy, e.g. '1', 'true', etc. + */ +export function isTruthy(variable: string | undefined): boolean { + if (!variable) { + return false + } + return ['1', 'true', 'TRUE', 'yes', 'YES'].includes(variable) +} diff --git a/packages/cli-kit/src/public/node/output.ts b/packages/cli-kit/src/public/node/output.ts index 895dc855aa..a38d474d44 100644 --- a/packages/cli-kit/src/public/node/output.ts +++ b/packages/cli-kit/src/public/node/output.ts @@ -3,7 +3,7 @@ import {isUnitTest, isVerbose} from './context/local.js' import {PackageManager} from './node-package-manager.js' import {AbortSignal} from './abort.js' import colors from './colors.js' -import {isTruthy} from '../../private/node/context/utilities.js' +import {isTruthy} from '../../public/node/context/utilities.js' import { ColorContentToken, CommandContentToken, diff --git a/packages/cli-kit/src/public/node/ruby.ts b/packages/cli-kit/src/public/node/ruby.ts index 0bfd4f1aac..717eba0d60 100644 --- a/packages/cli-kit/src/public/node/ruby.ts +++ b/packages/cli-kit/src/public/node/ruby.ts @@ -10,7 +10,7 @@ import {firstPartyDev} from './context/local.js' import {pathConstants} from '../../private/node/constants.js' import {AdminSession} from '../../public/node/session.js' import {outputContent, outputToken} from '../../public/node/output.js' -import {isTruthy} from '../../private/node/context/utilities.js' +import {isTruthy} from '../../public/node/context/utilities.js' import {coerceSemverVersion} from '../../private/node/semver.js' import {CLI_KIT_VERSION} from '../common/version.js' import envPaths from 'env-paths' From a80e0dfb1bd09fa98c47587545f686bcdbc90b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isaac=20Rold=C3=A1n?= Date: Fri, 16 Jun 2023 09:59:38 +0200 Subject: [PATCH 42/47] change major to minor --- .changeset/thin-islands-matter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/thin-islands-matter.md b/.changeset/thin-islands-matter.md index dba296bf5e..2ae86c441c 100644 --- a/.changeset/thin-islands-matter.md +++ b/.changeset/thin-islands-matter.md @@ -1,5 +1,5 @@ --- -'@shopify/cli': major +'@shopify/cli': minor --- Updated app generate schema to output to either to schema.graphql (by default) or stdout with --stdout flag From e217b34ebedaed644c98e40b4aaacbae7aabc1d2 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Fri, 16 Jun 2023 09:14:50 +0100 Subject: [PATCH 43/47] Add changeset --- .changeset/wicked-pants-pull.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/wicked-pants-pull.md diff --git a/.changeset/wicked-pants-pull.md b/.changeset/wicked-pants-pull.md new file mode 100644 index 0000000000..824b5376b5 --- /dev/null +++ b/.changeset/wicked-pants-pull.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli-kit': patch +'@shopify/app': patch +--- + +Add React deduplication plugin for ESBuild & extensions From 13f6ede31d0a5f32c23b120abfc2beafc27d71c7 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Fri, 16 Jun 2023 11:15:19 +0100 Subject: [PATCH 44/47] Bundle tests check for the deduplication plugin --- .../cli/services/extensions/bundle.test.ts | 62 +++++++++++++++++++ .../app/src/cli/services/extensions/bundle.ts | 16 +++-- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/packages/app/src/cli/services/extensions/bundle.test.ts b/packages/app/src/cli/services/extensions/bundle.test.ts index 892cd57706..5c9788c746 100644 --- a/packages/app/src/cli/services/extensions/bundle.test.ts +++ b/packages/app/src/cli/services/extensions/bundle.test.ts @@ -104,6 +104,68 @@ describe('bundleExtension()', () => { `) const plugins = options.plugins?.map(({name}) => name) expect(plugins).toContain('graphql-loader') + expect(plugins).toContain('shopify:deduplicate-react') + }) + + test('can switch off React deduplication', async () => { + // Given + const extension = await testUIExtension() + const stdout: any = { + write: vi.fn(), + } + const stderr: any = { + write: vi.fn(), + } + const app = testApp({ + directory: '/project', + dotenv: { + path: '/project/.env', + variables: { + FOO: 'BAR', + }, + }, + allExtensions: [extension], + }) + const esbuildWatch = vi.fn() + const esbuildDispose = vi.fn() + const esbuildRebuild = vi.fn(esbuildResultFixture) + + vi.mocked(esContext).mockResolvedValue({ + rebuild: esbuildRebuild, + watch: esbuildWatch, + dispose: esbuildDispose, + cancel: vi.fn(), + serve: vi.fn(), + }) + + // When + await bundleExtension( + { + env: app.dotenv?.variables ?? {}, + outputPath: extension.outputPath, + minify: true, + environment: 'production', + stdin: { + contents: 'console.log("mock stdin content")', + resolveDir: 'mock/resolve/dir', + loader: 'tsx', + }, + stdout, + stderr, + }, + { + ...process.env, + SHOPIFY_CLI_SKIP_ESBUILD_REACT_DEDUPLICATION: 'true', + }, + ) + + // Then + const call = vi.mocked(esContext).mock.calls[0]! + expect(call).not.toBeUndefined() + const options = call[0] + + const plugins = options.plugins?.map(({name}) => name) + expect(plugins).not.toContain('shopify:deduplicate-react') }) test('stops the ESBuild when the abort signal receives an event', async () => { diff --git a/packages/app/src/cli/services/extensions/bundle.ts b/packages/app/src/cli/services/extensions/bundle.ts index b00fc009b2..d52fd02efa 100644 --- a/packages/app/src/cli/services/extensions/bundle.ts +++ b/packages/app/src/cli/services/extensions/bundle.ts @@ -7,7 +7,6 @@ import {AbortSignal} from '@shopify/cli-kit/node/abort' import {copyFile} from '@shopify/cli-kit/node/fs' import {joinPath, relativePath} from '@shopify/cli-kit/node/path' import {outputDebug} from '@shopify/cli-kit/node/output' -import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment' import {isTruthy} from '@shopify/cli-kit/node/context/utilities' import {Writable} from 'stream' import {createRequire} from 'module' @@ -50,9 +49,10 @@ export interface BundleOptions { /** * Invokes ESBuild with the given options to bundle an extension. * @param options - ESBuild options + * @param processEnv - Environment variables for the running process (not those from .env) */ -export async function bundleExtension(options: BundleOptions) { - const esbuildOptions = getESBuildOptions(options) +export async function bundleExtension(options: BundleOptions, processEnv = process.env) { + const esbuildOptions = getESBuildOptions(options, processEnv) const context = await esContext(esbuildOptions) if (options.watch) { await context.watch() @@ -102,7 +102,7 @@ function onResult(result: Awaited> | null, options: B } } -function getESBuildOptions(options: BundleOptions): Parameters[0] { +function getESBuildOptions(options: BundleOptions, processEnv = process.env): Parameters[0] { const env: {[variable: string]: string} = options.env const define = Object.keys(env || {}).reduce( (acc, key) => ({ @@ -123,7 +123,7 @@ function getESBuildOptions(options: BundleOptions): Parameters }, legalComments: 'none', minify: options.minify, - plugins: getPlugins(options.stdin.resolveDir), + plugins: getPlugins(options.stdin.resolveDir, processEnv), target: 'es6', resolveExtensions: ['.tsx', '.ts', '.js', '.json', '.esnext', '.mjs', '.ejs'], } @@ -148,7 +148,7 @@ type ESBuildPlugins = Parameters[0]['plugins'] * It returns the plugins that should be used with ESBuild. * @returns List of plugins. */ -function getPlugins(resolveDir: string | undefined): ESBuildPlugins { +function getPlugins(resolveDir: string | undefined, processEnv = process.env): ESBuildPlugins { const plugins = [] if (isGraphqlPackageAvailable()) { @@ -156,9 +156,7 @@ function getPlugins(resolveDir: string | undefined): ESBuildPlugins { plugins.push(graphqlLoader()) } - const skipReactDeduplication = isTruthy( - getEnvironmentVariables()[environmentVariableNames.skipEsbuildReactDedeuplication], - ) + const skipReactDeduplication = isTruthy(processEnv[environmentVariableNames.skipEsbuildReactDedeuplication]) if (resolveDir && !skipReactDeduplication) { let resolvedReactPath: string | undefined try { From 433ffc1c88d0ba38d229193c741b7e89aa06ad80 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Fri, 16 Jun 2023 11:31:50 +0100 Subject: [PATCH 45/47] Avoid importing from `../public` --- packages/cli-kit/package.json | 2 +- packages/cli-kit/src/public/node/base-command.ts | 6 +++--- packages/cli-kit/src/public/node/cli.ts | 2 +- packages/cli-kit/src/public/node/context/local.ts | 2 +- packages/cli-kit/src/public/node/context/spin.ts | 4 ++-- packages/cli-kit/src/public/node/output.ts | 2 +- packages/cli-kit/src/public/node/ruby.ts | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 1cb3fae791..b19aeb681f 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -86,7 +86,7 @@ ], "static": [ "@oclif/core", - "../../public/node/context/utilities.js", + "./context/utilities.js", "../../private/node/demo-recorder.js" ] } diff --git a/packages/cli-kit/src/public/node/base-command.ts b/packages/cli-kit/src/public/node/base-command.ts index a2023381b6..864c44d951 100644 --- a/packages/cli-kit/src/public/node/base-command.ts +++ b/packages/cli-kit/src/public/node/base-command.ts @@ -4,10 +4,10 @@ import {isDevelopment} from './context/local.js' import {addPublicMetadata} from './metadata.js' import {AbortError} from './error.js' import {renderInfo} from './ui.js' +import {outputContent, outputInfo, outputToken} from './output.js' +import {hashString} from './crypto.js' +import {isTruthy} from './context/utilities.js' import {JsonMap} from '../../private/common/json.js' -import {outputContent, outputInfo, outputToken} from '../../public/node/output.js' -import {hashString} from '../../public/node/crypto.js' -import {isTruthy} from '../../public/node/context/utilities.js' import {Command} from '@oclif/core' import {FlagOutput, Input, ParserOutput, FlagInput, ArgOutput} from '@oclif/core/lib/interfaces/parser.js' diff --git a/packages/cli-kit/src/public/node/cli.ts b/packages/cli-kit/src/public/node/cli.ts index e400fe79c5..d45ebc29b4 100644 --- a/packages/cli-kit/src/public/node/cli.ts +++ b/packages/cli-kit/src/public/node/cli.ts @@ -1,4 +1,4 @@ -import {isTruthy} from '../../public/node/context/utilities.js' +import {isTruthy} from './context/utilities.js' import {printEventsJson} from '../../private/node/demo-recorder.js' import {Flags} from '@oclif/core' diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index 549d1868a6..0a14bed536 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -1,6 +1,6 @@ import {isSpin} from './spin.js' +import {isTruthy} from './utilities.js' import {getCIMetadata, isSet, Metadata} from '../../../private/node/context/utilities.js' -import {isTruthy} from '../../../public/node/context/utilities.js' import {environmentVariables, pathConstants} from '../../../private/node/constants.js' import {fileExists} from '../fs.js' import {exec} from '../system.js' diff --git a/packages/cli-kit/src/public/node/context/spin.ts b/packages/cli-kit/src/public/node/context/spin.ts index 03168decef..e2a09d6802 100644 --- a/packages/cli-kit/src/public/node/context/spin.ts +++ b/packages/cli-kit/src/public/node/context/spin.ts @@ -1,8 +1,8 @@ +import {isTruthy} from './utilities.js' import {fileExists, readFileSync} from '../fs.js' -import {isTruthy} from '../../../public/node/context/utilities.js' import {environmentVariables} from '../../../private/node/constants.js' import {captureOutput} from '../system.js' -import {outputContent, outputToken} from '../../../public/node/output.js' +import {outputContent, outputToken} from '../output.js' import {getCachedSpinFqdn, setCachedSpinFqdn} from '../../../private/node/context/spin-cache.js' import {AbortError} from '../error.js' import {Environment, serviceEnvironment} from '../../../private/node/context/service.js' diff --git a/packages/cli-kit/src/public/node/output.ts b/packages/cli-kit/src/public/node/output.ts index a38d474d44..cd174a43b8 100644 --- a/packages/cli-kit/src/public/node/output.ts +++ b/packages/cli-kit/src/public/node/output.ts @@ -3,7 +3,7 @@ import {isUnitTest, isVerbose} from './context/local.js' import {PackageManager} from './node-package-manager.js' import {AbortSignal} from './abort.js' import colors from './colors.js' -import {isTruthy} from '../../public/node/context/utilities.js' +import {isTruthy} from './context/utilities.js' import { ColorContentToken, CommandContentToken, diff --git a/packages/cli-kit/src/public/node/ruby.ts b/packages/cli-kit/src/public/node/ruby.ts index 6aa020e8ba..792c91e2e9 100644 --- a/packages/cli-kit/src/public/node/ruby.ts +++ b/packages/cli-kit/src/public/node/ruby.ts @@ -7,9 +7,9 @@ import {AbortError, AbortSilentError} from './error.js' import {getEnvironmentVariables} from './environment.js' import {isSpinEnvironment, spinFqdn} from './context/spin.js' import {firstPartyDev, useEmbeddedThemeCLI} from './context/local.js' +import {outputContent, outputToken} from './output.js' +import {isTruthy} from './context/utilities.js' import {pathConstants} from '../../private/node/constants.js' -import {outputContent, outputToken} from '../../public/node/output.js' -import {isTruthy} from '../../public/node/context/utilities.js' import {coerceSemverVersion} from '../../private/node/semver.js' import {CLI_KIT_VERSION} from '../common/version.js' import envPaths from 'env-paths' From 99e5c9ff9763a376481fb16db3ab634c644b8fbc Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Fri, 16 Jun 2023 13:12:16 +0100 Subject: [PATCH 46/47] Deactivating test on Windows --- .../src/private/node/ui/components/SelectInput.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx index fb9a52d025..1a1c5dc78a 100644 --- a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx @@ -2,6 +2,7 @@ import {SelectInput} from './SelectInput.js' import {sendInputAndWait, sendInputAndWaitForChange, waitForInputsToBeReady, render} from '../../testing/ui.js' import {describe, expect, test, vi} from 'vitest' import React from 'react' +import { platformAndArch } from '@shopify/cli-kit/node/os.js' const ARROW_UP = '\u001B[A' const ARROW_DOWN = '\u001B[B' @@ -208,7 +209,9 @@ describe('SelectInput', async () => { expect(onChange).toHaveBeenCalledWith(items[2]) }) - test('support groups', async () => { + const runningOnWindows = platformAndArch().platform === 'windows' + + test.skipIf(runningOnWindows)('support groups', async () => { const onChange = vi.fn() const items = [ From 3711816d087846fa607c87a8bb968cdb07188748 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Fri, 16 Jun 2023 13:14:16 +0100 Subject: [PATCH 47/47] Prefer relative import --- .../cli-kit/src/private/node/ui/components/SelectInput.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx index 1a1c5dc78a..2b8ccba384 100644 --- a/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx +++ b/packages/cli-kit/src/private/node/ui/components/SelectInput.test.tsx @@ -1,8 +1,8 @@ import {SelectInput} from './SelectInput.js' import {sendInputAndWait, sendInputAndWaitForChange, waitForInputsToBeReady, render} from '../../testing/ui.js' +import {platformAndArch} from '../../../../public/node/os.js' import {describe, expect, test, vi} from 'vitest' import React from 'react' -import { platformAndArch } from '@shopify/cli-kit/node/os.js' const ARROW_UP = '\u001B[A' const ARROW_DOWN = '\u001B[B'