Skip to content

Commit 1a18314

Browse files
committed
fix(dist): exclude maintainer-only files + add drift guard + changelog
The 10up SVN deploy action and `npm run plugin-zip` use opposite rules for what ships: 10up uses .distignore as an EXCLUDE list, plugin-zip uses package.json `files` as an INCLUDE list. Three top-level entries were tracked but listed in neither: CITATION.cff, eslint.config.cjs, and patterns/ (empty .gitkeep scaffold). They were absent from the local zip but shipped to SVN trunk in v1.0.3 (changeset 3534041). Three changes to converge the two paths and prevent the next leak: 1. Add the three offenders to .distignore. The next deploy will remove them from trunk via 10up's rsync --delete. 2. New bin/check-distignore.mjs script asserts every tracked top-level entry is in either package.json `files` OR matched by .distignore. Wired into lefthook pre-push and ci.yml so a missing rule fails at PR time, not after SVN deploy. Verified against the v1.0.3 leak set: the guard correctly flags all three files when the .distignore fix is reverted, clean when in place. 3. Populate readme.txt Changelog with 1.0.4, 1.0.3, 1.0.2 entries. The wp.org-visible history starts from the first published version; 1.0.0 (pre-rename) and 1.0.1 (pended) never reached users. Version stays at 1.0.3 in this commit. The automated release workflow bumps to 1.0.4 on next dispatch.
1 parent 8d01aca commit 1a18314

6 files changed

Lines changed: 144 additions & 32 deletions

File tree

.distignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
/README.md
3636
/CLAUDE.md
3737
/MAINTAINER.md
38+
/CITATION.cff
39+
/eslint.config.cjs
40+
/patterns
3841
/todo.md
3942
/specs
4043
/vendor

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
run: npm run generate
2323
- name: Fail if generated code is stale
2424
run: git diff --exit-code src/Generated blocks/generated
25+
- name: Fail if distribution drifts between local zip and SVN deploy
26+
run: node bin/check-distignore.mjs
2527

2628
lint:
2729
name: Lint and Plugin Check

bin/check-distignore.mjs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Distribution drift guard.
4+
*
5+
* Two release paths produce the published zip and they use opposite rules:
6+
*
7+
* - Local: `npm run plugin-zip` (wp-scripts plugin-zip → npm-packlist) ships
8+
* only entries listed in package.json `files`. Include-list semantics.
9+
* - CI: 10up/action-wordpress-plugin-deploy ships every top-level entry
10+
* except those matched by .distignore. Exclude-list semantics.
11+
*
12+
* Any top-level repo entry that is in neither list ships via 10up but is
13+
* absent from the local zip — silent drift that only surfaces after SVN
14+
* deploy. v1.0.3 leaked CITATION.cff, eslint.config.cjs, and patterns/
15+
* exactly this way.
16+
*
17+
* This script fails (exit 1) if any tracked top-level entry is neither
18+
* a member of the package.json `files` include-list nor matched by a
19+
* .distignore rule. Run before push via lefthook.
20+
*/
21+
import { execFileSync } from 'node:child_process';
22+
import { readFileSync } from 'node:fs';
23+
import { fileURLToPath } from 'node:url';
24+
import path from 'node:path';
25+
26+
const root = path.resolve(
27+
path.dirname( fileURLToPath( import.meta.url ) ),
28+
'..'
29+
);
30+
31+
const tracked = execFileSync( 'git', [ 'ls-files' ], {
32+
cwd: root,
33+
encoding: 'utf8',
34+
} )
35+
.split( '\n' )
36+
.filter( Boolean );
37+
38+
const topLevel = new Set();
39+
for ( const f of tracked ) {
40+
const slash = f.indexOf( '/' );
41+
topLevel.add( slash === -1 ? f : f.slice( 0, slash ) );
42+
}
43+
44+
const pkg = JSON.parse(
45+
readFileSync( path.join( root, 'package.json' ), 'utf8' )
46+
);
47+
const filesField = new Set(
48+
( pkg.files || [] ).map( ( e ) => e.replace( /\/$/, '' ) )
49+
);
50+
51+
const distignore = readFileSync( path.join( root, '.distignore' ), 'utf8' )
52+
.split( '\n' )
53+
.map( ( l ) => l.trim() )
54+
.filter( ( l ) => l && ! l.startsWith( '#' ) );
55+
56+
const distignoreLeading = new Set(
57+
distignore
58+
.filter( ( l ) => l.startsWith( '/' ) )
59+
.map( ( l ) => l.slice( 1 ) )
60+
);
61+
const distignoreGlobs = distignore.filter( ( l ) => l.includes( '*' ) );
62+
63+
function matchedByDistignore( entry ) {
64+
if ( distignoreLeading.has( entry ) ) {
65+
return true;
66+
}
67+
for ( const glob of distignoreGlobs ) {
68+
const pattern =
69+
'^' + glob.replace( /\./g, '\\.' ).replace( /\*/g, '.*' ) + '$';
70+
if ( new RegExp( pattern ).test( entry ) ) {
71+
return true;
72+
}
73+
}
74+
return false;
75+
}
76+
77+
const drift = [];
78+
for ( const entry of topLevel ) {
79+
const inFiles = filesField.has( entry ) || filesField.has( entry + '/' );
80+
const inIgnore = matchedByDistignore( entry );
81+
if ( ! inFiles && ! inIgnore ) {
82+
drift.push( entry );
83+
}
84+
}
85+
86+
if ( drift.length ) {
87+
console.error( '✗ Distribution drift detected.' );
88+
console.error( '' );
89+
console.error(
90+
'The following top-level entries are tracked in git but are:'
91+
);
92+
console.error(
93+
' - NOT listed in package.json `files` (so npm-packlist excludes them locally)'
94+
);
95+
console.error(
96+
' - NOT matched by any .distignore rule (so 10up rsync ships them to SVN)'
97+
);
98+
console.error( '' );
99+
console.error(
100+
'This produces a silent drift between `npm run plugin-zip` and the SVN deploy.'
101+
);
102+
console.error( '' );
103+
for ( const entry of drift.sort() ) {
104+
console.error( ` - ${ entry }` );
105+
}
106+
console.error( '' );
107+
console.error(
108+
'Fix by adding each to .distignore (if dev-only) or to package.json `files` (if shippable).'
109+
);
110+
process.exit( 1 );
111+
}
112+
113+
console.log(
114+
`✓ no distribution drift (${ topLevel.size } top-level entries audited)`
115+
);

