From 78fb37f118df02135a29a911c950f33ae78488dc Mon Sep 17 00:00:00 2001 From: Alex Leventer Date: Fri, 9 Oct 2026 08:30:17 -0700 Subject: [PATCH 1/3] Fix Shopify launches on Shopify CLI 4.x Shopify CLI 4.0 removed `--force` from `shopify app deploy`, so `fly launch` on a Shopify app fails with "Nonexistent flag: --force" and Dockerfile generation exits 1. Use `--allow-updates`, Shopify's replacement for non-interactive runs. Also catch a failed `shopify app deploy` (for example, CLI not installed) and warn instead of aborting the launch, and pass `--config` to `shopify app env show` as the log line already says. Fixes #151. Supersedes #153. Co-Authored-By: Claude Opus 5.5 --- fly.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fly.js b/fly.js index 2a35da9..7b03493 100755 --- a/fly.js +++ b/fly.js @@ -365,7 +365,7 @@ GDF.extend(class extends GDF { try { console.log(`${chalk.bold.green('execute'.padStart(11))} shopify app env show --config ${configFile}`) - const stdout = execSync('shopify app env show', { encoding: 'utf8' }) + const stdout = execSync(`shopify app env show --config ${configFile}`, { encoding: 'utf8' }) for (const match of stdout.matchAll(/^\s*(\w+)=(.*)/mg)) { if (match[1] === 'SHOPIFY_API_SECRET') { console.log(`${chalk.bold.green('execute'.padStart(11))} flyctl secrets set SHOPIFY_API_SECRET`) @@ -391,8 +391,13 @@ GDF.extend(class extends GDF { if (original !== config) { console.log(`${chalk.bold.green('update'.padStart(11, ' '))} ${configFile}`) fs.writeFileSync(configFile, config) - console.log(`${chalk.bold.green('execute'.padStart(11))} shopify app deploy --force --config ${configFile}`) - execSync(`shopify app deploy --force --config ${configFile}`, { stdio: 'inherit' }) + const cmd = `shopify app deploy --allow-updates --config ${configFile}` + console.log(`${chalk.bold.green('execute'.padStart(11))} ${cmd}`) + try { + execSync(cmd, { stdio: 'inherit' }) + } catch { + console.log(`${chalk.bold.yellow('warn'.padStart(11))} ${chalk.bold.blue(cmd)} failed; run it yourself to update your app's URLs in Shopify.`) + } } } From d2c64103a366a472df119538832b4e11a0f740d8 Mon Sep 17 00:00:00 2001 From: Alex Leventer Date: Fri, 9 Oct 2026 08:32:48 -0700 Subject: [PATCH 2/3] Test Shopify deploy flags and failure handling Co-Authored-By: Claude Opus 5.5 --- test/test.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/test.js b/test/test.js index 00b3925..3dca10a 100644 --- a/test/test.js +++ b/test/test.js @@ -133,3 +133,80 @@ for (const group of fs.readdirSync('test', { withFileTypes: true })) { }) } } + +describe('fly: shopify', function() { + const savedPath = process.env.PATH + const savedCwd = process.cwd() + let workdir, bindir, log + + // write an executable stub that logs its arguments + function stub(name, script = '') { + const file = path.join(bindir, name) + fs.writeFileSync(file, `#!/bin/sh\necho "${name} $*" >> "${log}"\n${script}`) + fs.chmodSync(file, 0o755) + } + + beforeEach(function() { + workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'shopify-')) + bindir = fs.mkdtempSync(path.join(os.tmpdir(), 'shopify-bin-')) + log = path.join(bindir, 'log') + fs.writeFileSync(log, '') + + fs.writeFileSync(path.join(workdir, 'package.json'), JSON.stringify({ name: 'shopify-app', scripts: { start: 'node index.js' } })) + fs.writeFileSync(path.join(workdir, 'fly.toml'), 'app = "shopify-test"\n') + fs.writeFileSync(path.join(workdir, 'shopify.app.toml'), [ + 'client_id = "abc123"', + 'application_url = "https://example.trycloudflare.com"', + '', + '[auth]', + 'redirect_urls = ["https://example.trycloudflare.com/auth/callback"]', + '' + ].join('\n')) + + stub('flyctl', 'case "$*" in *" list "*) echo "[]";; esac\n') + + // fly.js looks for shopify.app*.toml in the current directory + process.chdir(workdir) + process.env.PATH = [bindir, path.dirname(process.execPath), '/usr/bin', '/bin'].join(path.delimiter) + }) + + afterEach(function() { + process.chdir(savedCwd) + process.env.PATH = savedPath + fs.rmSync(workdir, { recursive: true }) + fs.rmSync(bindir, { recursive: true }) + }) + + it('deploys with --allow-updates', async function() { + // Shopify CLI 4.x rejects --force + stub('shopify', 'case "$*" in *--force*) exit 2;; esac\n') + + await new GDF().run(workdir, { ...defaults, force: true }) + + const calls = fs.readFileSync(log, 'utf-8') + expect(calls).to.include('shopify app env show --config shopify.app.toml') + expect(calls).to.include('shopify app deploy --allow-updates --config shopify.app.toml') + expect(calls).not.to.include('--force') + + const config = fs.readFileSync(path.join(workdir, 'shopify.app.toml'), 'utf-8') + expect(config).to.include('application_url = "https://shopify-test.fly.dev"') + expect(config).to.include('"https://shopify-test.fly.dev/auth/callback"') + }) + + it('continues when shopify app deploy fails', async function() { + stub('shopify', 'exit 1\n') + + await new GDF().run(workdir, { ...defaults, force: true }) + + const config = fs.readFileSync(path.join(workdir, 'shopify.app.toml'), 'utf-8') + expect(config).to.include('application_url = "https://shopify-test.fly.dev"') + expect(fs.existsSync(path.join(workdir, 'Dockerfile'))).to.equal(true) + }) + + it('continues when the shopify CLI is not installed', async function() { + await new GDF().run(workdir, { ...defaults, force: true }) + + const config = fs.readFileSync(path.join(workdir, 'shopify.app.toml'), 'utf-8') + expect(config).to.include('application_url = "https://shopify-test.fly.dev"') + }) +}) From b7344e1503d53347230f7bf7dd5239c690c2ad39 Mon Sep 17 00:00:00 2001 From: Alex Leventer Date: Fri, 9 Oct 2026 08:34:26 -0700 Subject: [PATCH 3/3] Fall back to --force on Shopify CLI before 3.90 --allow-updates first appeared in Shopify CLI 3.90 (3.87 has only --force), so check `shopify app deploy --help` and use --force when --allow-updates isn't listed. Co-Authored-By: Claude Opus 5.5 --- fly.js | 14 +++++++++++--- test/test.js | 12 +++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/fly.js b/fly.js index 7b03493..2140951 100755 --- a/fly.js +++ b/fly.js @@ -364,8 +364,9 @@ GDF.extend(class extends GDF { } try { - console.log(`${chalk.bold.green('execute'.padStart(11))} shopify app env show --config ${configFile}`) - const stdout = execSync(`shopify app env show --config ${configFile}`, { encoding: 'utf8' }) + const cmd = `shopify app env show --config ${configFile}` + console.log(`${chalk.bold.green('execute'.padStart(11))} ${cmd}`) + const stdout = execSync(cmd, { encoding: 'utf8' }) for (const match of stdout.matchAll(/^\s*(\w+)=(.*)/mg)) { if (match[1] === 'SHOPIFY_API_SECRET') { console.log(`${chalk.bold.green('execute'.padStart(11))} flyctl secrets set SHOPIFY_API_SECRET`) @@ -391,7 +392,14 @@ GDF.extend(class extends GDF { if (original !== config) { console.log(`${chalk.bold.green('update'.padStart(11, ' '))} ${configFile}`) fs.writeFileSync(configFile, config) - const cmd = `shopify app deploy --allow-updates --config ${configFile}` + // Shopify CLI 3.90 added --allow-updates; 4.0 removed --force + let flag = '--allow-updates' + try { + const help = execSync('shopify app deploy --help', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + if (!help.includes('--allow-updates')) flag = '--force' + } catch { } + + const cmd = `shopify app deploy ${flag} --config ${configFile}` console.log(`${chalk.bold.green('execute'.padStart(11))} ${cmd}`) try { execSync(cmd, { stdio: 'inherit' }) diff --git a/test/test.js b/test/test.js index 3dca10a..64c5682 100644 --- a/test/test.js +++ b/test/test.js @@ -179,7 +179,7 @@ describe('fly: shopify', function() { it('deploys with --allow-updates', async function() { // Shopify CLI 4.x rejects --force - stub('shopify', 'case "$*" in *--force*) exit 2;; esac\n') + stub('shopify', 'case "$*" in *--help*) echo "--allow-updates";; *--force*) exit 2;; esac\n') await new GDF().run(workdir, { ...defaults, force: true }) @@ -193,6 +193,16 @@ describe('fly: shopify', function() { expect(config).to.include('"https://shopify-test.fly.dev/auth/callback"') }) + it('deploys with --force on Shopify CLI before 3.90', async function() { + stub('shopify', 'case "$*" in *--help*) echo "--force";; *--allow-updates*) exit 2;; esac\n') + + await new GDF().run(workdir, { ...defaults, force: true }) + + const calls = fs.readFileSync(log, 'utf-8') + expect(calls).to.include('shopify app deploy --force --config shopify.app.toml') + expect(calls).not.to.include('deploy --allow-updates') + }) + it('continues when shopify app deploy fails', async function() { stub('shopify', 'exit 1\n')