Drupal https://brookemahoney.com/ en DDEV https://brookemahoney.com/blog/ddev <span class="field field--name-title field--type-string field--label-hidden">DDEV</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><p>Just some notes about DDEV</p><p><code>ddev snapshot --name=seed</code>&nbsp;</p><p><code>ddev delete</code></p><p><code>ddev start</code></p><h2>Addons for base Drupal configurations</h2><h3>Descriptions of 4 common ways to setup DDEV</h3><table><thead><tr><th>Add-on</th><th>Best for</th><th>Typical project layout</th></tr></thead><tbody><tr><td><a href="https://github.com/UltraBob/ddev-drupal-code-quality"><code>UltraBob/ddev-drupal-code-quality</code></a></td><td>Full Drupal website projects where your site repo already contains Drupal code and custom code.</td><td>Existing site/project repo; installs code-quality configs and IDE shims in-place.</td></tr><tr><td><a href="https://github.com/ddev/ddev-drupal-contrib"><code>ddev/ddev-drupal-contrib</code></a></td><td>Drupal contrib module/theme development where the contrib project is the center of the repo.</td><td>Contrib project repo with Drupal scaffolded around it (symlink workflow).</td></tr><tr><td><a href="https://github.com/justafish/ddev-drupal-core-dev"><code>justafish/ddev-drupal-core-dev</code></a> / <a href="https://github.com/joachim-n/ddev-drupal-core-dev"><code>joachim-n/ddev-drupal-core-dev</code></a></td><td>Drupal core development.</td><td>Drupal core checkout or core-dev project template.</td></tr><tr><td><a href="https://addons.ddev.com/addons/amateescu/ddev-drupal-dev">https://addons.ddev.com/addons/amateescu/ddev-drupal-dev</a></td><td>Drupal core and contrib combined, see below</td><td>core git checkout as the project root.</td></tr></tbody></table><h3>DDEV Drupal Dev from Andrei Mateescu of Tag1</h3><p>A DDEV add-on for working on Drupal core and contrib modules together, using a core git checkout as the project root.</p><p>Other add-ons target either core or contrib in isolation. This one is for when you need both: developing a contrib module against the latest core, fixing a core bug that affects contrib, or running contrib tests on a core patch.</p><p>Extra dependencies (contrib modules, Drush, dev tools) are managed through a <code>composer.local.json</code> overlay, keeping core's <code>composer.json</code> and <code>composer.lock</code> untouched.</p><p>"You can work on multiple modules this way; each gets its own git checkout that you can commit and push to independently."</p></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2026-11-02T11:59:42-08:00" title="Monday, 2 November 2026 - 11:59" class="datetime">Mon, 2 Nov 2026 - 11:59</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> <div class="field__item"><a href="https://brookemahoney.com/tags/ddev" hreflang="en">DDEV</a></div> </div> </div> Mon, 02 Nov 2026 19:59:42 +0000 brooke 180 at https://brookemahoney.com Contrib Drupal Module Development https://brookemahoney.com/blog/contrib-drupal-module-development <span class="field field--name-title field--type-string field--label-hidden">Contrib Drupal Module Development</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><p>One way to set up local Development for a contrib module using e0ipso's AI suite. See <a href="https://www.youtube.com/watch?v=q0R9e7KhBsg">Agentic Coding Harness for Drupal&nbsp;</a></p><ul><li>opt out of T3 telemetry by adding the following to <code>~/.ddev/.env</code><ul><li><code>T3CODE_TELEMETRY_ENABLED=false</code></li></ul></li><li>Install <a href="https://github.com/e0ipso/self-review">https://github.com/e0ipso/self-review</a></li><li>Then:</li></ul><pre><code class="language-plaintext">BDEV_CURRENT_SITE="config_suite" cd ~/Sites git clone git@git.drupal.org:project/$BDEV_CURRENT_SITE.git cd $BDEV_CURRENT_SITE cp ~/projects/.gitignore-ddev .gitignore ddev config --project-type=drupal11 --docroot=web orb ddev add-on get ddev/ddev-drupal-contrib ddev add-on get hanoii/ddev-drupal-contrib-extras ddev start ddev poser ddev symlink-project ddev config --update ddev restart ddev drush si -y ddev drush en $BDEV_CURRENT_SITE -y open $(ddev drush uli) ddev exec "cd web/core &amp;&amp; yarn install" # Temporary workaround on Mac because e0ipso's plugin only works for Linux for importing local OpenCode config mkdir -p $HOME/Sites/$BDEV_CURRENT_SITE/.ddev/homeadditions/.local/share ln -s $HOME/.local/share/opencode $HOME/Sites/$BDEV_CURRENT_SITE/.ddev/homeadditions/.local/share/opencode ddev add-on get e0ipso/ddev-playwright-cli ddev add-on get e0ipso/ddev-assistant-opencode ddev add-on get e0ipso/ddev-assistant-t3 ddev restart # Allows running playwright-cli from inside the container # without having to use the --config option # Symlinks .claude playwright skills to .agents folder for other harnesses. ddev ssh rm .playwright/cli.config.json ln -s "$(pwd)/.ddev/playwright-cli/cli.config.json" .playwright/cli.config.json mkdir -p .agents/skills ln -s "$(pwd)/.claude/skills/playwright-cli" .agents/skills/playwright-cli exit npx kenkeep init --harnesses opencode npx kenkeep doctor npx kenkeep pack import e0ipso/kenkeep-pack-drupal npx strikethroo init --harnesses opencode npx skills add e0ipso/strikethroo </code></pre><p>Strikethroo workflow: <a href="https://strikethroo.canpicasoft.com/workflow.html">https://strikethroo.canpicasoft.com/workflow.html</a> Kenkeep docs: <a href="https://kenkeep.canpicasoft.com/">https://kenkeep.canpicasoft.com/</a></p><ul><li><code>ddev t3 start</code></li><li><code>npx strikethroo serve</code></li><li><code>/st-create-plan display a welcome message to new users</code></li><li><code>/st-refine-plan 1, see review.xml</code></li><li><code>/st-execute-blueprint 1</code></li><li><code>/st-code-review 1, line 95 in example.module has a typo: "piece" should be "peace"</code></li></ul></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2026-11-02T11:58:32-08:00" title="Monday, 2 November 2026 - 11:58" class="datetime">Mon, 2 Nov 2026 - 11:58</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/ai" hreflang="en">AI</a></div> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> <div class="field__item"><a href="https://brookemahoney.com/tags/ddev" hreflang="en">DDEV</a></div> </div> </div> Mon, 02 Nov 2026 19:58:32 +0000 brooke 179 at https://brookemahoney.com Notes for: Site Analysis and AI's Strengths and Limits, By David Hobbs https://brookemahoney.com/blog/notes-site-analysis-and-ais-strengths-and-limits-david-hobbs <span class="field field--name-title field--type-string field--label-hidden">Notes for: Site Analysis and AI's Strengths and Limits, By David Hobbs</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><p>Just copying over some notes from a great video, in case it ever goes away:</p><p>&nbsp;</p><h2><a href="https://www.youtube.com/watch?v=5N1-jrcCgLQ">Site Analysis and AI's Strengths and Limits</a></h2><p>By David Hobbs</p><h3>Progression of content analysis</h3><table class="table-style-align-center"><thead><tr><th>#</th><th>Type</th><th>Example</th><th>Automation at scale?</th><th>Use Al?</th><th>Quantitative or<br><br>Qualitative?</th></tr></thead><tbody><tr><td>1</td><td>Traditional Automation</td><td>Crawl with [[Screaming Frog]]</td><td>Yes</td><td>No</td><td>Quantitative</td></tr><tr><td>2</td><td>Manual</td><td>Line-by-line qualitative audit</td><td>No</td><td>No</td><td>Qualitative</td></tr><tr><td>3</td><td>One-off Al</td><td>Ask ChatGPT a question about a site</td><td>No</td><td>Yes, one time</td><td>Qualitative</td></tr><tr><td>4</td><td>LLM → Deterministic</td><td>Have Claude write a script to run reading level analysis on every page</td><td>Partial</td><td>Yes, one time</td><td>Quantitative</td></tr><tr><td>5</td><td>Scaled Al</td><td>Pass thousands of pages to low power LLM to categorize</td><td>Yes</td><td>Yes, every page</td><td>Qualitative</td></tr><tr><td>Note: Al allows us to do some qualitative analysis that is NOT manual.</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table><h2>Advantages of particular types of content analysis</h2><table class="table-style-align-center"><thead><tr><th>&nbsp;</th><th>&nbsp;</th><th>&nbsp;</th></tr></thead><tbody><tr><td>#</td><td>Type</td><td>Advantages</td></tr><tr><td>1</td><td>Traditional Automation</td><td>Easy to get basic information on many pages.</td></tr><tr><td>2</td><td>Manual</td><td>The approach everyone knows, and it feels like things are being done in a high quality manner (even before Al this was often the feeling).</td></tr><tr><td>3</td><td>One-off Al</td><td>Easy to get analysis that might be outside your expertise for a subset of pages.</td></tr><tr><td>4</td><td>LLM → Deterministic</td><td>Only use Al once, and then have all the advantages of traditional automation.</td></tr><tr><td>5</td><td>Scaled Al</td><td>Get qualitative information over a large swath of content (possibly impossible otherwise).</td></tr></tbody></table><h2>Disadvantages of the manual approach</h2><ul><li>Almost certainly leads to restricting the number of pages that are analyzed.</li><li>Very difficult to iterate or change course (such as if you decide to change the definition of audience)</li><li>Very difficult to merge analysis from multiple people.</li><li>Very difficult to be consistent.</li><li>Difficult to track over time.</li><li>Likely to encourage team to take action on content while analyzing it.</li></ul><h2>Disadvantages of one-off Al</h2><ul><li>Generally low transparency (How did it come up with the answer? Did it look at any pages? Did it consider your existing analysis?)</li><li>Related to the above, it can definitely hallucinate or over-extrapolate.</li><li>Generally requires powerful LLM</li><li>Can short circuit critical thinking of the analyst</li><li>NOT for pervasive analysis across a digital presence</li></ul><p>Tip: If you do one-off chatbot analysis, explicitly ask how it came up with the answer and its sources. ie., "Explain to me in detail how you determined this. What sources did you use? What did you derive from each source? What confidence do you have in this answer?"</p><h2>Disadvantages of LLM → Deterministic</h2><ul><li>Depending on skillset, can be difficult to evaluate whether the script is doing what you want it to do</li><li>Can be difficult to clearly specify the requirements</li><li>One-off efforts to write scripts can be difficult to reconcile together into a coherent whole</li></ul><h2>Keep in mind non-LLM automated analysis options</h2><ul><li>Near-text duplicate analysis</li><li>Graph database analysis (analysis of linkages between content / sections)</li><li>Scraping out information and component analysis</li><li>PDF analysis</li><li>Merge information (for instance from Google Analytics)</li><li>Reading level analysis</li><li>Charting</li><li>HTML structure analysis</li><li>Over-time analysis</li><li>Logo analysis</li><li>Formulas, maps, and rules</li><li>Technology analysis (such as with wappalyzer)</li></ul><p>Tip: Consider converting problems to deterministic ones with AI</p><h2>Disadvantage of Scaled Al</h2><ul><li>Difficult to develop on your own</li><li>Heavy processing is in the backend (not in chatbot)</li></ul><p>TIp: Clearly differentiate between what's EASY to analyze and what's IMPORTANT to analyze.</p><p>Note: Far too often teams just revert to what's simple to analyze when that will not help them achieve their real business goals. For example, you can't analyze for consolidation if you only look at a small subset of pages because then you would be able to use a spreadsheet.</p><h2>Summary</h2><ul><li>There is lots of automation that can be applied outside of LLMs.</li><li>For some questions, you really do need to look at the entire digital presence.</li><li>Al can help you accomplish qualitative assessments that required manual intervention before.<br>There are a variety of ways of using Al for site analysis.</li></ul><p>Tips</p><ol><li>Focus on refining the QUESTIONS you need answered.</li><li>If you do one-off chatbot analysis, explicitly ask how it came up with the answer in its sources.</li><li>Consider converting problems to deterministic ones with Al.</li><li>Clearly differentiate between what's EASY to analyze and what's IMPORTANT to analyze.</li></ol></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2026-11-02T11:04:39-08:00" title="Monday, 2 November 2026 - 11:04" class="datetime">Mon, 2 Nov 2026 - 11:04</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/ai" hreflang="en">AI</a></div> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> </div> </div> Mon, 02 Nov 2026 19:04:39 +0000 brooke 176 at https://brookemahoney.com AI Ethics https://brookemahoney.com/blog/ai-ethics <span class="field field--name-title field--type-string field--label-hidden">AI Ethics</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><h2>The Environment</h2><p>From <a href="https://www.acquia.com/blog/when-clock-ticks-closer-ai-ethics-and-drupals-responsibility-public-good">When the Clock Ticks Closer: AI, Ethics, and Drupal's Responsibility to the Public Good April 1, 2026</a></p><ul><li>Data centers are multiplying across the globe drawing enormous quantities of power and water</li><li>from communities already under resource strain.</li><li>The raw materials required for AI hardware involve<ul><li>environmentally damaging extraction processes</li><li>energy-intensive manufacturing</li><li>growing mountains of electronic waste</li></ul></li></ul><p>"This is not an abstraction for the Drupal community. As an ecosystem that powers a substantial share of the world's web infrastructure - from small nonprofits to national governments - the collective energy footprint of Drupal-based sites and the hosting decisions behind them carries real weight. Organizations within the community have begun pushing for greener hosting choices, leaner architectures, and more deliberate thinking about when AI features add genuine value versus when they simply add compute."</p></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2026-11-02T10:58:09-08:00" title="Monday, 2 November 2026 - 10:58" class="datetime">Mon, 2 Nov 2026 - 10:58</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/ai" hreflang="en">AI</a></div> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> </div> </div> Mon, 02 Nov 2026 18:58:09 +0000 brooke 174 at https://brookemahoney.com Drupal Headless Code Components Setup - My First Attempt https://brookemahoney.com/blog/drupal-headless-code-components-setup-my-first-attempt <span class="field field--name-title field--type-string field--label-hidden">Drupal Headless Code Components Setup - My First Attempt</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><h2 data-heading="Overview">Overview</h2><p>This is an overview of my first attempt at working with Drupal Code Components and headless Next.js. The purpose of this page is to document my poor understanding of how to set this up so that I may revisit it when I know better and so that I may mentor other people to do better.</p><p>See <a class="external-link" href="https://project.pages.drupalcode.org/canvas/headless/setup/" target="_blank" rel="noopener nofollow">https://project.pages.drupalcode.org/canvas/headless/setup/</a> for the offical documentation.</p><h2 data-heading="Outline of Setup Steps">Outline of Setup Steps</h2><ul><li data-list-item-id="e380ff679f58638f8520ecd6e703560bd">Create a folder for the project and cd into it.</li><li data-list-item-id="eccf5125973286850741f44448a191e96">Configure DDEV for Drupal with some extra modules needed for the Canvas Headless (canvas_headless) module</li><li data-list-item-id="e2efb13ff70788842eade998e433a6e88">Create a folder to store OAuth keys.</li><li data-list-item-id="e1e3979a27aa8b91407e50774a3187e19">Enable the Canvas Headless and the Canvas OAuth modules</li><li data-list-item-id="e971e806d54fe5c9f2f1a60af5f7f6d19">Log in with Chrome (<a class="external-link" href="https://project.pages.drupalcode.org/canvas/headless/setup/#prerequisites" target="_blank" rel="noopener nofollow" aria-label="https://project.pages.drupalcode.org/canvas/headless/setup/#prerequisites" data-tooltip-position="top">the docs say you have to use a Chromium-based browser, so no Firefox of Safari.</a>.)</li><li data-list-item-id="e17888cb5d46b4c3f134748a4561fb533">Scaffold the Canvas Headless code, in a new folder called <code>headless</code>, with support for Next.js</li><li data-list-item-id="e3fe16d877258bb9f30dfd9ff9daf9786">Get Vercel AI agent skills to help your agents understand the Next/React conventions.</li><li data-list-item-id="e6ea9f890dffb3ee14f67a639908ef6ee">Get Drupal Canvas AI agent skills</li><li data-list-item-id="e7d7d6b458cb7504d84f90ae0c72c0624">Install Canvas CLI</li><li data-list-item-id="e0884aa0230b719750dfcc699a275f0da">Start the Canvas Headless Next.js server.</li><li data-list-item-id="e592b6e08cd32fb9229cb700d0e94c3be">Initialize a git repo with a default .gitignore</li><li data-list-item-id="e6afca5163bab06c13e5277ea71b56ee0">Generate OAuth keys</li><li data-list-item-id="e2fb90d3c56c1cab87cb3e74178a8c0de">Setup a consumer</li><li data-list-item-id="ee4f4332be46e86129d932e960132015e">Add the Frontend in the Canvas editor</li><li data-list-item-id="e49a0e11920e977afffce39ab9ce373d1">Login with Canvas CLI</li><li data-list-item-id="e8d8bece8be3073882d7ab8f5edc2b592">Push the default Code Components to Drupal</li><li data-list-item-id="e9d087b449c6e8c74a89ea9bb565537ef">Edit a page in the Canvas editor in Drupal</li><li data-list-item-id="eeb12dcb9b3de0e1e56a6770699bfe69d">View the site in the Next.js app in the browser</li></ul><h2 data-heading="Detailed Setup Steps">Detailed Setup Steps</h2><pre><code class="language-plaintext">cd ~/Sites mkdir headless cd headless </code></pre><pre><code class="language-plaintext"> ddev config --project-type=drupal11 --docroot=web ddev start ddev composer create-project drupal/recommended-project ddev composer require --dev 'drush/drush' ddev composer require 'drupal/canvas' 'drupal/consumers' 'drupal/custom_elements' 'drupal/simple_oauth' ddev restart mkdir keys ddev drush si -y ddev drush en canvas_headless, canvas_oauth -y open -a "Google Chrome" "$(ddev drush uli)" npx @drupal-canvas/create@latest headless \ --template nextjs \ --site-url https://headless.ddev.site \ --agents none cd headless npx skills add vercel-labs/agent-skills --yes npx skills add drupal-canvas/skills --yes npm install @drupal-canvas/cli npm run dev </code></pre><p>Open up a new terminal window.</p><ul><li data-list-item-id="e1f9ed96547694e04e11509d5078a5d0d">Remove the newly added <code>.git</code> folder that the scaffolding added (<code>~/Sites/headless/headless/.git</code>).</li><li data-list-item-id="e89598ba515cf5990faefe444f314b6db">Init a git repo for the whole project instead.</li><li data-list-item-id="e7b465cfdc07b53894d213900ea9667c6">Make an initial commit so that later, you can get a clear picture of what Canvas CLI creates or pulls in.</li></ul><pre><code class="language-plaintext">rm -rf ~/Sites/headless/headless/.git cd ~/Sites/headless/ git init . echo "web/sites/default/files" &gt;&gt; .gitignore echo "vendor" &gt;&gt; .gitignore git add . &amp;&amp; git commit -m 'initial commit' </code></pre><p>Open <a class="external-link" href="http://headless.ddev.site/admin/config/people/simple_oauth" target="_blank" rel="noopener nofollow">http://headless.ddev.site/admin/config/people/simple_oauth</a> and click "Generate keys" button, then enter the folder that was created in the steps above:</p><pre><code class="language-plaintext">/var/www/html/keys </code></pre><p>Make sure to click the "Save configuration" button.</p><p>Open <a class="external-link" href="http://headless.ddev.site/admin/config/services/consumer" target="_blank" rel="noopener nofollow">http://headless.ddev.site/admin/config/services/consumer</a> and click "+ Add Consumer", then set the following field values:</p><ul><li data-list-item-id="eb4015809ec3dd9abc72155f7667fe9da">Label: Code Components</li><li data-list-item-id="ea3d8e4eb780463261d085736f609ac7d">Client ID: code-components</li><li data-list-item-id="ed24bc11cd161d5130ee67e19202902dd">Grant types (enable both):<ul><li data-list-item-id="e5ea457461a79414727ad2a0fdf5285f5">JWT bearer (preview assertion)</li><li data-list-item-id="e147e4d6152b101f9be02dd4ba7719d03">Authorization Code</li></ul></li><li data-list-item-id="e1da84f08356b464968ea92882808b476">Authorization Code settings<ul><li data-list-item-id="ee00f27ed4edd11621581f0c52679fa15">Enable all scopes</li><li data-list-item-id="e6a947fc2c00f2442ba013df927f7d470">Enable "Automatic authorization", "Remember previous approval", "Use PKCE?"</li><li data-list-item-id="eefade9eb6b5c66ff57e64cba9be71dea">Redirect URIs:</li></ul></li></ul><pre><code class="language-plaintext">http://localhost:4444/callback </code></pre><ul><li data-list-item-id="e8cc263dd3e58ac0fc20a23d30c46eeca">Disable "Is this consumer 3rd party?"</li><li data-list-item-id="e1617103256f33b9e9e0d23281516bb2d">Disable "Is Confidential?"</li><li data-list-item-id="eb5a2cc6b6010ce4df90c59daf85e18aa">Set "Access token expiration time" to 86400 (one day)</li><li data-list-item-id="e4cc9be60e30ccd4da23210e02c6d3e19">Click Save</li><li data-list-item-id="e01f1abdbf7c538a484667e8775bf5202">Open <a class="external-link" href="http://headless.ddev.site/admin/content/pages" target="_blank" rel="noopener nofollow">http://headless.ddev.site/admin/content/pages</a> and click "Add Page", which opens up a new Canvas editing screen at <code>canvas/editor/canvas_page/1</code></li><li data-list-item-id="ed25053f01b23146fcbde8c11afd0d35a">Click the icon in the left rail for "Headless frontends"</li><li data-list-item-id="e4e720dbcc09d1cbe9d12e5461ca9d177">Click "Add frontend".<ul><li data-list-item-id="ea3bb57aefc06b473949c0c5f602560fb">For the Frontend URL field, use the following (no trailing slash, no query, and no fragment):</li></ul></li></ul><pre><code class="language-plaintext">http://localhost:3000 </code></pre><p>If everything went as expected, the "Frontends" section will show <code>http://localhost:3000</code> as being "Ready".</p><p>Change the default OS browser to Chrome; the Canvas CLI login command will open a browser and it needs to be Chrome (or <code>Chromium-based</code>).</p><ul><li data-list-item-id="e0ac6bfe2bdf958cdcbba13ae8b47730b">Cd into the <code>headless</code> folder in the directory of the site.<ul><li data-list-item-id="e2a83f9ed3adb9ccef4ace031ffd3ff1c">In this example, the site and the folder are both named <code>headless</code>.</li></ul></li><li data-list-item-id="e865819432ee310b9ca968ab6cb176810">Login with Canvas CLI using the OAuth consumer client id from the step above (<code>code-components</code>).</li></ul><pre><code class="language-plaintext">cd ~/Sites/headless/headless npx canvas login --client-id code-components </code></pre><p>That will open a browser window to a link on the Drupal site, which redirects you to a localhost page that shows you a success/failure message. If everything went well, you can now use Canvas CLI (<code>npx canvas</code>) to push and pull components between Drupal and Next. Since the scaffolding created some new components, use the push command to get them to the Drupal site:</p><pre><code class="language-plaintext">npx canvas push </code></pre><p>Now, you can create a page in Drupal and the components from the Next.js app should appear there. Build out a page, change some css in the code if you want (<code>headless/app/globals.css</code> for example, maybe change the body background color), and watch things change in the edit screen in realtime. Then, save your changes in Canvas, just like when you are in regular non-headless mode, and view your site at <code>http://localhost:3000</code>.</p><h2 data-heading="Notes">Notes</h2><p>The example above doesn't use Astro, but if you do use Astro:</p><pre><code class="language-plaintext">npx astro telemetry disable </code></pre></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2026-10-06T18:06:33-07:00" title="Tuesday, 6 October 2026 - 18:06" class="datetime">Tue, 6 Oct 2026 - 18:06</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> </div> </div> Wed, 07 Oct 2026 01:06:33 +0000 brooke 150 at https://brookemahoney.com Bash scripts for Drupal, DDEV, and Tome https://brookemahoney.com/blog/bash-scripts-drupal-ddev-and-tome <span class="field field--name-title field--type-string field--label-hidden">Bash scripts for Drupal, DDEV, and Tome</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__items"> <div class="field__item"><p>This website is made with <a href="https://drupal.org">Drupal</a>. Local development is done with <a href="https://ddev.com/">DDEV</a> configured to use Orbstack for container management. The <a href="https://tome.fyi/">Tome</a> Drupal module exports the dynamic site to a static site. Python is used to preview the site locally. Public hosting is provided free by Github.</p><p>Here are some custom bash aliases and functions I use to make common local tasks easy. <a href="https://www.npmjs.com/package/concurrently">Concurrently</a> is used to run two processes in one terminal session.</p><pre><code class="language-bash language-plaintext">alias brookemahoneycomCd='cd ~/Sites/brookemahoneycom' brookemahoneycomLaunch () { # Starts Orbstack if it isn't running. orb brookemahoneycomCd cd drupal ddev launch } brookemahoneycomPreview () { brookemahoneycomCd cd docs concurrently 'python -m http.server' 'open "http://localhost:8000"' } brookemahoneycomBuild() { # Starts Orbstack if it isn't running. orb # Changes to the drupal directory. cd ~/Sites/brookemahoneycom/drupal # Clears caches. ddev drush cr # Exports the site with Tome. ddev drush tome:static --uri=https://brookemahoney.com # Removes old docs directory. rm -rf ~/Sites/brookemahoneycom/docs # Copies the Tome build directory to docs for Github hosting. cp -R ~/Sites/brookemahoneycom/drupal/html ~/Sites/brookemahoneycom/docs # Copies over static files, such as CNAME for Github hosting. cp -R ~/projects/brookemahoneycom/sync/ ~/Sites/brookemahoneycom/docs } </code></pre><p>All the code for the Drupal install is in <a href="https://github.com/brookemahoney/brookmahoneycom">this site's Github repo</a>.</p></div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span>brooke</span></span> <span class="field field--name-created field--type-created field--label-hidden"><time datetime="2025-09-17T13:46:57-07:00" title="Wednesday, 17 September 2025 - 13:46" class="datetime">Wed, 17 Sep 2025 - 13:46</time> </span> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="https://brookemahoney.com/tags/drupal" hreflang="en">Drupal</a></div> </div> </div> Wed, 17 Sep 2025 20:46:57 +0000 brooke 11 at https://brookemahoney.com