diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..9e9897278 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,69 @@ +name: Deploy +on: + push: + branches: + - prod + - dev + +jobs: + test: + name: Deploy + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - run: pwd + working-directory: ${{ runner.home }} + + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + + - name: Prepare deployment package + run: pnpm turbo:prep && pnpm deploy --filter "@custom/website" ../deploy --prod + env: + VITE_DECAP_REPO: ${{ github.repository }} + VITE_DECAP_BRANCH: ${{ steps.extract_branch.outputs.branch }} + + - name: Build + run: pnpm run --filter @custom/website build + working-directory: ../deploy + env: + CLOUDINARY_API_KEY: ${{ vars.CLOUDINARY_API_KEY }} + CLOUDINARY_API_SECRET: ${{ secrets.CLOUDINARY_API_SECRET }} + CLOUDINARY_CLOUDNAME: ${{ secrets.CLOUDINARY_CLOUDNAME }} + GATSBY_PUBLIC_URL: ${{ vars.GATSBY_PUBLIC_URL }} + + - name: Check for Netlify auth token + id: netlify-check + shell: bash + run: | + if [ "${{ secrets.NETLIFY_AUTH_TOKEN }}" != '' ]; then + echo "available=true" >> $GITHUB_OUTPUT; + else + echo "available=false" >> $GITHUB_OUTPUT; + fi + + - name: Deploy to dev + run: pnpm netlify deploy --prod --filter "@custom/website" + working-directory: ../deploy + if: github.ref == 'refs/heads/dev' && steps.netlify-check.outputs.available == 'true' && vars.NETLIFY_DEV_ID != '' + env: + NETLIFY_SITE_ID: ${{ vars.NETLIFY_DEV_ID }} + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + + - name: Deploy to prod + run: pnpm netlify deploy --prod --filter "@custom/website" + working-directory: ../deploy + if: github.ref == 'refs/heads/prod' && steps.netlify-check.outputs.available == 'true' && vars.NETLIFY_PROD_ID != '' + env: + NETLIFY_SITE_ID: ${{ vars.NETLIFY_PROD_ID }} + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41f768f27..a8f39a873 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,16 @@ jobs: path: tests/e2e/playwright-report/ retention-days: 3 + - name: Check for Chromatic project token + id: chromatic-check + shell: bash + run: | + if [ "${{ secrets.CHROMATIC_PROJECT_TOKEN }}" != '' ]; then + echo "available=true" >> $GITHUB_OUTPUT; + else + echo "available=false" >> $GITHUB_OUTPUT; + fi + - name: Publish to Chromatic uses: chromaui/action@v1 with: @@ -49,6 +59,7 @@ jobs: storybookBaseDir: packages/ui onlyChanged: true exitOnceUploaded: true + if: ${{ steps.chromatic-check.outputs.available == 'true' }} - name: Deploy storybook to netlify run: diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index b4a9db06c..913f8189c 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -1,4 +1,11 @@ FROM gitpod/workspace-full + +RUN bash -c 'VERSION="18.19.0" \ + && source $HOME/.nvm/nvm.sh && nvm install $VERSION \ + && nvm use $VERSION && nvm alias default $VERSION' + +RUN echo "nvm use default &>/dev/null" >> ~/.bashrc.d/51-nvm-fix + RUN sudo update-alternatives --set php $(which php8.2) RUN sudo install-packages php8.2-gd php8.2-mbstring php8.2-curl php8.2-sqlite3 php8.2-zip php8.2-xdebug php8.2-imagick RUN pnpx playwright@1.32.3 install-deps @@ -11,14 +18,9 @@ RUN /home/gitpod/.deno/bin/deno completions bash > /home/gitpod/.bashrc.d/90-den echo 'export DENO_INSTALL="/home/gitpod/.deno"' >> /home/gitpod/.bashrc.d/90-deno && \ echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> /home/gitpod/.bashrc.d/90-deno -# Install neovim and helpers -RUN wget https://github.com/neovim/neovim/releases/download/v0.9.2/nvim-linux64.tar.gz && \ - tar xzf nvim-linux64.tar.gz && \ - sudo mv nvim-linux64 /usr/local/nvim && \ - sudo ln -s /usr/local/nvim/bin/nvim /usr/local/bin/nvim && \ - rm -rf nvim-linux64.tar.gz -RUN sudo apt-get install -y fd-find -RUN npm install -g neovim +RUN sudo add-apt-repository ppa:maveonair/helix-editor && \ + sudo apt update && \ + sudo apt install helix # Install phpactor RUN curl -Lo phpactor.phar https://github.com/phpactor/phpactor/releases/latest/download/phpactor.phar diff --git a/.idea/silverback-template.iml b/.idea/silverback-template.iml index 9c91dbf7b..a6f46f687 100644 --- a/.idea/silverback-template.iml +++ b/.idea/silverback-template.iml @@ -15,6 +15,7 @@ + diff --git a/INIT.md b/INIT.md index 4e60b7c30..679a143b6 100644 --- a/INIT.md +++ b/INIT.md @@ -21,7 +21,11 @@ replace( '# ' + process.env.PROJECT_NAME_HUMAN, ); replace( - 'apps/cms/config/sync/system.site.yml', + [ + 'apps/cms/config/sync/system.site.yml', + 'tests/schema/specs/content.spec.ts', + 'tests/e2e/specs/drupal/metatags.spec.ts', + ], 'Silverback Drupal Template', process.env.PROJECT_NAME_HUMAN, ); diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index 7176d4424..07ca8aaa5 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -96,16 +96,16 @@ }, { "name": "amazeelabs/graphql_directives", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/AmazeeLabs/graphql_directives.git", - "reference": "57d0b5b48a42f27c612e8fe22da967e138f64e30" + "reference": "aa67a16d5acedc87a46f80827482527bd3bc19b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AmazeeLabs/graphql_directives/zipball/57d0b5b48a42f27c612e8fe22da967e138f64e30", - "reference": "57d0b5b48a42f27c612e8fe22da967e138f64e30", + "url": "https://api.github.com/repos/AmazeeLabs/graphql_directives/zipball/aa67a16d5acedc87a46f80827482527bd3bc19b2", + "reference": "aa67a16d5acedc87a46f80827482527bd3bc19b2", "shasum": "" }, "require": { @@ -127,9 +127,9 @@ "homepage": "https://silverback.netlify.app", "support": { "issues": "https://github.com/AmazeeLabs/graphql_directives/issues", - "source": "https://github.com/AmazeeLabs/graphql_directives/tree/2.4.0" + "source": "https://github.com/AmazeeLabs/graphql_directives/tree/2.5.0" }, - "time": "2024-02-07T13:55:07+00:00" + "time": "2024-04-04T10:12:23+00:00" }, { "name": "amazeelabs/proxy-default-content", diff --git a/apps/cms/config/sync/views.view.content_hub.yml b/apps/cms/config/sync/views.view.content_hub.yml new file mode 100644 index 000000000..5ea530f9e --- /dev/null +++ b/apps/cms/config/sync/views.view.content_hub.yml @@ -0,0 +1,274 @@ +uuid: 52941f28-544a-4658-86d4-a806ca2adc29 +langcode: en +status: true +dependencies: + config: + - node.type.page + module: + - node + - user +id: content_hub +label: 'Content hub' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + id: default + display_title: Default + display_plugin: default + position: 0 + display_options: + fields: + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: title + plugin_id: field + label: '' + exclude: false + alter: + alter_text: false + make_link: false + absolute: false + word_boundary: false + ellipsis: false + strip_tags: false + trim: false + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + pager: + type: mini + options: + offset: 0 + items_per_page: 10 + total_pages: null + id: 0 + tags: + next: ›› + previous: ‹‹ + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + empty: { } + sorts: + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: title + plugin_id: standard + order: ASC + expose: + label: '' + field_identifier: '' + exposed: false + arguments: { } + filters: + status: + id: status + table: node_field_data + field: status + entity_type: node + entity_field: status + plugin_id: boolean + value: '1' + group: 1 + expose: + operator: '' + type: + id: type + table: node_field_data + field: type + entity_type: node + entity_field: type + plugin_id: bundle + value: + page: page + group: 1 + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: langcode + plugin_id: language + operator: in + value: + '***LANGUAGE_language_interface***': '***LANGUAGE_language_interface***' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: title + plugin_id: string + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + operator_limit_selection: false + operator_list: { } + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + super_admin: '0' + administrator: '0' + gatsby_build: '0' + editor: '0' + placeholder: '' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + filter_groups: + operator: AND + groups: + 1: AND + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + default_field_elements: true + inline: { } + separator: '' + hide_empty: false + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + relationships: { } + header: { } + footer: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/apps/cms/gatsby-config.mjs b/apps/cms/gatsby-config.mjs index 83072b510..316fb9147 100644 --- a/apps/cms/gatsby-config.mjs +++ b/apps/cms/gatsby-config.mjs @@ -16,6 +16,7 @@ export const plugins = [ drupal_external_url: // File requests are proxied through netlify. process.env.NETLIFY_URL || 'http://127.0.0.1:8000', + graphql_path: '/graphql', auth_key: 'cfdb0555111c0f8924cecab028b53474', type_prefix: '', diff --git a/apps/decap/package.json b/apps/decap/package.json index 455aaf76b..4ed3ea046 100644 --- a/apps/decap/package.json +++ b/apps/decap/package.json @@ -16,8 +16,8 @@ }, "dependencies": { "@amazeelabs/cloudinary-responsive-image": "^1.6.15", - "@amazeelabs/graphql-directives": "^1.3.2", "@amazeelabs/gatsby-plugin-static-dirs": "^1.0.1", + "@amazeelabs/graphql-directives": "^1.3.2", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", "decap-cms-app": "^3.0.12", @@ -35,7 +35,7 @@ "zod": "^3.22.4" }, "devDependencies": { - "@amazeelabs/decap-cms-backend-token-auth": "^1.1.1", + "@amazeelabs/decap-cms-backend-token-auth": "^1.1.7", "@types/node": "^18", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", diff --git a/apps/decap/src/main.tsx b/apps/decap/src/main.tsx index 8c68ab190..8514165f0 100644 --- a/apps/decap/src/main.tsx +++ b/apps/decap/src/main.tsx @@ -21,9 +21,20 @@ CMS.registerPreviewStyle(css, { raw: true }); CMS.registerWidget('uuid', UuidWidget); CMS.registerBackend('token-auth', TokenAuthBackend); +if ( + window.location.hostname !== 'localhost' && + !import.meta.env.DEV && + (!import.meta.env.VITE_DECAP_REPO || !import.meta.env.VITE_DECAP_BRANCH) +) { + console.error( + "VITE_DECAP_REPO and VITE_DECAP_BRANCH environment variables are missing. Can't connect to the repository.", + ); +} + CMS.init({ config: { - publish_mode: 'simple', + load_config_file: false, + publish_mode: 'editorial_workflow', media_folder: 'apps/decap/media', // @ts-ignore backend: import.meta.env.DEV @@ -31,7 +42,9 @@ CMS.init({ // In development, use the in-memory backend. name: 'test-repo', } - : window.location.hostname === 'localhost' + : window.location.hostname === 'localhost' || + !import.meta.env.VITE_DECAP_REPO || + !import.meta.env.VITE_DECAP_BRANCH ? { // On localhost, use the proxy backend that writes to files. name: 'proxy', @@ -41,8 +54,8 @@ CMS.init({ // Otherwise, its production. Use the token auth backend. name: 'token-auth', api_root: '/.netlify/functions/github-proxy', - repo: 'AmazeeLabs/silverback-template', - branch: 'prod', + repo: import.meta.env.VITE_DECAP_REPO, + branch: import.meta.env.VITE_DECAP_BRANCH, }, i18n: { structure: 'single_file', diff --git a/apps/website/gatsby-node.mjs b/apps/website/gatsby-node.mjs index 4a9a8456e..89ea700c3 100644 --- a/apps/website/gatsby-node.mjs +++ b/apps/website/gatsby-node.mjs @@ -114,25 +114,6 @@ export const createPages = async ({ actions }) => { statusCode: 404, }); - // Proxy Drupal webforms. - Object.values(Locale).forEach((locale) => { - actions.createRedirect({ - fromPath: `/${locale}/form/*`, - toPath: `${process.env.GATSBY_DRUPAL_URL}/${locale}/form/:splat`, - statusCode: 200, - }); - }); - - // Additionally proxy themes and modules as they can have additional - // non-aggregated assets. - ['themes', 'modules'].forEach((path) => { - actions.createRedirect({ - fromPath: `/${path}/*`, - toPath: `${process.env.GATSBY_DRUPAL_URL}/${path}/:splat`, - statusCode: 200, - }); - }); - // Any unhandled requests are handed to strangler, which will try to pass // them to all registered legacy systems and return 404 if none of them // respond. diff --git a/apps/website/netlify.toml b/apps/website/netlify.toml index c267421cc..58f3e6585 100644 --- a/apps/website/netlify.toml +++ b/apps/website/netlify.toml @@ -5,6 +5,7 @@ directory = "netlify/functions" [build] + publish = "public" edge_functions = "netlify/edge-functions" [functions.strangler] diff --git a/apps/website/package.json b/apps/website/package.json index 59921a618..284072ca7 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -4,10 +4,10 @@ "dependencies": { "@amazeelabs/bridge-gatsby": "^1.2.7", "@amazeelabs/cloudinary-responsive-image": "^1.6.15", - "@amazeelabs/decap-cms-backend-token-auth": "^1.1.1", + "@amazeelabs/decap-cms-backend-token-auth": "^1.1.7", "@amazeelabs/gatsby-plugin-operations": "^1.1.3", "@amazeelabs/gatsby-plugin-static-dirs": "^1.0.1", - "@amazeelabs/gatsby-source-silverback": "^1.13.17", + "@amazeelabs/gatsby-source-silverback": "^1.14.0", "@amazeelabs/publisher": "^2.4.17", "@amazeelabs/strangler-netlify": "^1.1.9", "@amazeelabs/token-auth-middleware": "^1.1.1", @@ -15,9 +15,6 @@ "@custom/decap": "workspace:*", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", - "@gatsbyjs/reach-router": "^2.0.1", - "babel-loader": "^9.1.3", - "body-parser": "^1.20.2", "gatsby": "^5.13.1", "gatsby-plugin-layout": "^4.13.0", "gatsby-plugin-manifest": "^5.13.0", diff --git a/package.json b/package.json index e99aba4f0..059b089cc 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "node-gyp": "^10.0.1" }, "resolutions": { + "gatsby-plugin-sharp": "5.13.1", "sharp": "0.33.1", "eslint": "7", "graphql": "16.8.1" diff --git a/packages/drupal/custom/custom.services.yml b/packages/drupal/custom/custom.services.yml index 0ad082aad..fd6d0a918 100644 --- a/packages/drupal/custom/custom.services.yml +++ b/packages/drupal/custom/custom.services.yml @@ -3,9 +3,6 @@ services: class: Drupal\custom\Routing\RouteSubscriber tags: - { name: event_subscriber } - custom.content_hub: - class: Drupal\custom\ContentHub - arguments: ['@entity_type.manager'] custom.webform: class: Drupal\custom\Webform diff --git a/packages/drupal/custom/src/ContentHub.php b/packages/drupal/custom/src/ContentHub.php deleted file mode 100644 index db8afcfde..000000000 --- a/packages/drupal/custom/src/ContentHub.php +++ /dev/null @@ -1,81 +0,0 @@ -entityTypeManager = $entityTypeManager; - } - - /** - * Query a list of pages. - * - * @param \Drupal\graphql_directives\DirectiveArguments $args - * The graphql argument bag. - * - * @return array{'total': int, 'items': \Drupal\node\NodeInterface[]} - * Result of the query. - */ - public function query(DirectiveArguments $args) : array { - // @todo Switch this to views. - $offset = $args->args['pagination']['offset']; - $limit = $args->args['pagination']['limit']; - $locale = $args->args['locale']; - $nodeStorage = $this->entityTypeManager->getStorage('node'); - - // Clear this whenever nodes are changed. - $args->context->addCacheTags($nodeStorage->getEntityType()->getListCacheTags()); - - $countQuery = $nodeStorage->getQuery(); - $countQuery->condition('type', 'page'); - $countQuery->condition('status', 1); - if (!empty($args->args['query'])) { - $countQuery->condition('title', $args->args['query'], 'CONTAINS'); - } - $countQuery->condition('langcode', $locale); - $count = $countQuery->count()->accessCheck(TRUE)->execute(); - - $query = $nodeStorage->getQuery(); - $query->condition('type', 'page'); - $query->condition('status', 1); - if (!empty($args->args['query'])) { - $query->condition('title', $args->args['query'], 'CONTAINS'); - } - $query->condition('langcode', $locale); - $pageIds = $query->range($offset, $limit) - ->sort('title', 'ASC') - ->accessCheck(TRUE) - ->execute(); - - $entities = array_map(function (NodeInterface $node) use ($locale) { - return $node->getTranslation($locale); - }, $pageIds ? $nodeStorage->loadMultiple($pageIds) : []); - - return [ - 'total' => $count, - 'items' => $entities, - ]; - } - -} diff --git a/packages/drupal/gutenberg_blocks/src/blocks/hero.tsx b/packages/drupal/gutenberg_blocks/src/blocks/hero.tsx index e552a27c1..12fd053a3 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/hero.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/hero.tsx @@ -6,12 +6,22 @@ import { RichText, } from 'wordpress__block-editor'; import { registerBlockType } from 'wordpress__blocks'; -import { PanelBody } from 'wordpress__components'; +import { PanelBody, SelectControl } from 'wordpress__components'; import { compose, withState } from 'wordpress__compose'; import { dispatch } from 'wordpress__data'; import { DrupalMediaEntity } from '../utils/drupal-media'; +declare const drupalSettings: { + customGutenbergBlocks: { + forms: Array<{ + id: string; + url: string; + label: string; + }>; + }; +}; + // @ts-ignore const { t: __ } = Drupal; // @ts-ignore @@ -44,6 +54,9 @@ registerBlockType('custom/hero', { type: 'boolean', default: true, }, + formId: { + type: 'string', + }, }, supports: { inserter: false, @@ -93,6 +106,23 @@ registerBlockType('custom/hero', { )} + + ({ + label: form.label, + value: form.id, + })), + ]} + onChange={(formId: string) => { + props.setAttributes({ + formId, + }); + }} + /> +
@@ -169,6 +199,16 @@ registerBlockType('custom/hero', { />
)} + {props.attributes.formId ? ( +