lefthook.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ pre-push:
6262
run: npm run generate:check
6363
priority: 1
6464

65+
# Distribution drift: every tracked top-level entry must be in
66+
# package.json `files` (shipped by local zip) or .distignore
67+
# (excluded by 10up SVN deploy). Catches the v1.0.3 leak class.
68+
distignore-drift-check:
69+
run: node bin/check-distignore.mjs
70+
priority: 1
71+
6572
# Full format pass (auto-fix). PHP first, then JS/CSS so each tool
6673
# only touches files it owns.
6774
php-format:

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"wp-env": "wp-env",
4545
"generate": "node bin/generate.mjs",
4646
"generate:check": "node bin/generate.mjs && git diff --exit-code src/Generated blocks/generated",
47+
"check:distignore": "node bin/check-distignore.mjs",
4748
"upgrade": "npx npm-check-updates --target minor --upgrade",
4849
"check-outdated": "npx npm-check-updates"
4950
},

readme.txt

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -130,40 +130,24 @@ Hero block editor sidebars are intentionally minimal in this release. Live previ
130130

131131
== Changelog ==
132132

133-
= 1.0.2 =
134-
* Block registration no longer calls a WordPress 6.7 only function, keeping the plugin compatible with the stated 6.5 minimum.
133+
= 1.0.4 =
134+
* Tidy distribution: drop maintainer-only files (CITATION.cff, eslint.config.cjs, empty patterns scaffold) that were leaking into the published zip.
135135

136-
= 1.0.1 =
137-
* Display name now leads with the RoxyAPI brand.
138-
* Form result lookups use an opaque token so visitor input never reaches the transient key.
136+
= 1.0.3 =
137+
* Sync to the latest RoxyAPI spec, now 133 endpoints across 10 spiritual domains.
138+
* Western astrology: detect aspect patterns (Grand Trine, Kite, T-Square, Grand Cross, Yod, Mystic Rectangle, Stellium) via new [roxy_detect_aspect_patterns] shortcode + matching block.
139+
* Vedic astrology: detect classical Vedic yogas in a birth chart via new [roxy_detect_yogas] shortcode + matching block.
140+
* Natal chart, transits, and aspect calculations now include Black Moon Lilith alongside the lunar nodes and Chiron for full 14-body planetary coverage.
141+
* Yoga catalog entries reworded for clearer glossary phrasing in both the list and the per-yoga detail responses.
139142

