@sociallane/elements 1.0.13 → 1.0.14
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/npm-npx-gates.yml +58 -0
- package/package.json +6 -1
- package/packages/widget-manifest.json +291 -0
- package/scripts/build-widget-packages.js +5 -16
- package/scripts/generate-widget-manifest.js +19 -0
- package/scripts/generate-widget-table.js +63 -0
- package/scripts/install.js +194 -17
- package/scripts/lib/widgets-manifest.js +163 -0
- package/scripts/publish-widget-packages.js +21 -3
- package/scripts/release-gate.js +200 -0
- package/scripts/sync-widgets.js +7 -15
- package/scripts/test-cli-smoke.js +141 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: SocialLane npm/npx Gates
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- 'sociallane-elements/**'
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
paths:
|
|
11
|
+
- 'sociallane-elements/**'
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
cli-smoke:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
defaults:
|
|
18
|
+
run:
|
|
19
|
+
working-directory: sociallane-elements
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Setup Node
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '20'
|
|
28
|
+
|
|
29
|
+
- name: CLI smoke tests
|
|
30
|
+
run: node scripts/test-cli-smoke.js
|
|
31
|
+
|
|
32
|
+
- name: List command sanity check
|
|
33
|
+
run: node scripts/install.js list --json
|
|
34
|
+
|
|
35
|
+
- name: Doctor command sanity check
|
|
36
|
+
run: node scripts/install.js doctor --json --target .
|
|
37
|
+
|
|
38
|
+
release-gate:
|
|
39
|
+
if: github.event_name == 'workflow_dispatch'
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
needs: cli-smoke
|
|
42
|
+
defaults:
|
|
43
|
+
run:
|
|
44
|
+
working-directory: sociallane-elements
|
|
45
|
+
steps:
|
|
46
|
+
- name: Checkout
|
|
47
|
+
uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- name: Setup Node
|
|
50
|
+
uses: actions/setup-node@v4
|
|
51
|
+
with:
|
|
52
|
+
node-version: '20'
|
|
53
|
+
|
|
54
|
+
- name: Sync canonical widget manifest
|
|
55
|
+
run: node scripts/generate-widget-manifest.js
|
|
56
|
+
|
|
57
|
+
- name: Release gate (strict)
|
|
58
|
+
run: node scripts/release-gate.js --strict
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sociallane/elements",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Elementor widgets and elements with Tailwind CSS for SocialLane. WordPress plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -30,8 +30,13 @@
|
|
|
30
30
|
"postinstall": "node scripts/postinstall.js",
|
|
31
31
|
"setup": "bash scripts/setup.sh",
|
|
32
32
|
"sync-widgets": "node scripts/sync-widgets.js",
|
|
33
|
+
"manifest:widgets": "node scripts/generate-widget-manifest.js",
|
|
34
|
+
"generate:widget-table": "node scripts/generate-widget-table.js",
|
|
33
35
|
"build:widget-packages": "node scripts/build-widget-packages.js",
|
|
34
36
|
"publish:widgets": "node scripts/build-widget-packages.js && node scripts/publish-widget-packages.js",
|
|
37
|
+
"test:cli-smoke": "node scripts/test-cli-smoke.js",
|
|
38
|
+
"release:check": "node scripts/release-gate.js --strict",
|
|
39
|
+
"release:check:local": "node scripts/release-gate.js --strict --skip-registry",
|
|
35
40
|
"reinstall": "rm -rf node_modules && npm install",
|
|
36
41
|
"format": "prettier --write .",
|
|
37
42
|
"format:check": "prettier --check ."
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"generated_at": "2026-02-10T09:16:15.290Z",
|
|
4
|
+
"source": "packages/widgets",
|
|
5
|
+
"widgets": [
|
|
6
|
+
{
|
|
7
|
+
"slug": "bento-grid",
|
|
8
|
+
"package": "@sociallane/widget-bento-grid"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"slug": "bento-grid-4",
|
|
12
|
+
"package": "@sociallane/widget-bento-grid-4"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"slug": "bento-grid-5",
|
|
16
|
+
"package": "@sociallane/widget-bento-grid-5"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"slug": "bento-grid-6",
|
|
20
|
+
"package": "@sociallane/widget-bento-grid-6"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"slug": "bento-portfolio",
|
|
24
|
+
"package": "@sociallane/widget-bento-portfolio"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"slug": "bento-portfolio-4",
|
|
28
|
+
"package": "@sociallane/widget-bento-portfolio-4"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"slug": "bento-portfolio-5",
|
|
32
|
+
"package": "@sociallane/widget-bento-portfolio-5"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"slug": "bento-portfolio-6",
|
|
36
|
+
"package": "@sociallane/widget-bento-portfolio-6"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"slug": "blog-grid",
|
|
40
|
+
"package": "@sociallane/widget-blog-grid"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"slug": "card-hover-reveal",
|
|
44
|
+
"package": "@sociallane/widget-card-hover-reveal"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"slug": "client-logos",
|
|
48
|
+
"package": "@sociallane/widget-client-logos"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"slug": "content-block",
|
|
52
|
+
"package": "@sociallane/widget-content-block"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"slug": "cta-banner",
|
|
56
|
+
"package": "@sociallane/widget-cta-banner"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"slug": "cta-notify",
|
|
60
|
+
"package": "@sociallane/widget-cta-notify"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"slug": "cta-split",
|
|
64
|
+
"package": "@sociallane/widget-cta-split"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"slug": "faq-centered",
|
|
68
|
+
"package": "@sociallane/widget-faq-centered"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"slug": "faq-split",
|
|
72
|
+
"package": "@sociallane/widget-faq-split"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"slug": "faq-stacked",
|
|
76
|
+
"package": "@sociallane/widget-faq-stacked"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"slug": "feature-grid",
|
|
80
|
+
"package": "@sociallane/widget-feature-grid"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"slug": "feature-grid-centered",
|
|
84
|
+
"package": "@sociallane/widget-feature-grid-centered"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"slug": "feature-list",
|
|
88
|
+
"package": "@sociallane/widget-feature-list"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"slug": "feature-list-cta",
|
|
92
|
+
"package": "@sociallane/widget-feature-list-cta"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"slug": "footer",
|
|
96
|
+
"package": "@sociallane/widget-footer"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"slug": "footer-brand",
|
|
100
|
+
"package": "@sociallane/widget-footer-brand"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"slug": "footer-links-contact",
|
|
104
|
+
"package": "@sociallane/widget-footer-links-contact"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"slug": "form-contact",
|
|
108
|
+
"package": "@sociallane/widget-form-contact"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"slug": "grid-case-studies",
|
|
112
|
+
"package": "@sociallane/widget-grid-case-studies"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"slug": "grid-components",
|
|
116
|
+
"package": "@sociallane/widget-grid-components"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"slug": "grid-team",
|
|
120
|
+
"package": "@sociallane/widget-grid-team"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"slug": "hero-announcement",
|
|
124
|
+
"package": "@sociallane/widget-hero-announcement"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"slug": "hero-centered-image",
|
|
128
|
+
"package": "@sociallane/widget-hero-centered-image"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"slug": "hero-collage",
|
|
132
|
+
"package": "@sociallane/widget-hero-collage"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"slug": "hero-overlay",
|
|
136
|
+
"package": "@sociallane/widget-hero-overlay"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"slug": "hero-overlay-single",
|
|
140
|
+
"package": "@sociallane/widget-hero-overlay-single"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"slug": "hero-overlay-slider",
|
|
144
|
+
"package": "@sociallane/widget-hero-overlay-slider"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"slug": "hero-saas-centered",
|
|
148
|
+
"package": "@sociallane/widget-hero-saas-centered"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"slug": "hero-saas-split",
|
|
152
|
+
"package": "@sociallane/widget-hero-saas-split"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"slug": "hero-saas-stacked",
|
|
156
|
+
"package": "@sociallane/widget-hero-saas-stacked"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"slug": "hero-split",
|
|
160
|
+
"package": "@sociallane/widget-hero-split"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"slug": "hero-stacked-image",
|
|
164
|
+
"package": "@sociallane/widget-hero-stacked-image"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"slug": "intro-pattern",
|
|
168
|
+
"package": "@sociallane/widget-intro-pattern"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"slug": "intro-text",
|
|
172
|
+
"package": "@sociallane/widget-intro-text"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"slug": "logo-grid-centered",
|
|
176
|
+
"package": "@sociallane/widget-logo-grid-centered"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"slug": "logo-grid-row",
|
|
180
|
+
"package": "@sociallane/widget-logo-grid-row"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"slug": "logo-grid-split",
|
|
184
|
+
"package": "@sociallane/widget-logo-grid-split"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"slug": "nav-centered",
|
|
188
|
+
"package": "@sociallane/widget-nav-centered"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"slug": "nav-compact",
|
|
192
|
+
"package": "@sociallane/widget-nav-compact"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"slug": "nav-default",
|
|
196
|
+
"package": "@sociallane/widget-nav-default"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"slug": "nav-floating",
|
|
200
|
+
"package": "@sociallane/widget-nav-floating"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"slug": "nav-minimal",
|
|
204
|
+
"package": "@sociallane/widget-nav-minimal"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"slug": "newsletter",
|
|
208
|
+
"package": "@sociallane/widget-newsletter"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"slug": "newsletter-card",
|
|
212
|
+
"package": "@sociallane/widget-newsletter-card"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"slug": "newsletter-section",
|
|
216
|
+
"package": "@sociallane/widget-newsletter-section"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"slug": "outreach-dashboard",
|
|
220
|
+
"package": "@sociallane/widget-outreach-dashboard"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"slug": "page-hero-center",
|
|
224
|
+
"package": "@sociallane/widget-page-hero-center"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"slug": "page-hero-left",
|
|
228
|
+
"package": "@sociallane/widget-page-hero-left"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"slug": "pipeline-dashboard",
|
|
232
|
+
"package": "@sociallane/widget-pipeline-dashboard"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"slug": "posts-grid",
|
|
236
|
+
"package": "@sociallane/widget-posts-grid"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"slug": "posts-grid-overlay",
|
|
240
|
+
"package": "@sociallane/widget-posts-grid-overlay"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"slug": "pricing-table",
|
|
244
|
+
"package": "@sociallane/widget-pricing-table"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"slug": "sales-dashboard",
|
|
248
|
+
"package": "@sociallane/widget-sales-dashboard"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"slug": "section-stats",
|
|
252
|
+
"package": "@sociallane/widget-section-stats"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"slug": "services",
|
|
256
|
+
"package": "@sociallane/widget-services"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"slug": "simple-page-hero",
|
|
260
|
+
"package": "@sociallane/widget-simple-page-hero"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"slug": "social-proof",
|
|
264
|
+
"package": "@sociallane/widget-social-proof"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"slug": "social-proof-trust",
|
|
268
|
+
"package": "@sociallane/widget-social-proof-trust"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"slug": "testimonial-quote",
|
|
272
|
+
"package": "@sociallane/widget-testimonial-quote"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"slug": "testimonials-bento",
|
|
276
|
+
"package": "@sociallane/widget-testimonials-bento"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"slug": "testimonials-grid",
|
|
280
|
+
"package": "@sociallane/widget-testimonials-grid"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"slug": "testimonials-masonry",
|
|
284
|
+
"package": "@sociallane/widget-testimonials-masonry"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"slug": "widget-filter",
|
|
288
|
+
"package": "@sociallane/widget-widget-filter"
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
@@ -5,27 +5,15 @@
|
|
|
5
5
|
* Run from plugin root: npm run build:widget-packages
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { cpSync, existsSync, mkdirSync,
|
|
8
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { getWidgetSourceDir, syncWidgetManifest } from './lib/widgets-manifest.js';
|
|
11
12
|
|
|
12
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const pluginRoot = path.resolve(__dirname, '..');
|
|
14
|
-
const widgetsDir = path.join(pluginRoot, 'packages', 'widgets');
|
|
15
15
|
const npmWidgetsDir = path.join(pluginRoot, 'packages', 'npm-widgets');
|
|
16
|
-
|
|
17
|
-
function getWidgetSlugs() {
|
|
18
|
-
if (!existsSync(widgetsDir)) return [];
|
|
19
|
-
const entries = readdirSync(widgetsDir, { withFileTypes: true });
|
|
20
|
-
const slugs = [];
|
|
21
|
-
for (const ent of entries) {
|
|
22
|
-
if (!ent.isDirectory()) continue;
|
|
23
|
-
const slug = ent.name;
|
|
24
|
-
const phpFile = path.join(widgetsDir, slug, `${slug}.php`);
|
|
25
|
-
if (existsSync(phpFile)) slugs.push(slug);
|
|
26
|
-
}
|
|
27
|
-
return slugs.sort((a, b) => a.localeCompare(b));
|
|
28
|
-
}
|
|
16
|
+
const widgetsDir = getWidgetSourceDir(pluginRoot);
|
|
29
17
|
|
|
30
18
|
function getRootVersion() {
|
|
31
19
|
const pkgPath = path.join(pluginRoot, 'package.json');
|
|
@@ -79,7 +67,8 @@ This installs the SocialLane Elements plugin if needed, then adds this widget an
|
|
|
79
67
|
}
|
|
80
68
|
|
|
81
69
|
function main() {
|
|
82
|
-
const
|
|
70
|
+
const manifest = syncWidgetManifest(pluginRoot);
|
|
71
|
+
const slugs = manifest.widgets.map((widget) => widget.slug);
|
|
83
72
|
const version = getRootVersion();
|
|
84
73
|
if (!existsSync(npmWidgetsDir)) mkdirSync(npmWidgetsDir, { recursive: true });
|
|
85
74
|
for (const slug of slugs) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate packages/widget-manifest.json from widget sources on disk.
|
|
4
|
+
* Run from plugin root: npm run manifest:widgets
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { syncWidgetManifest, getWidgetManifestPath } from './lib/widgets-manifest.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pluginRoot = path.resolve(__dirname, '..');
|
|
13
|
+
|
|
14
|
+
const manifest = syncWidgetManifest(pluginRoot);
|
|
15
|
+
const manifestPath = getWidgetManifestPath(pluginRoot);
|
|
16
|
+
|
|
17
|
+
console.log(
|
|
18
|
+
`Widget manifest updated: ${manifest.widgets.length} widgets (${manifest.source}) -> ${manifestPath}`
|
|
19
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate a markdown table from the canonical widget manifest.
|
|
4
|
+
* Prints to stdout by default, or write to --out <path>.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { loadWidgetManifest } from './lib/widgets-manifest.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pluginRoot = path.resolve(__dirname, '..');
|
|
14
|
+
|
|
15
|
+
function parseOutPath(args) {
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
if (args[i] !== '--out') continue;
|
|
18
|
+
const value = args[i + 1];
|
|
19
|
+
if (!value || value.startsWith('-')) {
|
|
20
|
+
console.error('Missing value for --out');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildMarkdownTable(manifest) {
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push('| Widget | Package | Command |');
|
|
31
|
+
lines.push('|--------|---------|---------|');
|
|
32
|
+
|
|
33
|
+
for (const widget of manifest.widgets) {
|
|
34
|
+
lines.push(
|
|
35
|
+
`| ${widget.slug} | \`${widget.package}\` | \`npm install ${widget.package}\` |`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join('\n') + '\n';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function main() {
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
const out = parseOutPath(args);
|
|
45
|
+
const manifest = loadWidgetManifest(pluginRoot);
|
|
46
|
+
const table = buildMarkdownTable(manifest);
|
|
47
|
+
|
|
48
|
+
if (!out) {
|
|
49
|
+
process.stdout.write(table);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const outPath = path.resolve(process.cwd(), out);
|
|
54
|
+
const outDir = path.dirname(outPath);
|
|
55
|
+
if (!existsSync(outDir)) {
|
|
56
|
+
mkdirSync(outDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
writeFileSync(outPath, table, 'utf8');
|
|
60
|
+
console.log(`Widget table generated: ${outPath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main();
|
package/scripts/install.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Install (base): npx @sociallane/elements --base [path]
|
|
6
6
|
* Install (full): npx @sociallane/elements [path]
|
|
7
7
|
* Add widgets: npx @sociallane/elements add <slug> [slug...]
|
|
8
|
+
* List widgets: npx @sociallane/elements list [--json]
|
|
9
|
+
* Doctor checks: npx @sociallane/elements doctor [--target <path>] [--json]
|
|
8
10
|
*
|
|
9
11
|
* Install: copies package to wp-content/plugins/sociallane-elements (or path), runs npm install.
|
|
10
12
|
* Base install: installs core/deps and keeps widgets.json empty (no widgets loaded by default).
|
|
@@ -15,6 +17,7 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync
|
|
|
15
17
|
import { spawnSync } from 'child_process';
|
|
16
18
|
import path from 'path';
|
|
17
19
|
import { fileURLToPath } from 'url';
|
|
20
|
+
import { getWidgetSourceDir, loadWidgetManifest } from './lib/widgets-manifest.js';
|
|
18
21
|
|
|
19
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
23
|
const packageRoot = path.resolve(__dirname, '..');
|
|
@@ -35,6 +38,8 @@ function printHelp() {
|
|
|
35
38
|
console.log('Usage:');
|
|
36
39
|
console.log(' npx @sociallane/elements [--base|--minimal] [target]');
|
|
37
40
|
console.log(' npx @sociallane/elements add [--only] [--target <path>] <slug> [slug...]');
|
|
41
|
+
console.log(' npx @sociallane/elements list [--json]');
|
|
42
|
+
console.log(' npx @sociallane/elements doctor [--target <path>] [--json]');
|
|
38
43
|
console.log(' npx @sociallane/elements --help');
|
|
39
44
|
console.log(' npx @sociallane/elements --version');
|
|
40
45
|
console.log('');
|
|
@@ -42,6 +47,7 @@ function printHelp() {
|
|
|
42
47
|
console.log(' --base, --minimal Install core/deps only, keep widgets.json empty');
|
|
43
48
|
console.log(' --only With add: replace widgets.json with exactly provided slugs');
|
|
44
49
|
console.log(' --target <path> With add: explicit plugin directory target');
|
|
50
|
+
console.log(' --json With list/doctor: output machine-readable JSON');
|
|
45
51
|
console.log(' --help Show this help text');
|
|
46
52
|
console.log(' --version Show CLI version');
|
|
47
53
|
console.log('');
|
|
@@ -49,6 +55,8 @@ function printHelp() {
|
|
|
49
55
|
console.log(' npx @sociallane/elements --base');
|
|
50
56
|
console.log(' npx @sociallane/elements add faq-stacked hero-split');
|
|
51
57
|
console.log(' npx @sociallane/elements add --only --target wp-content/plugins/sociallane-elements content-block');
|
|
58
|
+
console.log(' npx @sociallane/elements list');
|
|
59
|
+
console.log(' npx @sociallane/elements doctor --target wp-content/plugins/sociallane-elements');
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
function resolvePluginDir(targetPath = '') {
|
|
@@ -79,23 +87,7 @@ function resolvePluginDir(targetPath = '') {
|
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
function getWidgetsSourceDir() {
|
|
82
|
-
|
|
83
|
-
path.join(packageRoot, 'packages', 'widgets'),
|
|
84
|
-
path.join(packageRoot, 'widgets'),
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
for (const dir of candidates) {
|
|
88
|
-
if (!existsSync(dir)) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const available = readdirSync(dir, { withFileTypes: true })
|
|
92
|
-
.filter((d) => d.isDirectory() && existsSync(path.join(dir, d.name, `${d.name}.php`)))
|
|
93
|
-
.map((d) => d.name);
|
|
94
|
-
if (available.length > 0) {
|
|
95
|
-
return dir;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return candidates[0];
|
|
90
|
+
return getWidgetSourceDir(packageRoot);
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
function updateWidgetsJson(pluginDir, slugsToAdd, replaceAll = false) {
|
|
@@ -196,6 +188,144 @@ function addWidgets(slugs, options = {}) {
|
|
|
196
188
|
console.log('Done. Widgets added:', slugs.join(', '));
|
|
197
189
|
}
|
|
198
190
|
|
|
191
|
+
function listWidgets(options = {}) {
|
|
192
|
+
const asJson = options.asJson === true;
|
|
193
|
+
const manifest = loadWidgetManifest(packageRoot);
|
|
194
|
+
const slugs = manifest.widgets.map((widget) => widget.slug);
|
|
195
|
+
|
|
196
|
+
if (asJson) {
|
|
197
|
+
console.log(
|
|
198
|
+
JSON.stringify(
|
|
199
|
+
{
|
|
200
|
+
count: slugs.length,
|
|
201
|
+
source: manifest.source || null,
|
|
202
|
+
widgets: slugs,
|
|
203
|
+
},
|
|
204
|
+
null,
|
|
205
|
+
2
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (slugs.length === 0) {
|
|
212
|
+
console.log('No widgets found.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log(slugs.join('\n'));
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(`Total: ${slugs.length}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function checkCommandVersion(command, args = ['--version']) {
|
|
222
|
+
const result = spawnSync(command, args, {
|
|
223
|
+
cwd: process.cwd(),
|
|
224
|
+
encoding: 'utf8',
|
|
225
|
+
shell: true,
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
ok: result.status === 0,
|
|
229
|
+
output: (result.stdout || result.stderr || '').trim(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function runDoctor(options = {}) {
|
|
234
|
+
const asJson = options.asJson === true;
|
|
235
|
+
const targetPath = options.targetPath || '';
|
|
236
|
+
const targetPlugin = resolvePluginDir(targetPath);
|
|
237
|
+
const checks = [];
|
|
238
|
+
|
|
239
|
+
const nodeVersion = process.version || '';
|
|
240
|
+
checks.push({
|
|
241
|
+
name: 'Node.js',
|
|
242
|
+
ok: nodeVersion !== '',
|
|
243
|
+
detail: nodeVersion || 'not detected',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
247
|
+
const npm = checkCommandVersion(npmCmd);
|
|
248
|
+
checks.push({
|
|
249
|
+
name: 'npm',
|
|
250
|
+
ok: npm.ok,
|
|
251
|
+
detail: npm.ok ? npm.output : `not available (${npm.output || 'command failed'})`,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const pluginExists = existsSync(targetPlugin);
|
|
255
|
+
checks.push({
|
|
256
|
+
name: 'Plugin directory',
|
|
257
|
+
ok: pluginExists,
|
|
258
|
+
detail: targetPlugin,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const bootstrapPath = path.join(targetPlugin, 'sociallane-elements.php');
|
|
262
|
+
checks.push({
|
|
263
|
+
name: 'Plugin bootstrap',
|
|
264
|
+
ok: existsSync(bootstrapPath),
|
|
265
|
+
detail: bootstrapPath,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const corePath = path.join(targetPlugin, 'packages', 'core');
|
|
269
|
+
checks.push({
|
|
270
|
+
name: 'Core package',
|
|
271
|
+
ok: existsSync(corePath),
|
|
272
|
+
detail: corePath,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const widgetsPath = path.join(targetPlugin, 'packages', 'widgets');
|
|
276
|
+
const legacyWidgetsPath = path.join(targetPlugin, 'widgets');
|
|
277
|
+
checks.push({
|
|
278
|
+
name: 'Widgets source',
|
|
279
|
+
ok: existsSync(widgetsPath) || existsSync(legacyWidgetsPath),
|
|
280
|
+
detail: existsSync(widgetsPath) ? widgetsPath : legacyWidgetsPath,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const widgetsJsonPath = path.join(targetPlugin, 'widgets.json');
|
|
284
|
+
let widgetsJsonValid = false;
|
|
285
|
+
let widgetsCount = 0;
|
|
286
|
+
if (existsSync(widgetsJsonPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const raw = readFileSync(widgetsJsonPath, 'utf8');
|
|
289
|
+
const parsed = JSON.parse(raw);
|
|
290
|
+
widgetsJsonValid = Array.isArray(parsed?.widgets);
|
|
291
|
+
if (widgetsJsonValid) {
|
|
292
|
+
widgetsCount = parsed.widgets.length;
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
widgetsJsonValid = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
checks.push({
|
|
299
|
+
name: 'widgets.json',
|
|
300
|
+
ok: existsSync(widgetsJsonPath) && widgetsJsonValid,
|
|
301
|
+
detail: existsSync(widgetsJsonPath)
|
|
302
|
+
? `${widgetsJsonPath} (${widgetsCount} widgets)`
|
|
303
|
+
: `${widgetsJsonPath} (missing)`,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const summary = {
|
|
307
|
+
target: targetPlugin,
|
|
308
|
+
ok: checks.every((check) => check.ok),
|
|
309
|
+
checks,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (asJson) {
|
|
313
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
314
|
+
} else {
|
|
315
|
+
console.log('SocialLane Elements doctor');
|
|
316
|
+
console.log(`Target: ${summary.target}`);
|
|
317
|
+
console.log('');
|
|
318
|
+
for (const check of checks) {
|
|
319
|
+
const status = check.ok ? 'OK' : 'FAIL';
|
|
320
|
+
console.log(`[${status}] ${check.name}: ${check.detail}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!summary.ok) {
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
199
329
|
function prepareBaseMode(pluginDir) {
|
|
200
330
|
const widgetsDest = path.join(pluginDir, 'packages', 'widgets');
|
|
201
331
|
if (!existsSync(widgetsDest)) {
|
|
@@ -334,6 +464,53 @@ function main() {
|
|
|
334
464
|
return;
|
|
335
465
|
}
|
|
336
466
|
|
|
467
|
+
if (args[0] === 'list') {
|
|
468
|
+
const listArgs = args.slice(1).filter(Boolean);
|
|
469
|
+
const knownListFlags = new Set(['--json']);
|
|
470
|
+
for (const arg of listArgs) {
|
|
471
|
+
if (!arg.startsWith('-')) {
|
|
472
|
+
console.error('Usage: npx @sociallane/elements list [--json]');
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
if (!knownListFlags.has(arg)) {
|
|
476
|
+
console.error('Unknown option for list:', arg);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
listWidgets({ asJson: listArgs.includes('--json') });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (args[0] === 'doctor') {
|
|
485
|
+
const doctorArgs = args.slice(1).filter(Boolean);
|
|
486
|
+
const knownDoctorFlags = new Set(['--target', '--json']);
|
|
487
|
+
let targetPath = '';
|
|
488
|
+
for (let i = 0; i < doctorArgs.length; i++) {
|
|
489
|
+
const arg = doctorArgs[i];
|
|
490
|
+
if (arg === '--target') {
|
|
491
|
+
const next = doctorArgs[i + 1];
|
|
492
|
+
if (!next || next.startsWith('-')) {
|
|
493
|
+
console.error('Missing value for --target');
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
targetPath = next;
|
|
497
|
+
i++;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (arg.startsWith('-')) {
|
|
501
|
+
if (!knownDoctorFlags.has(arg)) {
|
|
502
|
+
console.error('Unknown option for doctor:', arg);
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
console.error('Usage: npx @sociallane/elements doctor [--target <path>] [--json]');
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
runDoctor({ targetPath, asJson: doctorArgs.includes('--json') });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
337
514
|
if (args[0] === 'add') {
|
|
338
515
|
const addArgs = args.slice(1).filter(Boolean);
|
|
339
516
|
const knownAddFlags = new Set(['--only', '--target']);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export const WIDGET_MANIFEST_RELATIVE_PATH = 'packages/widget-manifest.json';
|
|
5
|
+
|
|
6
|
+
function normalizeSlug(value) {
|
|
7
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function uniqueSortedSlugs(slugs) {
|
|
11
|
+
return [...new Set(slugs.map(normalizeSlug).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeSourcePath(pluginRoot, sourceDir) {
|
|
15
|
+
const relative = path.relative(pluginRoot, sourceDir).split(path.sep).join('/');
|
|
16
|
+
return relative === '' ? '.' : relative;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeManifestWidget(entry) {
|
|
20
|
+
if (typeof entry === 'string') {
|
|
21
|
+
const slug = normalizeSlug(entry);
|
|
22
|
+
if (!slug) return null;
|
|
23
|
+
return { slug, package: `@sociallane/widget-${slug}` };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!entry || typeof entry !== 'object') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const slug = normalizeSlug(entry.slug);
|
|
31
|
+
if (!slug) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pkg =
|
|
36
|
+
typeof entry.package === 'string' && entry.package.trim() !== ''
|
|
37
|
+
? entry.package.trim()
|
|
38
|
+
: `@sociallane/widget-${slug}`;
|
|
39
|
+
|
|
40
|
+
return { slug, package: pkg };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getWidgetSourceDir(pluginRoot) {
|
|
44
|
+
const candidates = [path.join(pluginRoot, 'packages', 'widgets'), path.join(pluginRoot, 'widgets')];
|
|
45
|
+
|
|
46
|
+
for (const dir of candidates) {
|
|
47
|
+
if (!existsSync(dir)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const hasWidgets = readdirSync(dir, { withFileTypes: true }).some((entry) => {
|
|
52
|
+
if (!entry.isDirectory()) return false;
|
|
53
|
+
return existsSync(path.join(dir, entry.name, `${entry.name}.php`));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (hasWidgets) {
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const dir of candidates) {
|
|
62
|
+
if (existsSync(dir)) {
|
|
63
|
+
return dir;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return candidates[0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getWidgetSlugsFromSource(pluginRoot) {
|
|
71
|
+
const sourceDir = getWidgetSourceDir(pluginRoot);
|
|
72
|
+
if (!existsSync(sourceDir)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const slugs = readdirSync(sourceDir, { withFileTypes: true })
|
|
77
|
+
.filter((entry) => entry.isDirectory() && existsSync(path.join(sourceDir, entry.name, `${entry.name}.php`)))
|
|
78
|
+
.map((entry) => entry.name);
|
|
79
|
+
|
|
80
|
+
return uniqueSortedSlugs(slugs);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildWidgetManifest(pluginRoot) {
|
|
84
|
+
const sourceDir = getWidgetSourceDir(pluginRoot);
|
|
85
|
+
const slugs = getWidgetSlugsFromSource(pluginRoot);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
schema_version: 1,
|
|
89
|
+
generated_at: new Date().toISOString(),
|
|
90
|
+
source: normalizeSourcePath(pluginRoot, sourceDir),
|
|
91
|
+
widgets: slugs.map((slug) => ({
|
|
92
|
+
slug,
|
|
93
|
+
package: `@sociallane/widget-${slug}`,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getWidgetManifestPath(pluginRoot) {
|
|
99
|
+
return path.join(pluginRoot, WIDGET_MANIFEST_RELATIVE_PATH);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function readWidgetManifest(pluginRoot) {
|
|
103
|
+
const manifestPath = getWidgetManifestPath(pluginRoot);
|
|
104
|
+
if (!existsSync(manifestPath)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const raw = readFileSync(manifestPath, 'utf8');
|
|
110
|
+
const parsed = JSON.parse(raw);
|
|
111
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const widgets = Array.isArray(parsed.widgets) ? parsed.widgets.map(normalizeManifestWidget).filter(Boolean) : [];
|
|
116
|
+
const uniqueSlugs = uniqueSortedSlugs(widgets.map((widget) => widget.slug));
|
|
117
|
+
const widgetsBySlug = new Map(widgets.map((widget) => [widget.slug, widget.package]));
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
schema_version: typeof parsed.schema_version === 'number' ? parsed.schema_version : 1,
|
|
121
|
+
generated_at: typeof parsed.generated_at === 'string' ? parsed.generated_at : null,
|
|
122
|
+
source: typeof parsed.source === 'string' ? parsed.source : null,
|
|
123
|
+
widgets: uniqueSlugs.map((slug) => ({
|
|
124
|
+
slug,
|
|
125
|
+
package: widgetsBySlug.get(slug) || `@sociallane/widget-${slug}`,
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function writeWidgetManifest(pluginRoot, manifest) {
|
|
134
|
+
const manifestPath = getWidgetManifestPath(pluginRoot);
|
|
135
|
+
const manifestDir = path.dirname(manifestPath);
|
|
136
|
+
if (!existsSync(manifestDir)) {
|
|
137
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
140
|
+
return manifestPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function syncWidgetManifest(pluginRoot) {
|
|
144
|
+
const manifest = buildWidgetManifest(pluginRoot);
|
|
145
|
+
writeWidgetManifest(pluginRoot, manifest);
|
|
146
|
+
return manifest;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function loadWidgetManifest(pluginRoot, options = {}) {
|
|
150
|
+
const preferSource = options.preferSource === true;
|
|
151
|
+
if (!preferSource) {
|
|
152
|
+
const fromDisk = readWidgetManifest(pluginRoot);
|
|
153
|
+
if (fromDisk) {
|
|
154
|
+
return fromDisk;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return buildWidgetManifest(pluginRoot);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getWidgetSlugsFromManifest(pluginRoot, options = {}) {
|
|
161
|
+
const manifest = loadWidgetManifest(pluginRoot, options);
|
|
162
|
+
return manifest.widgets.map((widget) => widget.slug);
|
|
163
|
+
}
|
|
@@ -9,6 +9,7 @@ import { readdirSync, existsSync } from 'fs';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { spawnSync } from 'child_process';
|
|
12
|
+
import { readWidgetManifest } from './lib/widgets-manifest.js';
|
|
12
13
|
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const pluginRoot = path.resolve(__dirname, '..');
|
|
@@ -19,10 +20,27 @@ if (!existsSync(npmWidgetsDir)) {
|
|
|
19
20
|
process.exit(1);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
+
const manifest = readWidgetManifest(pluginRoot);
|
|
24
|
+
if (!manifest || !Array.isArray(manifest.widgets) || manifest.widgets.length === 0) {
|
|
25
|
+
console.error('Widget manifest not found or empty. Run npm run sync-widgets first.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const manifestSlugs = manifest.widgets.map((widget) => widget.slug).sort((a, b) => a.localeCompare(b));
|
|
30
|
+
const availableDirs = readdirSync(npmWidgetsDir, { withFileTypes: true })
|
|
23
31
|
.filter((d) => d.isDirectory())
|
|
24
|
-
.map((d) => d.name)
|
|
25
|
-
|
|
32
|
+
.map((d) => d.name);
|
|
33
|
+
const dirs = manifestSlugs.filter((slug) => availableDirs.includes(slug));
|
|
34
|
+
|
|
35
|
+
const missingBuiltPackages = manifestSlugs.filter((slug) => !availableDirs.includes(slug));
|
|
36
|
+
if (missingBuiltPackages.length > 0) {
|
|
37
|
+
console.error(
|
|
38
|
+
'Missing built widget packages in packages/npm-widgets/:',
|
|
39
|
+
missingBuiltPackages.slice(0, 20).join(', ') + (missingBuiltPackages.length > 20 ? '...' : '')
|
|
40
|
+
);
|
|
41
|
+
console.error('Run npm run build:widget-packages first.');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
26
44
|
|
|
27
45
|
if (dirs.length === 0) {
|
|
28
46
|
console.error('No widget packages in packages/npm-widgets/');
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Release gate checks:
|
|
4
|
+
* - canonical widget manifest exists and matches widget folders
|
|
5
|
+
* - CLI sample slugs in install.js exist in manifest
|
|
6
|
+
* - npm registry versions match root package version (strict mode)
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/release-gate.js --strict
|
|
10
|
+
* node scripts/release-gate.js --strict --skip-registry
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { spawnSync } from 'child_process';
|
|
17
|
+
import { getWidgetSlugsFromSource, readWidgetManifest, syncWidgetManifest } from './lib/widgets-manifest.js';
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const pluginRoot = path.resolve(__dirname, '..');
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const strict = args.includes('--strict');
|
|
23
|
+
const skipRegistry = args.includes('--skip-registry');
|
|
24
|
+
|
|
25
|
+
function runNpmViewVersion(pkgName) {
|
|
26
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
27
|
+
const result = spawnSync(
|
|
28
|
+
npmCmd,
|
|
29
|
+
['view', pkgName, 'version', '--registry=https://registry.npmjs.org', '--loglevel=error'],
|
|
30
|
+
{
|
|
31
|
+
cwd: pluginRoot,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
shell: true,
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (result.status !== 0) {
|
|
38
|
+
const stderr = (result.stderr || '')
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.find((line) => line !== '');
|
|
42
|
+
return { ok: false, version: null, error: stderr || 'npm view failed' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const version = (result.stdout || '').trim().split('\n').pop().trim();
|
|
46
|
+
if (!version) {
|
|
47
|
+
return { ok: false, version: null, error: 'empty version response' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { ok: true, version, error: null };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getRootVersion() {
|
|
54
|
+
const packageJsonPath = path.join(pluginRoot, 'package.json');
|
|
55
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
56
|
+
return typeof pkg.version === 'string' ? pkg.version : '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function checkCliExampleSlugs(manifestSlugs, issues) {
|
|
60
|
+
const installScriptPath = path.join(pluginRoot, 'scripts', 'install.js');
|
|
61
|
+
if (!existsSync(installScriptPath)) {
|
|
62
|
+
issues.push('scripts/install.js not found for CLI example validation.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const content = readFileSync(installScriptPath, 'utf8');
|
|
67
|
+
const expectedSampleSlugs = ['faq-stacked', 'hero-split', 'content-block'];
|
|
68
|
+
|
|
69
|
+
for (const slug of expectedSampleSlugs) {
|
|
70
|
+
if (!manifestSlugs.includes(slug)) {
|
|
71
|
+
issues.push(`CLI sample slug "${slug}" is not present in canonical widget manifest.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!content.includes('npx @sociallane/elements add')) {
|
|
76
|
+
issues.push('CLI help examples do not include add command examples.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkDocumentationExamples(manifestSlugs, manifestPackages, issues) {
|
|
81
|
+
const docsFiles = [
|
|
82
|
+
path.join(pluginRoot, 'docs', 'install-new-instance.md'),
|
|
83
|
+
path.join(pluginRoot, 'docs', 'package-installation.md'),
|
|
84
|
+
path.join(pluginRoot, 'docs', 'npm-widget-cheatsheet.md'),
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const docsFile of docsFiles) {
|
|
88
|
+
if (!existsSync(docsFile)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const content = readFileSync(docsFile, 'utf8');
|
|
93
|
+
|
|
94
|
+
// Validate widget package references in docs.
|
|
95
|
+
const packageMatches = content.match(/@sociallane\/widget-[a-z0-9-]+/g) || [];
|
|
96
|
+
for (const pkg of packageMatches) {
|
|
97
|
+
if (!manifestPackages.has(pkg)) {
|
|
98
|
+
issues.push(`Docs reference unknown widget package "${pkg}" (${path.basename(docsFile)}).`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate npx add slug examples in docs.
|
|
103
|
+
const addCommandMatches =
|
|
104
|
+
content.match(/npx\s+@sociallane\/elements\s+add(?:\s+--[a-z-]+(?:\s+[^\s`]+)?)*\s+[a-z0-9-][^\n`]*/g) || [];
|
|
105
|
+
for (const cmd of addCommandMatches) {
|
|
106
|
+
const parts = cmd.split(/\s+/).slice(3); // strip: npx @sociallane/elements add
|
|
107
|
+
const slugs = parts.filter((part, idx) => {
|
|
108
|
+
if (!part || part.startsWith('--')) return false;
|
|
109
|
+
// Skip values for --target
|
|
110
|
+
const prev = parts[idx - 1];
|
|
111
|
+
if (prev === '--target') return false;
|
|
112
|
+
return /^[a-z0-9-]+$/.test(part);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for (const slug of slugs) {
|
|
116
|
+
if (/^slug\d*$/i.test(slug)) {
|
|
117
|
+
continue; // placeholder tokens in docs examples
|
|
118
|
+
}
|
|
119
|
+
if (!manifestSlugs.has(slug)) {
|
|
120
|
+
issues.push(`Docs add command references unknown slug "${slug}" (${path.basename(docsFile)}).`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function main() {
|
|
128
|
+
const issues = [];
|
|
129
|
+
|
|
130
|
+
// Keep manifest current as part of the gate.
|
|
131
|
+
syncWidgetManifest(pluginRoot);
|
|
132
|
+
const manifest = readWidgetManifest(pluginRoot);
|
|
133
|
+
if (!manifest || !Array.isArray(manifest.widgets) || manifest.widgets.length === 0) {
|
|
134
|
+
issues.push('Widget manifest is missing or empty. Run npm run manifest:widgets.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const manifestSlugs = manifest ? manifest.widgets.map((widget) => widget.slug) : [];
|
|
138
|
+
const manifestSlugSet = new Set(manifestSlugs);
|
|
139
|
+
const manifestPackageSet = new Set((manifest?.widgets ?? []).map((widget) => widget.package));
|
|
140
|
+
const sourceSlugs = getWidgetSlugsFromSource(pluginRoot);
|
|
141
|
+
|
|
142
|
+
if (manifestSlugs.length !== sourceSlugs.length) {
|
|
143
|
+
issues.push(
|
|
144
|
+
`Manifest/source widget count mismatch (manifest=${manifestSlugs.length}, source=${sourceSlugs.length}).`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const missingInManifest = sourceSlugs.filter((slug) => !manifestSlugs.includes(slug));
|
|
149
|
+
if (missingInManifest.length > 0) {
|
|
150
|
+
issues.push(`Widget slugs missing in manifest: ${missingInManifest.slice(0, 20).join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const missingInSource = manifestSlugs.filter((slug) => !sourceSlugs.includes(slug));
|
|
154
|
+
if (missingInSource.length > 0) {
|
|
155
|
+
issues.push(`Manifest contains missing widget folders: ${missingInSource.slice(0, 20).join(', ')}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
checkCliExampleSlugs(manifestSlugs, issues);
|
|
159
|
+
checkDocumentationExamples(manifestSlugSet, manifestPackageSet, issues);
|
|
160
|
+
|
|
161
|
+
const rootVersion = getRootVersion();
|
|
162
|
+
if (!rootVersion) {
|
|
163
|
+
issues.push('Could not determine root package version from package.json.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (strict && !skipRegistry) {
|
|
167
|
+
console.log(`Checking npm registry versions against @sociallane/elements@${rootVersion}...`);
|
|
168
|
+
|
|
169
|
+
const rootPkg = '@sociallane/elements';
|
|
170
|
+
const rootVersionCheck = runNpmViewVersion(rootPkg);
|
|
171
|
+
if (!rootVersionCheck.ok) {
|
|
172
|
+
issues.push(`${rootPkg}: ${rootVersionCheck.error}`);
|
|
173
|
+
} else if (rootVersionCheck.version !== rootVersion) {
|
|
174
|
+
issues.push(`${rootPkg} version mismatch (registry=${rootVersionCheck.version}, local=${rootVersion}).`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const widget of manifest?.widgets ?? []) {
|
|
178
|
+
const view = runNpmViewVersion(widget.package);
|
|
179
|
+
if (!view.ok) {
|
|
180
|
+
issues.push(`${widget.package}: ${view.error}`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (view.version !== rootVersion) {
|
|
184
|
+
issues.push(`${widget.package} version mismatch (registry=${view.version}, expected=${rootVersion}).`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (issues.length > 0) {
|
|
190
|
+
console.error('Release gate failed:');
|
|
191
|
+
for (const issue of issues) {
|
|
192
|
+
console.error(`- ${issue}`);
|
|
193
|
+
}
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`Release gate passed (${manifestSlugs.length} widgets validated).`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main();
|
package/scripts/sync-widgets.js
CHANGED
|
@@ -10,28 +10,15 @@
|
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
|
+
import { getWidgetSlugsFromSource, syncWidgetManifest } from './lib/widgets-manifest.js';
|
|
13
14
|
|
|
14
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
|
|
16
17
|
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
17
|
-
const WIDGETS_DIR = path.join(PLUGIN_ROOT, 'packages', 'widgets');
|
|
18
18
|
const WIDGETS_JSON = path.join(PLUGIN_ROOT, 'widgets.json');
|
|
19
19
|
|
|
20
20
|
function getSlugsOnDisk() {
|
|
21
|
-
|
|
22
|
-
return [];
|
|
23
|
-
}
|
|
24
|
-
const entries = fs.readdirSync(WIDGETS_DIR, { withFileTypes: true });
|
|
25
|
-
const slugs = [];
|
|
26
|
-
for (const ent of entries) {
|
|
27
|
-
if (!ent.isDirectory()) continue;
|
|
28
|
-
const slug = ent.name;
|
|
29
|
-
const phpFile = path.join(WIDGETS_DIR, slug, `${slug}.php`);
|
|
30
|
-
if (fs.existsSync(phpFile)) {
|
|
31
|
-
slugs.push(slug);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return slugs.sort((a, b) => a.localeCompare(b));
|
|
21
|
+
return getWidgetSlugsFromSource(PLUGIN_ROOT);
|
|
35
22
|
}
|
|
36
23
|
|
|
37
24
|
function getExistingSlugs() {
|
|
@@ -64,6 +51,11 @@ function main() {
|
|
|
64
51
|
if (added > 0 || removed > 0) {
|
|
65
52
|
console.log(`widgets.json updated: ${added} added, ${removed} removed. Total: ${widgets.length}`);
|
|
66
53
|
}
|
|
54
|
+
|
|
55
|
+
const manifest = syncWidgetManifest(PLUGIN_ROOT);
|
|
56
|
+
if (added > 0 || removed > 0) {
|
|
57
|
+
console.log(`widget manifest synced: ${manifest.widgets.length} widgets.`);
|
|
58
|
+
}
|
|
67
59
|
}
|
|
68
60
|
|
|
69
61
|
main();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI smoke tests for scripts/install.js.
|
|
4
|
+
* Uses a fake npm binary so tests run quickly and offline.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import assert from 'assert/strict';
|
|
8
|
+
import { chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { spawnSync } from 'child_process';
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const pluginRoot = path.resolve(__dirname, '..');
|
|
16
|
+
const cliPath = path.join(pluginRoot, 'scripts', 'install.js');
|
|
17
|
+
|
|
18
|
+
function fail(message, extra = '') {
|
|
19
|
+
const details = extra ? `\n${extra}` : '';
|
|
20
|
+
throw new Error(`${message}${details}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runCli(args, options = {}) {
|
|
24
|
+
const result = spawnSync(process.execPath, [cliPath, ...args], {
|
|
25
|
+
cwd: options.cwd || pluginRoot,
|
|
26
|
+
env: options.env || process.env,
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createFakeNpmBin(rootDir) {
|
|
33
|
+
const binDir = path.join(rootDir, 'fake-bin');
|
|
34
|
+
mkdirSync(binDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
const npmPath = path.join(binDir, 'npm');
|
|
37
|
+
writeFileSync(
|
|
38
|
+
npmPath,
|
|
39
|
+
[
|
|
40
|
+
'#!/usr/bin/env sh',
|
|
41
|
+
'echo "fake npm $@" >> "$FAKE_NPM_LOG"',
|
|
42
|
+
'exit 0',
|
|
43
|
+
'',
|
|
44
|
+
].join('\n'),
|
|
45
|
+
'utf8'
|
|
46
|
+
);
|
|
47
|
+
chmodSync(npmPath, 0o755);
|
|
48
|
+
|
|
49
|
+
return binDir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function main() {
|
|
53
|
+
const tempRoot = mkdtempSync(path.join(os.tmpdir(), 'sociallane-cli-smoke-'));
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const fakeLog = path.join(tempRoot, 'fake-npm.log');
|
|
57
|
+
const fakeBin = createFakeNpmBin(tempRoot);
|
|
58
|
+
const env = {
|
|
59
|
+
...process.env,
|
|
60
|
+
PATH: `${fakeBin}${path.delimiter}${process.env.PATH || ''}`,
|
|
61
|
+
FAKE_NPM_LOG: fakeLog,
|
|
62
|
+
SOCIALLANE_SKIP_SYNC_WIDGETS: '1',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 1) help exits successfully
|
|
66
|
+
const help = runCli(['--help'], { env });
|
|
67
|
+
assert.equal(help.status, 0, `--help failed: ${help.stderr}`);
|
|
68
|
+
assert.match(help.stdout, /SocialLane Elements CLI/, '--help output missing CLI header');
|
|
69
|
+
|
|
70
|
+
// 2) unknown flag fails
|
|
71
|
+
const bad = runCli(['--not-a-real-flag'], { env });
|
|
72
|
+
assert.notEqual(bad.status, 0, 'Unknown flag should fail');
|
|
73
|
+
|
|
74
|
+
// 3) base install creates plugin with empty widgets.json and no imported widgets
|
|
75
|
+
const wpRoot = path.join(tempRoot, 'site');
|
|
76
|
+
const pluginDir = path.join(wpRoot, 'wp-content', 'plugins', 'sociallane-elements');
|
|
77
|
+
const base = runCli(['--base', pluginDir], { cwd: tempRoot, env });
|
|
78
|
+
assert.equal(base.status, 0, `--base failed:\n${base.stdout}\n${base.stderr}`);
|
|
79
|
+
|
|
80
|
+
const widgetsJsonPath = path.join(pluginDir, 'widgets.json');
|
|
81
|
+
assert.equal(existsSync(path.join(pluginDir, 'sociallane-elements.php')), true, 'Plugin bootstrap missing');
|
|
82
|
+
assert.equal(existsSync(widgetsJsonPath), true, 'widgets.json missing after base install');
|
|
83
|
+
|
|
84
|
+
const widgetsJson = JSON.parse(readFileSync(widgetsJsonPath, 'utf8'));
|
|
85
|
+
assert.deepEqual(widgetsJson.widgets, [], 'Base install should keep widgets.json empty');
|
|
86
|
+
|
|
87
|
+
const widgetsPackagesDir = path.join(pluginDir, 'packages', 'widgets');
|
|
88
|
+
assert.equal(existsSync(widgetsPackagesDir), true, 'packages/widgets should exist after base install');
|
|
89
|
+
const widgetsAfterBase = readdirSync(widgetsPackagesDir, { withFileTypes: true }).filter((entry) =>
|
|
90
|
+
entry.isDirectory()
|
|
91
|
+
);
|
|
92
|
+
assert.equal(widgetsAfterBase.length, 0, 'Base install should not import widget component folders');
|
|
93
|
+
|
|
94
|
+
// 4) add with explicit --target from one directory above plugins
|
|
95
|
+
const fromWpContent = path.join(wpRoot, 'wp-content');
|
|
96
|
+
const addTarget = runCli(['add', '--target', pluginDir, 'faq-stacked', 'hero-split'], {
|
|
97
|
+
cwd: fromWpContent,
|
|
98
|
+
env,
|
|
99
|
+
});
|
|
100
|
+
assert.equal(addTarget.status, 0, `add --target failed:\n${addTarget.stdout}\n${addTarget.stderr}`);
|
|
101
|
+
|
|
102
|
+
const widgetsJsonAfterAdd = JSON.parse(readFileSync(widgetsJsonPath, 'utf8'));
|
|
103
|
+
assert.deepEqual(
|
|
104
|
+
widgetsJsonAfterAdd.widgets,
|
|
105
|
+
['faq-stacked', 'hero-split'],
|
|
106
|
+
'add --target should set selected slugs'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
assert.equal(
|
|
110
|
+
existsSync(path.join(pluginDir, 'packages', 'widgets', 'faq-stacked', 'faq-stacked.php')),
|
|
111
|
+
true,
|
|
112
|
+
'faq-stacked folder missing after add'
|
|
113
|
+
);
|
|
114
|
+
assert.equal(
|
|
115
|
+
existsSync(path.join(pluginDir, 'packages', 'widgets', 'hero-split', 'hero-split.php')),
|
|
116
|
+
true,
|
|
117
|
+
'hero-split folder missing after add'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// 5) add without --target from wp-content should auto-resolve plugin dir
|
|
121
|
+
const addAuto = runCli(['add', 'content-block'], { cwd: fromWpContent, env });
|
|
122
|
+
assert.equal(addAuto.status, 0, `add (auto target) failed:\n${addAuto.stdout}\n${addAuto.stderr}`);
|
|
123
|
+
|
|
124
|
+
const widgetsJsonAfterAuto = JSON.parse(readFileSync(widgetsJsonPath, 'utf8'));
|
|
125
|
+
assert.deepEqual(
|
|
126
|
+
widgetsJsonAfterAuto.widgets,
|
|
127
|
+
['faq-stacked', 'hero-split', 'content-block'],
|
|
128
|
+
'auto target add should append slug'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (!existsSync(fakeLog)) {
|
|
132
|
+
fail('Fake npm was never invoked');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log('CLI smoke tests passed.');
|
|
136
|
+
} finally {
|
|
137
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main();
|