Skip to content

Commit 08e50fa

Browse files
committed
Add dry run feature
1 parent b18525f commit 08e50fa

File tree

5 files changed

+123
-10
lines changed

5 files changed

+123
-10
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ CLI sub-commands:
2424
npx wp-deployer --help
2525
npx wp-deployer --version
2626
npx wp-deployer --assets
27+
npx wp-deployer --dry-run
2728
```
2829

2930
Use `--assets` when you only want to push the assets directory (e.g. screenshots, banner) to WordPress.org and skip trunk and tag deployment.
3031

32+
Use `--dry-run` to run checkout, clear/copy, and local `svn add` / `svn delete` preparation only. No `svn commit` or remote tag copy runs, so nothing is pushed—useful to inspect the working copy before a real deploy.
33+
3134
Or add a script to `package.json` and run it:
3235

3336
```sh

index.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const exec = promisify(execCb)
2828
const __dirname = path.dirname(fileURLToPath(import.meta.url))
2929

3030
const argv = minimist(process.argv.slice(2), {
31-
boolean: ['help', 'version', 'assets'],
31+
boolean: ['help', 'version', 'assets', 'dry-run'],
3232
alias: { h: 'help', v: 'version' }
3333
})
3434

@@ -41,6 +41,7 @@ Options:
4141
--help, -h Show this message
4242
--version, -v Print wp-deployer version
4343
--assets Deploy only the assets directory (plugin only; skips trunk and tag)
44+
--dry-run Prepare working copy and local SVN changes only; no commit or remote tag copy
4445
`)
4546
}
4647

@@ -71,6 +72,10 @@ process.on('SIGINT', () => {
7172
})
7273

7374
const wpDeployer = async () => {
75+
const dryRun = Boolean(argv['dry-run'])
76+
if (dryRun) {
77+
console.log(chalk.yellow('Dry run: SVN checkout and local prep will run; commits and remote operations are skipped.'))
78+
}
7479
console.log(chalk.cyan('Processing...'))
7580

7681
let { settings, error, errorMessage } = resolveSettings(pkg)
@@ -130,7 +135,7 @@ const wpDeployer = async () => {
130135
throw e
131136
}
132137

133-
const helpers = { exec, fs, awk, noRunIfEmpty }
138+
const helpers = { exec, fs, awk, noRunIfEmpty, dryRun }
134139
const steps = settings.repoType === 'plugin'
135140
? createPluginSteps(settings, helpers)
136141
: createThemeSteps(settings, helpers)
@@ -140,7 +145,11 @@ const wpDeployer = async () => {
140145
for (const step of steps) {
141146
s = await step(s)
142147
}
143-
console.log(chalk.green('Finished successfully.'))
148+
if (dryRun) {
149+
console.log(chalk.green('Dry run finished — working copy prepared; no changes were pushed to WordPress.org.'))
150+
} else {
151+
console.log(chalk.green('Finished successfully.'))
152+
}
144153
return EXIT_SUCCESS
145154
} catch (err) {
146155
console.error(chalk.red(err?.message || err))

lib/steps.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function copyDirectory (fs, srcDir, destDir) {
1414
fs.copySync(srcDir, destDir)
1515
}
1616

17-
export function createPluginSteps (settings, { exec, fs, awk, noRunIfEmpty }) {
17+
export function createPluginSteps (settings, { exec, fs, awk, noRunIfEmpty, dryRun = false }) {
1818
const clearTrunk = (s) => {
1919
return async function (s) {
2020
console.log('Clearing trunk.')
@@ -111,19 +111,19 @@ export function createPluginSteps (settings, { exec, fs, awk, noRunIfEmpty }) {
111111
settings.deployTrunk ? clearTrunk(settings) : null,
112112
settings.deployTrunk ? copyBuild(settings) : null,
113113
settings.deployTrunk ? addFiles(settings) : null,
114-
settings.deployTrunk ? commitToTrunk(settings) : null,
115-
settings.deployTag ? commitTag(settings) : null,
114+
settings.deployTrunk && !dryRun ? commitToTrunk(settings) : null,
115+
settings.deployTag && !dryRun ? commitTag(settings) : null,
116116
settings.deployAssets ? checkoutDir('assets', settings) : null,
117117
settings.deployAssets ? clearAssets(settings) : null,
118118
settings.deployAssets ? copyAssets(settings) : null,
119119
settings.deployAssets ? addAssets(settings) : null,
120-
settings.deployAssets ? commitToAssets(settings) : null
120+
settings.deployAssets && !dryRun ? commitToAssets(settings) : null
121121
].filter(function (val) { return val !== null })
122122

123123
return steps
124124
}
125125

126-
export function createThemeSteps (settings, { exec, fs, awk, noRunIfEmpty }) {
126+
export function createThemeSteps (settings, { exec, fs, awk, noRunIfEmpty, dryRun = false }) {
127127
const prepareThemeTmp = (s) => {
128128
return async function (s) {
129129
console.log('Preparing temporary folder.')
@@ -195,8 +195,8 @@ export function createThemeSteps (settings, { exec, fs, awk, noRunIfEmpty }) {
195195
clearTheme(settings),
196196
copyTheme(settings),
197197
addThemeFiles(settings),
198-
commitTheme(settings)
199-
]
198+
dryRun ? null : commitTheme(settings)
199+
].filter(function (val) { return val !== null })
200200

201201
return steps
202202
}

test/cli-flags.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,10 @@ describe('CLI flags', () => {
4141
assert.strictEqual(r.status, 0, r.stderr)
4242
assert.match(r.stdout, /--assets\b/)
4343
})
44+
45+
it('--help documents --dry-run', () => {
46+
const r = spawnSync(process.execPath, [indexJs, '--help'], { encoding: 'utf8' })
47+
assert.strictEqual(r.status, 0, r.stderr)
48+
assert.match(r.stdout, /--dry-run\b/)
49+
})
4450
})

test/steps.test.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ describe('createPluginSteps', () => {
5656
})
5757
})
5858

59+
it('plugin dry-run omits commit and remote tag; still checks out, clears, adds', async () => {
60+
const fs = createRecordingFs()
61+
const exec = createRecordingExec()
62+
const steps = createPluginSteps(baseSettings, { exec, fs, awk, noRunIfEmpty, dryRun: true })
63+
let s = baseSettings
64+
for (const step of steps) {
65+
s = await step(s)
66+
}
67+
68+
const trunk = path.join(svnPath, 'trunk')
69+
const url = baseSettings.url
70+
const addCmd =
71+
'svn resolve --accept working -R . && svn status |awk \'/^[?]/{print $2}\' | xargs svn add;' +
72+
'svn status | awk \'/^[!]/{print $2}\' | xargs svn delete;'
73+
74+
assert.deepStrictEqual(exec.calls, [
75+
{
76+
cmd: `svn co --force-interactive --username="jane" ${url}trunk/ ${trunk}`,
77+
opts: { maxBuffer: baseSettings.maxBuffer }
78+
},
79+
{ cmd: `rm -fr ${trunk}/*`, opts: {} },
80+
{ cmd: addCmd, opts: { cwd: trunk } }
81+
])
82+
83+
assert.deepStrictEqual(fs.copies, [
84+
{ src: 'dist/', dest: path.join(svnPath, 'trunk') + path.sep }
85+
])
86+
})
87+
5988
it('full plugin (deployTrunk + deployTag) runs expected exec commands and cwd', async () => {
6089
const fs = createRecordingFs()
6190
const exec = createRecordingExec()
@@ -135,6 +164,39 @@ describe('createPluginSteps', () => {
135164
])
136165
})
137166

167+
it('plugin with deployAssets and dryRun skips trunk, tag, and assets commits', async () => {
168+
const fs = createRecordingFs()
169+
const exec = createRecordingExec()
170+
const settings = { ...baseSettings, deployAssets: true }
171+
const steps = createPluginSteps(settings, { exec, fs, awk, noRunIfEmpty, dryRun: true })
172+
let s = settings
173+
for (const step of steps) {
174+
s = await step(s)
175+
}
176+
177+
const trunk = path.join(svnPath, 'trunk')
178+
const assets = path.join(svnPath, 'assets')
179+
const url = settings.url
180+
const addCmd =
181+
'svn resolve --accept working -R . && svn status |awk \'/^[?]/{print $2}\' | xargs svn add;' +
182+
'svn status | awk \'/^[!]/{print $2}\' | xargs svn delete;'
183+
184+
assert.strictEqual(exec.calls.length, 6)
185+
assert.strictEqual(exec.calls[0].cmd, `svn co --force-interactive --username="jane" ${url}trunk/ ${trunk}`)
186+
assert.strictEqual(exec.calls[1].cmd, `rm -fr ${trunk}/*`)
187+
assert.strictEqual(exec.calls[2].cmd, addCmd)
188+
assert.strictEqual(exec.calls[2].opts.cwd, trunk)
189+
assert.strictEqual(exec.calls[3].cmd, `svn co --force-interactive --username="jane" ${url}assets/ ${assets}`)
190+
assert.strictEqual(exec.calls[4].cmd, `rm -fr ${assets}/*`)
191+
assert.strictEqual(exec.calls[5].cmd, addCmd)
192+
assert.strictEqual(exec.calls[5].opts.cwd, assets)
193+
194+
const noCommit = exec.calls.every((c) => !c.cmd.includes('svn commit'))
195+
assert.strictEqual(noCommit, true)
196+
const noRemoteTag = exec.calls.every((c) => !c.cmd.includes('tags/'))
197+
assert.strictEqual(noRemoteTag, true)
198+
})
199+
138200
it('plugin with deployAssets true includes assets checkout, copy, svn add, commit with cwd', async () => {
139201
const fs = createRecordingFs()
140202
const exec = createRecordingExec()
@@ -221,6 +283,39 @@ describe('createThemeSteps', () => {
221283
})
222284
})
223285

286+
it('theme dry-run omits final svn commit', async () => {
287+
const fs = createRecordingFs()
288+
const exec = createRecordingExec()
289+
const steps = createThemeSteps(baseSettings, { exec, fs, awk, noRunIfEmpty, dryRun: true })
290+
let s = baseSettings
291+
for (const step of steps) {
292+
s = await step(s)
293+
}
294+
295+
const verDir = path.join(svnPath, baseSettings.newVersion)
296+
const addCmd =
297+
'svn resolve --accept working -R . && svn status |awk \'/^[?]/{print $2}\' | xargs svn add;' +
298+
'svn status | awk \'/^[!]/{print $2}\' | xargs svn delete;'
299+
300+
assert.deepStrictEqual(exec.calls, [
301+
{ cmd: `rm -fr ${svnPath}`, opts: {} },
302+
{
303+
cmd: `svn co --force-interactive --username="jane" ${baseSettings.url}/ ${svnPath}`,
304+
opts: { maxBuffer: baseSettings.maxBuffer }
305+
},
306+
{
307+
cmd: 'svn copy 1.0.0 2.0.0',
308+
opts: { cwd: svnPath }
309+
},
310+
{ cmd: `rm -fr ${verDir}/*`, opts: {} },
311+
{ cmd: addCmd, opts: { cwd: verDir } }
312+
])
313+
314+
assert.deepStrictEqual(fs.copies, [
315+
{ src: 'dist/', dest: path.join(svnPath, '2.0.0') + path.sep }
316+
])
317+
})
318+
224319
it('theme pipeline runs expected exec commands, cwd, and copySync destinations', async () => {
225320
const fs = createRecordingFs()
226321
const exec = createRecordingExec()

0 commit comments

Comments
 (0)