140-
= 1.0.0 =
141-
* Initial release. 131 endpoints across 10 spiritual domains under one API key.
142-
* 17 hero shortcodes:
143-
- Western astrology: Horoscope (daily, weekly, monthly), Natal chart, Synastry, Compatibility, Moon phase.
144-
- Vedic astrology: Kundli, Panchang, Mangal Dosha, KP chart, Gun Milan (Ashtakoota matrimonial).
145-
- Tarot: Tarot card (daily, three card, Celtic Cross), Tarot yes or no.
146-
- Numerology: Numerology chart, Life path.
147-
- Plus Biorhythm, Angel number, Crystals by zodiac.
148-
* Form mode on every hero. Drop the shortcode with no attributes and visitors fill in the form themselves; submission is server side, the API key never reaches the browser.
149-
* Two-chart heroes (Synastry, Gun Milan, Compatibility) are form-only because static mode would require ten plus inline attributes; the form has Person 1 / Person 2 fieldsets with city autocomplete.
150-
* 117 auto generated shortcodes for the long tail. Generated from the live OpenAPI spec via npm run generate.
151-
* Hero shortcode attribute names aligned with the documented examples so a copy-paste from the admin onboarding page works first try.
152-
* Horoscope block ships three real variations (daily, weekly, monthly) wired to the matching SaaS endpoints; the period attribute now actually dispatches.
153-
* Auto detecting form mode on every hero shortcode. Drop the shortcode with no attributes and visitors submit their own sign, name, birth date, or question.
154-
* GDPR Article 9 consent gate on every visitor form. Submission requires an explicit opt in checkbox; the plugin registers privacy policy content via wp_add_privacy_policy_content for the WordPress Privacy Policy Guide.
155-
* City autocomplete for natal chart and synastry forms. ARIA 1.2 combobox proxied through /wp-json/roxyapi/v1/geocode so the API key never reaches the browser.
156-
* Top level RoxyAPI menu in the admin sidebar with a tabbed settings page (Connect, Branding, Display, Privacy, Advanced) and a 3 step onboarding flow for first time users.
157-
* Branding controls: accent color, opt in source line under each reading.
158-
* Display controls: default response language, optional disclaimer line.
159-
* Advanced controls: cache preset (fresh, balanced, quota saver) on top of per endpoint TTLs.
160-
* Dashboard widget showing connection status and the most used shortcodes with copy to clipboard.
161-
* Settings API key field with wp config constant override and an inline Test Connection button.
162-
* Encryption at rest via AES 256 CTR. Returns false on missing keys instead of falling back to a hardcoded secret.
163-
* Server side caching with per endpoint TTL via WordPress transients (Redis and Memcached compatible automatically).
164-
* Rate limiting per IP to protect the site owner API quota, applied to form submissions, the Test Connection button, and the geocoder proxy.
165-
* Block Bindings API source roxyapi/daily-text. Bind a paragraph to it with a sign argument to render the daily overview inline.
166-
* X-SDK-Client and User-Agent headers matching the TypeScript and Python SDK pattern so RoxyAPI can identify plugin traffic.
143+
= 1.0.2 =
144+
* Initial public release on the WordPress Plugin Directory.
145+
* 131 endpoints across 10 spiritual domains under one RoxyAPI key. 17 hero shortcodes with matching Gutenberg blocks (Western astrology, Vedic astrology, tarot, numerology, biorhythm, angel numbers, crystals) plus 117 auto-generated long-tail shortcodes for the full spec.
146+
* Compatible with the declared WordPress 6.5 minimum: block registration no longer calls a WordPress 6.7 only function.
147+
* Form mode on every hero shortcode: drop with no attributes and visitors fill the form themselves. Server-side submission, API key never reaches the browser, GDPR Article 9 consent gate, per-IP rate limit.
148+
* Two-chart heroes (Synastry, Gun Milan, Compatibility) ship form-only with Person 1 / Person 2 fieldsets and ARIA 1.2 combobox city autocomplete via a key-protected /wp-json/roxyapi/v1/geocode proxy.
149+
* Tabbed admin settings (Connect, Branding, Display, Privacy, Advanced) with accent color, response-language picker, disclaimer line, cache preset (fresh / balanced / quota saver), and inline Test Connection button. API key supports a ROXYAPI_KEY wp-config constant override.
150+
* Encryption at rest via AES 256 CTR. Server-side caching with per-endpoint TTL via WordPress transients (Redis / Memcached compatible). Block Bindings API source roxyapi/daily-text for inline horoscope binding.
167151

168152
== Upgrade Notice ==
169153

0 commit comments

Comments
 (0)