wp-advads 1.0.31 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +17 -0
- package/CHANGELOG.md +23 -1
- package/LICENSE +15 -0
- package/README.md +8 -7
- package/package.json +9 -7
- package/src/app.js +17 -11
- package/src/commands/build.js +72 -59
- package/src/commands/class.js +34 -40
- package/src/commands/css.js +13 -19
- package/src/commands/init.js +4 -1
- package/src/commands/javascript.js +13 -19
- package/src/commands/release/change-version.js +14 -17
- package/src/commands/release/changelog.js +23 -31
- package/src/commands/release/git.js +25 -13
- package/src/commands/release/github.js +63 -12
- package/src/commands/release/semver.js +22 -25
- package/src/commands/release/translations.js +17 -23
- package/src/commands/release.js +3 -4
- package/src/commands/updates.js +20 -17
- package/src/commands/views.js +16 -18
- package/src/commands/wp-glotpress.js +125 -126
- package/src/commands/wp-pot.js +17 -9
- package/src/utilities/filesystem.js +57 -36
- package/src/utilities/flow.js +21 -0
- package/src/utilities/formatting.js +13 -4
- package/src/utilities/index.js +1 -1
- package/src/utilities/misc.js +1 -6
- package/src/utilities/settings.js +34 -14
- package/src/utilities/shell.js +60 -8
- package/template/files/class-singleton.php +5 -0
- package/template/files/javascript.js +0 -2
- package/template/files/view.php +2 -0
- package/test/build.test.js +168 -0
- package/test/changelog-locales.test.js +49 -0
- package/test/class.test.js +47 -0
- package/test/compile-templates.test.js +70 -0
- package/test/deps-hygiene.test.js +46 -0
- package/test/engines.test.js +11 -0
- package/test/get-latest-tag.test.js +51 -0
- package/test/git-precheck.test.js +44 -0
- package/test/github-final.test.js +93 -0
- package/test/glotpress-download.test.js +176 -0
- package/test/nested-path.test.js +39 -0
- package/test/no-fs-extra.test.js +11 -0
- package/test/package-identity.test.js +19 -0
- package/test/path-confine.test.js +42 -0
- package/test/pot-docs.test.js +27 -0
- package/test/pot-headers.test.js +49 -0
- package/test/project-root.test.js +26 -0
- package/test/release-waterfall.test.js +62 -0
- package/test/semver.test.js +25 -0
- package/test/settings-clone.test.js +34 -0
- package/test/settings-path.test.js +31 -0
- package/test/shell.test.js +100 -0
- package/test/singleton.test.js +14 -0
- package/test/stable-tag.test.js +13 -0
- package/test/stub-security.test.js +37 -0
- package/test/template-folder.test.js +19 -0
- package/test/translations.test.js +51 -0
- package/test/update-file-content.test.js +38 -0
- package/test/updates.test.js +39 -0
- package/test/version.test.js +22 -0
- package/test/view-escape.test.js +17 -0
- package/test/view.test.js +33 -0
- package/test/write-force.test.js +47 -0
- package/src/utilities/semver.js +0 -45
- package/template/.github/pull_request_template.md +0 -52
- package/template/configs/.editorconfig +0 -27
- package/template/configs/.eslintignore +0 -13
- package/template/configs/.eslintrc.js +0 -13
- package/template/configs/.phpcs.xml.dist +0 -87
- package/template/configs/.prettierignore +0 -49
- package/template/configs/.prettierrc.js +0 -3
- package/template/configs/.stylelintrc +0 -44
- package/template/configs/composer.json +0 -51
- package/template/configs/gitattributes +0 -34
- package/template/configs/gitignore +0 -43
- package/template/configs/package.json +0 -66
- package/template/configs/postcss.config.js +0 -6
- package/template/configs/tailwind.config.js +0 -8
- package/template/configs/webpack.mix.js +0 -65
- package/template/configs/webpack.mix.local.js +0 -3
- package/template/includes/index.php +0 -2
- package/template/index.js +0 -0
- package/template/index.php +0 -4
- package/template/plugin/assets/scss/app.scss +0 -0
- package/template/plugin/assets/src/app.js +0 -0
- package/template/tools/laravel-mix/wp-pot.js +0 -57
- package/template/tools/wp-glotpress.js +0 -162
package/src/utilities/shell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { spawn } from 'node:child_process'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal Dependencies
|
|
@@ -19,10 +19,20 @@ import { onSameLine } from './formatting.js'
|
|
|
19
19
|
*/
|
|
20
20
|
export function runCommand( command, args, callback ) {
|
|
21
21
|
const commandSpawn = spawn( command, args, {
|
|
22
|
-
shell:
|
|
23
|
-
stdio: [ 'inherit' ]
|
|
22
|
+
shell: false,
|
|
23
|
+
stdio: [ 'inherit', 'pipe', 'pipe' ]
|
|
24
24
|
} )
|
|
25
25
|
|
|
26
|
+
let finished = false
|
|
27
|
+
const done = ( err ) => {
|
|
28
|
+
if ( finished ) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
finished = true
|
|
33
|
+
callback( err )
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
commandSpawn.stdout.on( 'data', (data) => {
|
|
27
37
|
onSameLine(data.toString())
|
|
28
38
|
})
|
|
@@ -31,23 +41,65 @@ export function runCommand( command, args, callback ) {
|
|
|
31
41
|
onSameLine(data.toString().replace('\n','').replace('\r','').trim())
|
|
32
42
|
})
|
|
33
43
|
|
|
44
|
+
commandSpawn.on( 'error', done )
|
|
45
|
+
|
|
34
46
|
commandSpawn.on( 'close', (code) => {
|
|
35
|
-
|
|
47
|
+
if ( code !== 0 ) {
|
|
48
|
+
done( new Error( `Command failed with exit code ${code}` ) )
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
done()
|
|
36
53
|
})
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
/**
|
|
40
57
|
* Execute a command in the terminal.
|
|
41
58
|
*
|
|
42
|
-
* @param {string} command Command to execute
|
|
59
|
+
* @param {string|string[]} command Command to execute, or argv array
|
|
43
60
|
* @param {Function} callback Callback function
|
|
61
|
+
* @param {Object} options cwd/env for spawn
|
|
44
62
|
*
|
|
45
63
|
* @returns {void}
|
|
46
64
|
*/
|
|
47
65
|
export function execCommand( command, callback, options = {} ) {
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
const spawnOpts = {
|
|
67
|
+
cwd: options.cwd,
|
|
68
|
+
env: options.env,
|
|
69
|
+
stdio: [ 'ignore', 'pipe', 'pipe' ],
|
|
50
70
|
}
|
|
51
71
|
|
|
52
|
-
|
|
72
|
+
const child = Array.isArray( command )
|
|
73
|
+
? spawn( command[0], command.slice( 1 ), { ...spawnOpts, shell: false } )
|
|
74
|
+
: spawn( command, { ...spawnOpts, shell: true } )
|
|
75
|
+
|
|
76
|
+
let stdout = ''
|
|
77
|
+
let stderr = ''
|
|
78
|
+
let finished = false
|
|
79
|
+
const done = ( error ) => {
|
|
80
|
+
if ( finished ) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
finished = true
|
|
85
|
+
callback( stdout, error, stderr )
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
child.stdout.on( 'data', ( data ) => {
|
|
89
|
+
const text = data.toString()
|
|
90
|
+
stdout += text
|
|
91
|
+
onSameLine( text )
|
|
92
|
+
} )
|
|
93
|
+
|
|
94
|
+
child.stderr.on( 'data', ( data ) => {
|
|
95
|
+
const text = data.toString()
|
|
96
|
+
stderr += text
|
|
97
|
+
onSameLine( text.replace( '\n', '' ).replace( '\r', '' ).trim() )
|
|
98
|
+
} )
|
|
99
|
+
|
|
100
|
+
child.on( 'error', done )
|
|
101
|
+
|
|
102
|
+
child.on( 'close', ( code ) => {
|
|
103
|
+
done( code ? new Error( `Command failed with exit code ${code}` ) : null )
|
|
104
|
+
} )
|
|
53
105
|
}
|
package/template/files/view.php
CHANGED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-002 / TASK-020: build waterfall must stop on composer / resource / rmSync failure.
|
|
3
|
+
* Resource build uses package.json scripts.build when present.
|
|
4
|
+
* Run with --experimental-test-module-mocks
|
|
5
|
+
*/
|
|
6
|
+
import assert from 'node:assert/strict'
|
|
7
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
import { mock, test, before, beforeEach } from 'node:test'
|
|
11
|
+
import fs from 'node:fs'
|
|
12
|
+
|
|
13
|
+
const execCalls = []
|
|
14
|
+
let execImpl = ( cmd, cb ) => cb( '', null, '' )
|
|
15
|
+
let potCalled = false
|
|
16
|
+
|
|
17
|
+
mock.module( import.meta.resolve( '../src/utilities/shell.js' ), {
|
|
18
|
+
exports: {
|
|
19
|
+
execCommand( cmd, cb, options = {} ) {
|
|
20
|
+
execCalls.push( { cmd, options } )
|
|
21
|
+
execImpl( cmd, cb, options )
|
|
22
|
+
},
|
|
23
|
+
runCommand() {},
|
|
24
|
+
},
|
|
25
|
+
} )
|
|
26
|
+
|
|
27
|
+
mock.module( import.meta.resolve( '../src/commands/wp-pot.js' ), {
|
|
28
|
+
exports: {
|
|
29
|
+
default( next ) {
|
|
30
|
+
potCalled = true
|
|
31
|
+
next()
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} )
|
|
35
|
+
|
|
36
|
+
const { default: wpBuild } = await import( '../src/commands/build.js' )
|
|
37
|
+
const { pluginData } = await import( '../src/commands/release.js' )
|
|
38
|
+
|
|
39
|
+
function runBuild() {
|
|
40
|
+
return new Promise( ( resolve ) => {
|
|
41
|
+
wpBuild( ( err ) => resolve( err ) )
|
|
42
|
+
} )
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function withPackage( pkg, run ) {
|
|
46
|
+
const dir = await mkdtemp( join( tmpdir(), 'wp-advads-build-' ) )
|
|
47
|
+
await writeFile( join( dir, 'package.json' ), JSON.stringify( pkg ) )
|
|
48
|
+
const cwd = process.cwd()
|
|
49
|
+
process.chdir( dir )
|
|
50
|
+
try {
|
|
51
|
+
return await run()
|
|
52
|
+
} finally {
|
|
53
|
+
process.chdir( cwd )
|
|
54
|
+
await rm( dir, { recursive: true, force: true } )
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
before( () => {
|
|
59
|
+
mock.method( fs, 'rmSync', () => {} )
|
|
60
|
+
} )
|
|
61
|
+
|
|
62
|
+
beforeEach( () => {
|
|
63
|
+
execCalls.length = 0
|
|
64
|
+
potCalled = false
|
|
65
|
+
execImpl = ( cmd, cb ) => cb( '', null, '' )
|
|
66
|
+
fs.rmSync.mock.mockImplementation( () => {} )
|
|
67
|
+
pluginData.commitMessages = []
|
|
68
|
+
} )
|
|
69
|
+
|
|
70
|
+
test( 'composer install error stops the build before dump and pot', async () => {
|
|
71
|
+
execImpl = ( cmd, cb ) => {
|
|
72
|
+
if ( String( cmd ).includes( 'composer install' ) ) {
|
|
73
|
+
cb( '', new Error( 'install failed' ), 'install exploded' )
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
cb( '', null, '' )
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const err = await runBuild()
|
|
80
|
+
|
|
81
|
+
assert.ok( err, 'expected the build callback to receive an error' )
|
|
82
|
+
assert.equal( potCalled, false )
|
|
83
|
+
assert.equal(
|
|
84
|
+
execCalls.some( ( call ) => call.cmd === 'composer dump' ),
|
|
85
|
+
false
|
|
86
|
+
)
|
|
87
|
+
} )
|
|
88
|
+
|
|
89
|
+
test( 'npm run build error stops the build before pot', async () => {
|
|
90
|
+
await withPackage( { scripts: { build: 'mix --production' } }, async () => {
|
|
91
|
+
execImpl = ( cmd, cb ) => {
|
|
92
|
+
if ( String( cmd ).includes( 'npm run build' ) ) {
|
|
93
|
+
cb( '', new Error( 'build failed' ), 'mix exploded' )
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
cb( '', null, '' )
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const err = await runBuild()
|
|
100
|
+
|
|
101
|
+
assert.ok( err )
|
|
102
|
+
assert.equal( potCalled, false )
|
|
103
|
+
} )
|
|
104
|
+
} )
|
|
105
|
+
|
|
106
|
+
test( 'resource build runs npm run build instead of wp-scripts', async () => {
|
|
107
|
+
await withPackage( { scripts: { build: 'mix --production' } }, async () => {
|
|
108
|
+
await runBuild()
|
|
109
|
+
|
|
110
|
+
assert.equal(
|
|
111
|
+
execCalls.some( ( call ) => String( call.cmd ).includes( 'wp-scripts' ) ),
|
|
112
|
+
false
|
|
113
|
+
)
|
|
114
|
+
assert.equal(
|
|
115
|
+
execCalls.some( ( call ) => call.cmd === 'npm run build' ),
|
|
116
|
+
true
|
|
117
|
+
)
|
|
118
|
+
} )
|
|
119
|
+
} )
|
|
120
|
+
|
|
121
|
+
test( 'resource build is skipped when package.json has no build script', async () => {
|
|
122
|
+
await withPackage( { scripts: { start: 'node x' } }, async () => {
|
|
123
|
+
await runBuild()
|
|
124
|
+
|
|
125
|
+
assert.equal(
|
|
126
|
+
execCalls.some( ( call ) => String( call.cmd ).includes( 'wp-scripts' ) ),
|
|
127
|
+
false
|
|
128
|
+
)
|
|
129
|
+
assert.equal(
|
|
130
|
+
execCalls.some( ( call ) => String( call.cmd ).includes( 'npm run build' ) ),
|
|
131
|
+
false
|
|
132
|
+
)
|
|
133
|
+
assert.equal( potCalled, true )
|
|
134
|
+
} )
|
|
135
|
+
} )
|
|
136
|
+
|
|
137
|
+
test( 'composer dump receives COMPOSER_VENDOR_DIR packages', async () => {
|
|
138
|
+
await runBuild()
|
|
139
|
+
|
|
140
|
+
const dump = execCalls.find( ( call ) => call.cmd === 'composer dump' )
|
|
141
|
+
assert.ok( dump, 'expected composer dump to run' )
|
|
142
|
+
assert.equal( dump.options.env.COMPOSER_VENDOR_DIR, 'packages' )
|
|
143
|
+
} )
|
|
144
|
+
|
|
145
|
+
test( 'rmSync failure stops the build before composer', async () => {
|
|
146
|
+
fs.rmSync.mock.mockImplementation( () => {
|
|
147
|
+
throw new Error( 'EACCES' )
|
|
148
|
+
} )
|
|
149
|
+
|
|
150
|
+
const err = await runBuild()
|
|
151
|
+
|
|
152
|
+
assert.ok( err )
|
|
153
|
+
assert.equal( execCalls.length, 0 )
|
|
154
|
+
assert.equal( potCalled, false )
|
|
155
|
+
} )
|
|
156
|
+
|
|
157
|
+
test( 'standalone build does not mutate pluginData.commitMessages', async () => {
|
|
158
|
+
await runBuild()
|
|
159
|
+
assert.equal( pluginData.commitMessages.length, 0 )
|
|
160
|
+
} )
|
|
161
|
+
|
|
162
|
+
test( 'build appends messages to an optional sink', async () => {
|
|
163
|
+
const sink = []
|
|
164
|
+
await new Promise( ( resolve ) => {
|
|
165
|
+
wpBuild( () => resolve(), sink )
|
|
166
|
+
} )
|
|
167
|
+
assert.ok( sink.includes( 'update 3rd party dependencies' ) )
|
|
168
|
+
} )
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-012: changelog lists git files from glotpress.destination, not pluginData.
|
|
3
|
+
* Run with --test-isolation=none --experimental-test-module-mocks
|
|
4
|
+
*/
|
|
5
|
+
import assert from 'node:assert/strict'
|
|
6
|
+
import { mkdir } from 'node:fs/promises'
|
|
7
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
import { mock, test } from 'node:test'
|
|
11
|
+
|
|
12
|
+
const execCalls = []
|
|
13
|
+
|
|
14
|
+
mock.module( import.meta.resolve( '../src/utilities/shell.js' ), {
|
|
15
|
+
exports: {
|
|
16
|
+
execCommand( cmd, cb ) {
|
|
17
|
+
execCalls.push( cmd )
|
|
18
|
+
cb( 'languages/myplugin-de_DE.po\n', null, '' )
|
|
19
|
+
},
|
|
20
|
+
runCommand() {},
|
|
21
|
+
},
|
|
22
|
+
} )
|
|
23
|
+
|
|
24
|
+
const { pluginData } = await import( '../src/commands/release.js' )
|
|
25
|
+
const { default: updateChangelog } = await import( '../src/commands/release/changelog.js' )
|
|
26
|
+
|
|
27
|
+
test( 'updateLocalesChangelog uses glotpress.destination instead of unset pluginData.glotpress', async () => {
|
|
28
|
+
const dir = await mkdtemp( join( tmpdir(), 'wp-advads-changelog-' ) )
|
|
29
|
+
const cwd = process.cwd()
|
|
30
|
+
process.chdir( dir )
|
|
31
|
+
await mkdir( join( dir, '.changelog' ) )
|
|
32
|
+
|
|
33
|
+
pluginData.semver = { preRelease: true, getNextVersionString: () => '1.2.3' }
|
|
34
|
+
pluginData.locales = { 'languages/myplugin-de_DE.po': 'German' }
|
|
35
|
+
delete pluginData.glotpress
|
|
36
|
+
execCalls.length = 0
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const err = await new Promise( ( resolve ) => {
|
|
40
|
+
updateChangelog( ( error ) => resolve( error ) )
|
|
41
|
+
} )
|
|
42
|
+
|
|
43
|
+
assert.equal( err, undefined )
|
|
44
|
+
assert.deepEqual( execCalls[ 0 ], [ 'git', 'ls-files', '--', './languages/' ] )
|
|
45
|
+
} finally {
|
|
46
|
+
process.chdir( cwd )
|
|
47
|
+
await rm( dir, { recursive: true, force: true } )
|
|
48
|
+
}
|
|
49
|
+
} )
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-008: class -i must still win when --heading is also set.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import { mock, test } from 'node:test'
|
|
9
|
+
|
|
10
|
+
const runCalls = []
|
|
11
|
+
|
|
12
|
+
mock.module( import.meta.resolve( '../src/utilities/shell.js' ), {
|
|
13
|
+
exports: {
|
|
14
|
+
execCommand() {},
|
|
15
|
+
runCommand( ...args ) {
|
|
16
|
+
runCalls.push( args )
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
} )
|
|
20
|
+
|
|
21
|
+
const { default: classFile } = await import( '../src/commands/class.js' )
|
|
22
|
+
|
|
23
|
+
test( 'class command uses the initializer template when heading and -i are both set', async () => {
|
|
24
|
+
const dir = await mkdtemp( join( tmpdir(), 'wp-advads-class-' ) )
|
|
25
|
+
const cwd = process.cwd()
|
|
26
|
+
process.chdir( dir )
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await writeFile( join( dir, 'wp.advads' ), JSON.stringify( {
|
|
30
|
+
paths: { php: 'includes' },
|
|
31
|
+
wp: { version: '1.0.0' },
|
|
32
|
+
misc: { package: 'AdvancedAds' },
|
|
33
|
+
author: { name: 'Test', email: 'test@example.com' },
|
|
34
|
+
} ) )
|
|
35
|
+
|
|
36
|
+
classFile( 'Foo_Bar', { heading: 'Class Heading', i: true } )
|
|
37
|
+
|
|
38
|
+
const php = await readFile( join( dir, 'includes', 'class-foo-bar.php' ), 'utf8' )
|
|
39
|
+
assert.match( php, /Initializer_Interface/ )
|
|
40
|
+
assert.match( php, /Class Heading/ )
|
|
41
|
+
assert.doesNotMatch( php, /\{\{/ )
|
|
42
|
+
assert.equal( runCalls.length, 0 )
|
|
43
|
+
} finally {
|
|
44
|
+
process.chdir( cwd )
|
|
45
|
+
await rm( dir, { recursive: true, force: true } )
|
|
46
|
+
}
|
|
47
|
+
} )
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-033 / TASK-058: stubs compile; view/update use init config plus command fields.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { readdirSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { test } from 'node:test'
|
|
8
|
+
import { compileTemplate, getTemplateFolder } from '../src/utilities/filesystem.js'
|
|
9
|
+
|
|
10
|
+
const fixture = {
|
|
11
|
+
heading: 'Heading',
|
|
12
|
+
description: 'Description',
|
|
13
|
+
className: 'Foo_Bar',
|
|
14
|
+
namespace: '',
|
|
15
|
+
prefix: 'advads',
|
|
16
|
+
version: '1_2_3',
|
|
17
|
+
wp: { version: '1.0.0' },
|
|
18
|
+
misc: { package: 'AdvancedAds', prefix: 'advads' },
|
|
19
|
+
author: { name: 'Test', email: 'test@example.com' },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const initShape = {
|
|
23
|
+
wp: { version: '1.0.0', description: 'Plugin description' },
|
|
24
|
+
misc: { package: 'AdvancedAds', prefix: 'advads' },
|
|
25
|
+
author: { name: 'Test', email: 'test@example.com' },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test( 'every template/files stub compiles without leftover placeholders', () => {
|
|
29
|
+
const dir = join( getTemplateFolder(), 'files' )
|
|
30
|
+
const files = readdirSync( dir ).filter( ( file ) => ! file.startsWith( '.' ) )
|
|
31
|
+
|
|
32
|
+
assert.ok( files.includes( 'view.php' ) )
|
|
33
|
+
assert.ok( files.includes( 'update.php' ) )
|
|
34
|
+
|
|
35
|
+
for ( const file of files ) {
|
|
36
|
+
const out = compileTemplate( join( dir, file ), fixture )
|
|
37
|
+
assert.doesNotMatch( out, /\{\{/, file )
|
|
38
|
+
}
|
|
39
|
+
} )
|
|
40
|
+
|
|
41
|
+
test( 'view.php does not compile from init config without a description field', () => {
|
|
42
|
+
assert.throws(
|
|
43
|
+
() => compileTemplate( join( getTemplateFolder(), 'files/view.php' ), {
|
|
44
|
+
...initShape,
|
|
45
|
+
heading: 'Sidebar template file',
|
|
46
|
+
} ),
|
|
47
|
+
/description/i
|
|
48
|
+
)
|
|
49
|
+
} )
|
|
50
|
+
|
|
51
|
+
test( 'view and update compile from init config plus command fields', () => {
|
|
52
|
+
const dir = join( getTemplateFolder(), 'files' )
|
|
53
|
+
|
|
54
|
+
const view = compileTemplate( join( dir, 'view.php' ), {
|
|
55
|
+
...initShape,
|
|
56
|
+
heading: 'Sidebar template file',
|
|
57
|
+
description: 'Sidebar template file',
|
|
58
|
+
} )
|
|
59
|
+
assert.doesNotMatch( view, /\{\{/ )
|
|
60
|
+
|
|
61
|
+
const update = compileTemplate( join( dir, 'update.php' ), {
|
|
62
|
+
...initShape,
|
|
63
|
+
heading: 'Update routine for version 1.2.3',
|
|
64
|
+
description: 'Update routine for version 1.2.3',
|
|
65
|
+
prefix: initShape.misc.prefix,
|
|
66
|
+
version: '1_2_3',
|
|
67
|
+
} )
|
|
68
|
+
assert.doesNotMatch( update, /\{\{/ )
|
|
69
|
+
assert.match( update, /function advads_upgrade_1_2_3/ )
|
|
70
|
+
} )
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-043 / 044 / 048 / 049 / 050: ignore junk, no jquery stub, lodash only for template, no async pkg, no deleteFile.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { readFileSync } from 'node:fs'
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
|
|
8
|
+
test( 'gitignore ignores .DS_Store', () => {
|
|
9
|
+
const gitignore = readFileSync( new URL( '../.gitignore', import.meta.url ), 'utf8' )
|
|
10
|
+
assert.match( gitignore, /^\.DS_Store$/m )
|
|
11
|
+
} )
|
|
12
|
+
|
|
13
|
+
test( 'javascript stub does not import jquery', () => {
|
|
14
|
+
const js = readFileSync( new URL( '../template/files/javascript.js', import.meta.url ), 'utf8' )
|
|
15
|
+
assert.doesNotMatch( js, /jquery/i )
|
|
16
|
+
} )
|
|
17
|
+
|
|
18
|
+
test( 'lodash is only used for template compile', () => {
|
|
19
|
+
const pkg = JSON.parse( readFileSync( new URL( '../package.json', import.meta.url ), 'utf8' ) )
|
|
20
|
+
assert.ok( pkg.dependencies.lodash )
|
|
21
|
+
assert.doesNotMatch( readFileSync( new URL( '../src/utilities/settings.js', import.meta.url ), 'utf8' ), /lodash/ )
|
|
22
|
+
assert.doesNotMatch( readFileSync( new URL( '../src/utilities/misc.js', import.meta.url ), 'utf8' ), /lodash/ )
|
|
23
|
+
assert.doesNotMatch( readFileSync( new URL( '../src/commands/init.js', import.meta.url ), 'utf8' ), /lodash/ )
|
|
24
|
+
assert.match( readFileSync( new URL( '../src/utilities/filesystem.js', import.meta.url ), 'utf8' ), /lodash\/template/ )
|
|
25
|
+
} )
|
|
26
|
+
|
|
27
|
+
test( 'package.json does not depend on async', () => {
|
|
28
|
+
const pkg = JSON.parse( readFileSync( new URL( '../package.json', import.meta.url ), 'utf8' ) )
|
|
29
|
+
assert.equal( pkg.dependencies.async, undefined )
|
|
30
|
+
} )
|
|
31
|
+
|
|
32
|
+
test( 'filesystem does not export deleteFile', () => {
|
|
33
|
+
const src = readFileSync( new URL( '../src/utilities/filesystem.js', import.meta.url ), 'utf8' )
|
|
34
|
+
assert.doesNotMatch( src, /deleteFile/ )
|
|
35
|
+
} )
|
|
36
|
+
|
|
37
|
+
test( 'scaffold commands share writeFromTemplate', () => {
|
|
38
|
+
assert.match( readFileSync( new URL( '../src/utilities/filesystem.js', import.meta.url ), 'utf8' ), /export function writeFromTemplate/ )
|
|
39
|
+
for ( const file of [ 'css.js', 'javascript.js', 'views.js', 'class.js', 'updates.js' ] ) {
|
|
40
|
+
assert.match(
|
|
41
|
+
readFileSync( new URL( `../src/commands/${file}`, import.meta.url ), 'utf8' ),
|
|
42
|
+
/writeFromTemplate/,
|
|
43
|
+
file
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
} )
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-042: npm should warn on Node older than 18.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { readFileSync } from 'node:fs'
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
|
|
8
|
+
test( 'package.json engines.node is >=18', () => {
|
|
9
|
+
const pkg = JSON.parse( readFileSync( new URL( '../package.json', import.meta.url ), 'utf8' ) )
|
|
10
|
+
assert.equal( pkg.engines?.node, '>=18' )
|
|
11
|
+
} )
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-054: getLatestTag must treat execCommand error; only "No names found" falls back.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import { mock, test } from 'node:test'
|
|
9
|
+
|
|
10
|
+
let execImpl = ( cmd, cb ) => cb( '', null, '' )
|
|
11
|
+
|
|
12
|
+
mock.module( import.meta.resolve( '../src/utilities/shell.js' ), {
|
|
13
|
+
exports: {
|
|
14
|
+
execCommand( cmd, cb ) {
|
|
15
|
+
execImpl( cmd, cb )
|
|
16
|
+
},
|
|
17
|
+
runCommand() {},
|
|
18
|
+
},
|
|
19
|
+
} )
|
|
20
|
+
|
|
21
|
+
const { getLatestTag } = await import( '../src/commands/release/git.js' )
|
|
22
|
+
|
|
23
|
+
test( 'getLatestTag rejects when git describe errors', async () => {
|
|
24
|
+
execImpl = ( cmd, cb ) => cb( '', new Error( 'failed' ), 'fatal: not a git repository' )
|
|
25
|
+
|
|
26
|
+
await assert.rejects( getLatestTag() )
|
|
27
|
+
} )
|
|
28
|
+
|
|
29
|
+
test( 'getLatestTag uses the described tag when git succeeds', async () => {
|
|
30
|
+
execImpl = ( cmd, cb ) => cb( 'v1.2.3\n', null, '' )
|
|
31
|
+
|
|
32
|
+
assert.equal( await getLatestTag(), '1.2.3' )
|
|
33
|
+
} )
|
|
34
|
+
|
|
35
|
+
test( 'getLatestTag falls back to wp.version when there are no tags', async () => {
|
|
36
|
+
execImpl = ( cmd, cb ) => {
|
|
37
|
+
cb( '', new Error( 'failed' ), 'fatal: No names found, cannot describe anything.' )
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const dir = await mkdtemp( join( tmpdir(), 'wp-advads-tag-' ) )
|
|
41
|
+
const cwd = process.cwd()
|
|
42
|
+
await writeFile( join( dir, 'wp.advads' ), JSON.stringify( { wp: { version: '3.4.5' } } ) )
|
|
43
|
+
process.chdir( dir )
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
assert.equal( await getLatestTag(), '3.4.5' )
|
|
47
|
+
} finally {
|
|
48
|
+
process.chdir( cwd )
|
|
49
|
+
await rm( dir, { recursive: true, force: true } )
|
|
50
|
+
}
|
|
51
|
+
} )
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TASK-055: hasPermission / gitFetch must treat error first and not crash on non-string stderr.
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import { mock, test } from 'node:test'
|
|
6
|
+
|
|
7
|
+
let execImpl = ( cmd, cb ) => cb( '', null, '' )
|
|
8
|
+
|
|
9
|
+
mock.module( import.meta.resolve( '../src/utilities/shell.js' ), {
|
|
10
|
+
exports: {
|
|
11
|
+
execCommand( cmd, cb ) {
|
|
12
|
+
execImpl( cmd, cb )
|
|
13
|
+
},
|
|
14
|
+
runCommand() {},
|
|
15
|
+
},
|
|
16
|
+
} )
|
|
17
|
+
|
|
18
|
+
const { hasPermission, gitFetch } = await import( '../src/commands/release/git.js' )
|
|
19
|
+
|
|
20
|
+
function run( fn ) {
|
|
21
|
+
return new Promise( ( resolve, reject ) => {
|
|
22
|
+
try {
|
|
23
|
+
fn( ( err ) => resolve( err ) )
|
|
24
|
+
} catch ( err ) {
|
|
25
|
+
reject( err )
|
|
26
|
+
}
|
|
27
|
+
} )
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test( 'hasPermission fails closed when error is set and stderr is missing', async () => {
|
|
31
|
+
execImpl = ( cmd, cb ) => cb( '', new Error( 'spawn failed' ), undefined )
|
|
32
|
+
|
|
33
|
+
const err = await run( hasPermission )
|
|
34
|
+
|
|
35
|
+
assert.ok( err )
|
|
36
|
+
} )
|
|
37
|
+
|
|
38
|
+
test( 'gitFetch fails closed when error is set and stderr is empty', async () => {
|
|
39
|
+
execImpl = ( cmd, cb ) => cb( '', new Error( 'spawn failed' ), '' )
|
|
40
|
+
|
|
41
|
+
const err = await run( gitFetch )
|
|
42
|
+
|
|
43
|
+
assert.ok( err )
|
|
44
|
+
} )
|