@sociallane/elements 1.0.12 → 1.0.13

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.
@@ -55,13 +55,13 @@ cd wp-content/plugins/sociallane-elements && npm install && npm run build
55
55
 
56
56
  ```bash
57
57
  # From your WordPress root (directory that contains wp-content)
58
- npx @sociallane/elements --minimal
58
+ npx @sociallane/elements --base
59
59
  ```
60
60
 
61
61
  Then add widgets by slug (no manual `widgets.json` editing):
62
62
 
63
63
  ```bash
64
- npx @sociallane/elements add hero-split faq content-block widget-filter
64
+ npx @sociallane/elements add hero-split faq-stacked content-block widget-filter
65
65
  ```
66
66
 
67
67
  Each `add` updates `widgets.json` and runs the build. You can run `add` anytime to add more widgets.
@@ -77,8 +77,8 @@ This copies the full plugin to `wp-content/plugins/sociallane-elements`, runs `n
77
77
  **Custom path:**
78
78
 
79
79
  ```bash
80
- npx @sociallane/elements --minimal path/to/wp-content/plugins/sociallane-elements
81
- npx @sociallane/elements add hero-split faq # run from WordPress root or plugin dir
80
+ npx @sociallane/elements --base path/to/wp-content/plugins/sociallane-elements
81
+ npx @sociallane/elements add --target path/to/wp-content/plugins/sociallane-elements hero-split faq-stacked
82
82
  ```
83
83
 
84
84
  **Single widget (per-widget packages):**
@@ -87,7 +87,7 @@ You can install one widget at a time via its own package. This installs the Soci
87
87
 
88
88
  ```bash
89
89
  # From your WordPress root or wp-content/plugins
90
- npx @sociallane/widget-hero-overlay
90
+ npx @sociallane/widget-content-block
91
91
  npx @sociallane/widget-faq-stacked
92
92
  ```
93
93
 
@@ -7,8 +7,11 @@ Quick reference for loading widgets via npm workspaces.
7
7
  ## One-liners
8
8
 
9
9
  ```bash
10
- # Full install REQUIRED for styled widgets (from plugin root)
11
- npm install && npm run build
10
+ # Base install (no widgets imported yet)
11
+ npx @sociallane/elements --base
12
+
13
+ # Add selected widgets
14
+ npx @sociallane/elements add hero-split faq-stacked content-block
12
15
 
13
16
  # Dev watch (rebuilds on file change)
14
17
  npm run dev
@@ -28,7 +31,7 @@ Edit `widgets.json` at plugin root:
28
31
  "hero-split",
29
32
  "hero-overlay",
30
33
  "content-block",
31
- "faq"
34
+ "faq-stacked"
32
35
  ]
33
36
  }
34
37
  ```
@@ -39,7 +42,10 @@ Only slugs listed here are loaded. Add or remove as needed.
39
42
 
40
43
  ## Widget → npm package & command
41
44
 
42
- Every widget and its npm package / add command:
45
+ Common widgets and their npm package / add command:
46
+
47
+ > Note: per-widget packages may not all be published at the same time.
48
+ > The most reliable flow is `npx @sociallane/elements --base` then `npx @sociallane/elements add ...`.
43
49
 
44
50
  | Widget | Package | Command |
45
51
  |--------|---------|---------|
@@ -52,7 +58,9 @@ Every widget and its npm package / add command:
52
58
  | cta-banner | `@sociallane/widget-cta-banner` | `npm install @sociallane/widget-cta-banner` |
53
59
  | cta-notify | `@sociallane/widget-cta-notify` | `npm install @sociallane/widget-cta-notify` |
54
60
  | cta-split | `@sociallane/widget-cta-split` | `npm install @sociallane/widget-cta-split` |
55
- | faq | `@sociallane/widget-faq` | `npm install @sociallane/widget-faq` |
61
+ | faq-stacked | `@sociallane/widget-faq-stacked` | `npm install @sociallane/widget-faq-stacked` |
62
+ | faq-split | `@sociallane/widget-faq-split` | `npm install @sociallane/widget-faq-split` |
63
+ | faq-centered | `@sociallane/widget-faq-centered` | `npm install @sociallane/widget-faq-centered` |
56
64
  | feature-grid | `@sociallane/widget-feature-grid` | `npm install @sociallane/widget-feature-grid` |
57
65
  | feature-list | `@sociallane/widget-feature-list` | `npm install @sociallane/widget-feature-list` |
58
66
  | feature-list-cta | `@sociallane/widget-feature-list-cta` | `npm install @sociallane/widget-feature-list-cta` |
@@ -62,25 +70,34 @@ Every widget and its npm package / add command:
62
70
  | form-contact | `@sociallane/widget-form-contact` | `npm install @sociallane/widget-form-contact` |
63
71
  | grid-case-studies | `@sociallane/widget-grid-case-studies` | `npm install @sociallane/widget-grid-case-studies` |
64
72
  | grid-components | `@sociallane/widget-grid-components` | `npm install @sociallane/widget-grid-components` |
65
- | grid-posts | `@sociallane/widget-grid-posts` | `npm install @sociallane/widget-grid-posts` |
73
+ | posts-grid | `@sociallane/widget-posts-grid` | `npm install @sociallane/widget-posts-grid` |
74
+ | posts-grid-overlay | `@sociallane/widget-posts-grid-overlay` | `npm install @sociallane/widget-posts-grid-overlay` |
66
75
  | grid-team | `@sociallane/widget-grid-team` | `npm install @sociallane/widget-grid-team` |
67
- | grid-testimonials | `@sociallane/widget-grid-testimonials` | `npm install @sociallane/widget-grid-testimonials` |
76
+ | testimonials-grid | `@sociallane/widget-testimonials-grid` | `npm install @sociallane/widget-testimonials-grid` |
77
+ | testimonials-masonry | `@sociallane/widget-testimonials-masonry` | `npm install @sociallane/widget-testimonials-masonry` |
78
+ | testimonials-bento | `@sociallane/widget-testimonials-bento` | `npm install @sociallane/widget-testimonials-bento` |
68
79
  | hero-announcement | `@sociallane/widget-hero-announcement` | `npm install @sociallane/widget-hero-announcement` |
69
80
  | hero-centered-image | `@sociallane/widget-hero-centered-image` | `npm install @sociallane/widget-hero-centered-image` |
70
81
  | hero-collage | `@sociallane/widget-hero-collage` | `npm install @sociallane/widget-hero-collage` |
71
82
  | hero-overlay | `@sociallane/widget-hero-overlay` | `npm install @sociallane/widget-hero-overlay` |
72
- | hero-saas | `@sociallane/widget-hero-saas` | `npm install @sociallane/widget-hero-saas` |
83
+ | hero-saas-split | `@sociallane/widget-hero-saas-split` | `npm install @sociallane/widget-hero-saas-split` |
84
+ | hero-saas-stacked | `@sociallane/widget-hero-saas-stacked` | `npm install @sociallane/widget-hero-saas-stacked` |
85
+ | hero-saas-centered | `@sociallane/widget-hero-saas-centered` | `npm install @sociallane/widget-hero-saas-centered` |
73
86
  | hero-split | `@sociallane/widget-hero-split` | `npm install @sociallane/widget-hero-split` |
74
87
  | hero-stacked-image | `@sociallane/widget-hero-stacked-image` | `npm install @sociallane/widget-hero-stacked-image` |
75
88
  | intro-pattern | `@sociallane/widget-intro-pattern` | `npm install @sociallane/widget-intro-pattern` |
76
89
  | intro-text | `@sociallane/widget-intro-text` | `npm install @sociallane/widget-intro-text` |
77
- | logo-grid | `@sociallane/widget-logo-grid` | `npm install @sociallane/widget-logo-grid` |
90
+ | logo-grid-centered | `@sociallane/widget-logo-grid-centered` | `npm install @sociallane/widget-logo-grid-centered` |
91
+ | logo-grid-row | `@sociallane/widget-logo-grid-row` | `npm install @sociallane/widget-logo-grid-row` |
92
+ | logo-grid-split | `@sociallane/widget-logo-grid-split` | `npm install @sociallane/widget-logo-grid-split` |
78
93
  | nav-default | `@sociallane/widget-nav-default` | `npm install @sociallane/widget-nav-default` |
79
94
  | nav-centered | `@sociallane/widget-nav-centered` | `npm install @sociallane/widget-nav-centered` |
80
95
  | nav-minimal | `@sociallane/widget-nav-minimal` | `npm install @sociallane/widget-nav-minimal` |
81
96
  | nav-compact | `@sociallane/widget-nav-compact` | `npm install @sociallane/widget-nav-compact` |
82
97
  | nav-floating | `@sociallane/widget-nav-floating` | `npm install @sociallane/widget-nav-floating` |
83
98
  | newsletter | `@sociallane/widget-newsletter` | `npm install @sociallane/widget-newsletter` |
99
+ | newsletter-card | `@sociallane/widget-newsletter-card` | `npm install @sociallane/widget-newsletter-card` |
100
+ | newsletter-section | `@sociallane/widget-newsletter-section` | `npm install @sociallane/widget-newsletter-section` |
84
101
  | outreach-dashboard | `@sociallane/widget-outreach-dashboard` | `npm install @sociallane/widget-outreach-dashboard` |
85
102
  | pipeline-dashboard | `@sociallane/widget-pipeline-dashboard` | `npm install @sociallane/widget-pipeline-dashboard` |
86
103
  | pricing-table | `@sociallane/widget-pricing-table` | `npm install @sociallane/widget-pricing-table` |
@@ -89,7 +106,9 @@ Every widget and its npm package / add command:
89
106
  | services | `@sociallane/widget-services` | `npm install @sociallane/widget-services` |
90
107
  | simple-page-hero | `@sociallane/widget-simple-page-hero` | `npm install @sociallane/widget-simple-page-hero` |
91
108
  | social-proof | `@sociallane/widget-social-proof` | `npm install @sociallane/widget-social-proof` |
109
+ | social-proof-trust | `@sociallane/widget-social-proof-trust` | `npm install @sociallane/widget-social-proof-trust` |
92
110
  | testimonial-quote | `@sociallane/widget-testimonial-quote` | `npm install @sociallane/widget-testimonial-quote` |
111
+ | widget-filter | `@sociallane/widget-widget-filter` | `npm install @sociallane/widget-widget-filter` |
93
112
 
94
113
  Then add the slug to `widgets.json`.
95
114
 
@@ -99,12 +118,12 @@ Then add the slug to `widgets.json`.
99
118
 
100
119
  | Category | Slugs |
101
120
  |----------|-------|
102
- | Heroes | `hero-split`, `hero-overlay`, `hero-saas`, `hero-collage`, `hero-centered-image`, `hero-stacked-image`, `hero-announcement`, `simple-page-hero` |
103
- | Content | `content-block`, `intro-text`, `intro-pattern`, `faq`, `feature-list`, `feature-list-cta`, `feature-grid` |
104
- | Cards & Grids | `card-hover-reveal`, `bento-grid`, `bento-portfolio`, `grid-posts`, `grid-team`, `grid-testimonials`, `grid-case-studies`, `grid-components`, `logo-grid`, `client-logos` |
105
- | Forms & CTAs | `cta-banner`, `cta-split`, `cta-notify`, `newsletter`, `form-contact`, `pricing-table` |
121
+ | Heroes | `hero-split`, `hero-overlay`, `hero-overlay-single`, `hero-overlay-slider`, `hero-saas-split`, `hero-saas-stacked`, `hero-saas-centered`, `hero-collage`, `hero-centered-image`, `hero-stacked-image`, `hero-announcement`, `simple-page-hero`, `page-hero-left`, `page-hero-center` |
122
+ | Content | `content-block`, `intro-text`, `intro-pattern`, `faq-stacked`, `faq-split`, `faq-centered`, `feature-list`, `feature-list-cta`, `feature-grid` |
123
+ | Cards & Grids | `card-hover-reveal`, `bento-grid`, `bento-portfolio`, `posts-grid`, `posts-grid-overlay`, `grid-team`, `testimonials-grid`, `testimonials-masonry`, `testimonials-bento`, `grid-case-studies`, `grid-components`, `logo-grid-centered`, `logo-grid-row`, `logo-grid-split`, `client-logos` |
124
+ | Forms & CTAs | `cta-banner`, `cta-split`, `cta-notify`, `newsletter`, `newsletter-card`, `newsletter-section`, `form-contact`, `pricing-table` |
106
125
  | Navigation | `nav-default`, `nav-centered`, `nav-minimal`, `nav-compact`, `nav-floating`, `footer`, `footer-brand`, `footer-links-contact` |
107
- | Other | `section-stats`, `social-proof`, `services`, `blog-grid`, `outreach-dashboard`, `pipeline-dashboard`, `sales-dashboard`, `testimonial-quote` |
126
+ | Other | `section-stats`, `social-proof`, `social-proof-trust`, `services`, `blog-grid`, `outreach-dashboard`, `pipeline-dashboard`, `sales-dashboard`, `testimonial-quote`, `widget-filter` |
108
127
 
109
128
  ---
110
129
 
@@ -29,7 +29,8 @@ Edit `widgets.json` to include only the widget slugs you want. You can also let
29
29
 
30
30
  Adding or removing widget directories is reflected in `widgets.json` automatically:
31
31
 
32
- - **On `npm install`** — The postinstall script runs `sync-widgets` then `build`. Any new folder under `packages/widgets/{slug}` with `{slug}.php` is added to `widgets.json`; removed folders are removed from the list. Existing order is preserved; new slugs are appended sorted.
32
+ - **On `npm install`** — The postinstall script runs `sync-widgets` then `build` by default. Any new folder under `packages/widgets/{slug}` with `{slug}.php` is added to `widgets.json`; removed folders are removed from the list. Existing order is preserved; new slugs are appended sorted.
33
+ The installers use `SOCIALLANE_SKIP_SYNC_WIDGETS=1` for base/cherry-pick flows so `widgets.json` is not expanded unintentionally.
33
34
  - **Manual sync** — From the plugin root run:
34
35
  ```bash
35
36
  npm run sync-widgets
@@ -42,7 +43,7 @@ Adding or removing widget directories is reflected in `widgets.json` automatical
42
43
  "hero-split",
43
44
  "hero-overlay",
44
45
  "content-block",
45
- "faq"
46
+ "faq-stacked"
46
47
  ]
47
48
  }
48
49
  ```
@@ -65,10 +66,10 @@ Use submodules so each WordPress instance only has the core package and the widg
65
66
 
66
67
  # Add only the widgets you want
67
68
  git submodule add <url-to-widget-hero-split> packages/widgets/hero-split
68
- git submodule add <url-to-widget-faq> packages/widgets/faq
69
+ git submodule add <url-to-widget-faq-stacked> packages/widgets/faq-stacked
69
70
  ```
70
71
 
71
- 3. Update **widgets.json** so its `widgets` array lists only the slugs you added (e.g. `hero-split`, `faq`).
72
+ 3. Update **widgets.json** so its `widgets` array lists only the slugs you added (e.g. `hero-split`, `faq-stacked`).
72
73
 
73
74
  4. Install and build from plugin root (both required for styled widgets):
74
75
 
@@ -123,13 +124,13 @@ The plugin is published as `@sociallane/elements`. To publish:
123
124
 
124
125
  ## Per-widget npm packages
125
126
 
126
- Individual widgets are published as `@sociallane/widget-<slug>` (e.g. `@sociallane/widget-hero-overlay`). Run from your WordPress root or `wp-content/plugins`:
127
+ Individual widgets are published as `@sociallane/widget-<slug>` (for example `@sociallane/widget-faq-stacked`). Run from your WordPress root or `wp-content/plugins`:
127
128
 
128
129
  ```bash
129
- npx @sociallane/widget-hero-overlay
130
+ npx @sociallane/widget-faq-stacked
130
131
  ```
131
132
 
132
- The installer installs the base plugin (`@sociallane/elements`) with `--minimal` if it is not present, then copies the widget into the plugin, runs `sync-widgets`, and `npm install`. Use these when you want a one-command install for a single widget. To add multiple widgets, `npx @sociallane/elements add slug1 slug2` is usually simpler.
133
+ The installer installs the base plugin (`@sociallane/elements`) with `--base` if it is not present, then delegates to `npx @sociallane/elements add --target <pluginDir> <slug>`. Use these when you want a one-command install for a single widget. To add multiple widgets, `npx @sociallane/elements add --target <pluginDir> slug1 slug2` is usually simpler.
133
134
 
134
135
  ## Adding a new widget package
135
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sociallane/elements",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Elementor widgets and elements with Tailwind CSS for SocialLane. WordPress plugin.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -484,8 +484,8 @@ function prepare_widget_filter_view( array $settings, string $widget_id ): array
484
484
  // Grid columns class
485
485
  $grid_columns = [
486
486
  '2' => 'grid-cols-1 sm:grid-cols-2',
487
- '3' => 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
488
- '4' => 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
487
+ '3' => 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
488
+ '4' => 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4',
489
489
  ];
490
490
  $grid_class = $grid_columns[ $columns ] ?? $grid_columns['3'];
491
491
 
@@ -2,10 +2,12 @@
2
2
  /**
3
3
  * SocialLane Elements CLI
4
4
  *
5
+ * Install (base): npx @sociallane/elements --base [path]
5
6
  * Install (full): npx @sociallane/elements [path]
6
7
  * Add widgets: npx @sociallane/elements add <slug> [slug...]
7
8
  *
8
9
  * Install: copies package to wp-content/plugins/sociallane-elements (or path), runs npm install.
10
+ * Base install: installs core/deps and keeps widgets.json empty (no widgets loaded by default).
9
11
  * Add: copies selected widget folders into an existing plugin, updates widgets.json, runs npm install.
10
12
  */
11
13
 
@@ -16,9 +18,44 @@ import { fileURLToPath } from 'url';
16
18
 
17
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
20
  const packageRoot = path.resolve(__dirname, '..');
19
- const widgetsSource = path.join(packageRoot, 'packages', 'widgets');
21
+ const packageJsonPath = path.join(packageRoot, 'package.json');
22
+
23
+ function getCliVersion() {
24
+ try {
25
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
26
+ return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
27
+ } catch {
28
+ return '0.0.0';
29
+ }
30
+ }
31
+
32
+ function printHelp() {
33
+ console.log('SocialLane Elements CLI');
34
+ console.log('');
35
+ console.log('Usage:');
36
+ console.log(' npx @sociallane/elements [--base|--minimal] [target]');
37
+ console.log(' npx @sociallane/elements add [--only] [--target <path>] <slug> [slug...]');
38
+ console.log(' npx @sociallane/elements --help');
39
+ console.log(' npx @sociallane/elements --version');
40
+ console.log('');
41
+ console.log('Options:');
42
+ console.log(' --base, --minimal Install core/deps only, keep widgets.json empty');
43
+ console.log(' --only With add: replace widgets.json with exactly provided slugs');
44
+ console.log(' --target <path> With add: explicit plugin directory target');
45
+ console.log(' --help Show this help text');
46
+ console.log(' --version Show CLI version');
47
+ console.log('');
48
+ console.log('Examples:');
49
+ console.log(' npx @sociallane/elements --base');
50
+ console.log(' npx @sociallane/elements add faq-stacked hero-split');
51
+ console.log(' npx @sociallane/elements add --only --target wp-content/plugins/sociallane-elements content-block');
52
+ }
53
+
54
+ function resolvePluginDir(targetPath = '') {
55
+ if (targetPath) {
56
+ return path.resolve(process.cwd(), targetPath);
57
+ }
20
58
 
21
- function resolvePluginDir() {
22
59
  const cwd = process.cwd();
23
60
  const normalized = path.normalize(cwd);
24
61
  const pluginsSegment = 'wp-content' + path.sep + 'plugins';
@@ -41,6 +78,26 @@ function resolvePluginDir() {
41
78
  return path.join(cwd, 'wp-content', 'plugins', 'sociallane-elements');
42
79
  }
43
80
 
81
+ function getWidgetsSourceDir() {
82
+ const candidates = [
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];
99
+ }
100
+
44
101
  function updateWidgetsJson(pluginDir, slugsToAdd, replaceAll = false) {
45
102
  const widgetsJsonPath = path.join(pluginDir, 'widgets.json');
46
103
  let existing = [];
@@ -71,8 +128,9 @@ function updateWidgetsJson(pluginDir, slugsToAdd, replaceAll = false) {
71
128
 
72
129
  function addWidgets(slugs, options = {}) {
73
130
  const replaceAll = options.replaceAll === true;
74
- const pluginDir = resolvePluginDir();
131
+ const pluginDir = resolvePluginDir(options.targetPath || '');
75
132
  const widgetsDest = path.join(pluginDir, 'packages', 'widgets');
133
+ const widgetsSource = getWidgetsSourceDir();
76
134
 
77
135
  if (!existsSync(path.join(pluginDir, 'sociallane-elements.php'))) {
78
136
  console.error('Plugin not found at', pluginDir);
@@ -85,8 +143,10 @@ function addWidgets(slugs, options = {}) {
85
143
  process.exit(1);
86
144
  }
87
145
 
146
+ console.log('Target plugin:', pluginDir);
147
+
88
148
  const available = readdirSync(widgetsSource, { withFileTypes: true })
89
- .filter((d) => d.isDirectory())
149
+ .filter((d) => d.isDirectory() && existsSync(path.join(widgetsSource, d.name, `${d.name}.php`)))
90
150
  .map((d) => d.name);
91
151
 
92
152
  const missing = slugs.filter((s) => !available.includes(s));
@@ -136,6 +196,14 @@ function addWidgets(slugs, options = {}) {
136
196
  console.log('Done. Widgets added:', slugs.join(', '));
137
197
  }
138
198
 
199
+ function prepareBaseMode(pluginDir) {
200
+ const widgetsDest = path.join(pluginDir, 'packages', 'widgets');
201
+ if (!existsSync(widgetsDest)) {
202
+ mkdirSync(widgetsDest, { recursive: true });
203
+ }
204
+ updateWidgetsJson(pluginDir, [], true);
205
+ }
206
+
139
207
  function copyPackage(toDir, filter) {
140
208
  if (!existsSync(toDir)) {
141
209
  mkdirSync(toDir, { recursive: true });
@@ -182,12 +250,21 @@ function install(targetPath, minimal) {
182
250
  const targetResolved = path.resolve(target);
183
251
  const isSelf = targetResolved === path.resolve(packageRoot) || targetResolved.startsWith(packageRoot + path.sep);
184
252
 
185
- console.log('SocialLane Elements installer' + (minimal ? ' (minimal – add widgets with: npx @sociallane/elements add <slug> ...)' : ''));
253
+ console.log(
254
+ 'SocialLane Elements installer' +
255
+ (minimal ? ' (base mode – add widgets with: npx @sociallane/elements add <slug> ...)' : '')
256
+ );
186
257
  console.log('Target:', target);
187
258
 
188
259
  if (isSelf) {
260
+ if (minimal) {
261
+ prepareBaseMode(packageRoot);
262
+ }
189
263
  console.log('Target is the current package. Running npm install here...');
190
264
  } else if (existsSync(target) && existsSync(path.join(target, 'package.json'))) {
265
+ if (minimal) {
266
+ prepareBaseMode(target);
267
+ }
191
268
  console.log('Target already exists. Running npm install only...');
192
269
  } else {
193
270
  console.log('Copying package...');
@@ -196,16 +273,18 @@ function install(targetPath, minimal) {
196
273
  const rel = path.relative(packageRoot, src).split(path.sep).join('/');
197
274
  // Exclude node_modules within the package (not in parent path)
198
275
  if (rel.includes('node_modules')) return false;
199
- // Exclude packages/widgets for minimal install
276
+ // Base install should not ship widget sources; users add explicitly via `add`.
200
277
  if (rel === 'packages/widgets' || rel.startsWith('packages/widgets/')) return false;
278
+ // Exclude legacy widgets directory and generated npm-widget package folders.
279
+ if (rel === 'widgets' || rel.startsWith('widgets/')) return false;
280
+ if (rel === 'packages/npm-widgets' || rel.startsWith('packages/npm-widgets/')) return false;
201
281
  return true;
202
282
  });
203
283
  const widgetsDest = path.join(target, 'packages', 'widgets');
204
284
  if (!existsSync(widgetsDest)) {
205
285
  mkdirSync(widgetsDest, { recursive: true });
206
286
  }
207
- const widgetsJson = path.join(target, 'widgets.json');
208
- writeFileSync(widgetsJson, JSON.stringify({ widgets: [] }, null, 2));
287
+ updateWidgetsJson(target, [], true);
209
288
  } else {
210
289
  copyPackage(target);
211
290
  }
@@ -214,8 +293,15 @@ function install(targetPath, minimal) {
214
293
  const installDir = isSelf ? packageRoot : target;
215
294
  console.log('Running npm install in', installDir + '...');
216
295
  const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
296
+ const installEnv = {
297
+ ...process.env,
298
+ };
299
+ if (minimal) {
300
+ installEnv.SOCIALLANE_SKIP_SYNC_WIDGETS = '1';
301
+ }
217
302
  const result = spawnSync(npmCmd, ['install'], {
218
303
  cwd: installDir,
304
+ env: installEnv,
219
305
  stdio: 'inherit',
220
306
  shell: true,
221
307
  });
@@ -237,21 +323,72 @@ function install(targetPath, minimal) {
237
323
 
238
324
  function main() {
239
325
  const args = process.argv.slice(2);
240
- const minimal = args.includes('--minimal');
241
- const installArgs = args.filter((a) => a !== '--minimal');
242
326
 
243
- if (installArgs[0] === 'add') {
244
- const addArgs = installArgs.slice(1).filter(Boolean);
327
+ if (args.includes('--help') || args.includes('-h')) {
328
+ printHelp();
329
+ return;
330
+ }
331
+
332
+ if (args.includes('--version') || args.includes('-v')) {
333
+ console.log(getCliVersion());
334
+ return;
335
+ }
336
+
337
+ if (args[0] === 'add') {
338
+ const addArgs = args.slice(1).filter(Boolean);
339
+ const knownAddFlags = new Set(['--only', '--target']);
340
+ let targetPath = '';
341
+ const slugs = [];
342
+ for (let i = 0; i < addArgs.length; i++) {
343
+ const arg = addArgs[i];
344
+ if (arg === '--target') {
345
+ const next = addArgs[i + 1];
346
+ if (!next || next.startsWith('-')) {
347
+ console.error('Missing value for --target');
348
+ process.exit(1);
349
+ }
350
+ targetPath = next;
351
+ i++;
352
+ continue;
353
+ }
354
+ if (arg.startsWith('-')) {
355
+ if (!knownAddFlags.has(arg)) {
356
+ console.error('Unknown option for add:', arg);
357
+ process.exit(1);
358
+ }
359
+ continue;
360
+ }
361
+ slugs.push(arg);
362
+ }
363
+
245
364
  const replaceAll = addArgs.includes('--only');
246
- const slugs = addArgs.filter((arg) => arg !== '--only');
247
365
  if (slugs.length === 0) {
248
366
  console.error('Usage: npx @sociallane/elements add [--only] <slug> [slug...]');
249
367
  console.error('Example: npx @sociallane/elements add --only hero-split faq-stacked content-block');
250
368
  process.exit(1);
251
369
  }
252
- addWidgets(slugs, { replaceAll });
370
+ addWidgets(slugs, { replaceAll, targetPath });
253
371
  } else {
254
- install(installArgs[0], minimal);
372
+ let minimal = false;
373
+ const positionals = [];
374
+ for (const arg of args) {
375
+ if (arg === '--minimal' || arg === '--base') {
376
+ minimal = true;
377
+ continue;
378
+ }
379
+ if (arg.startsWith('-')) {
380
+ console.error('Unknown option:', arg);
381
+ process.exit(1);
382
+ }
383
+ positionals.push(arg);
384
+ }
385
+
386
+ if (positionals.length > 1) {
387
+ console.error('Too many positional arguments. Expected at most one target path.');
388
+ process.exit(1);
389
+ }
390
+
391
+ install(positionals[0], minimal);
255
392
  }
256
393
  }
257
394
 
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Per-widget installer: run from a @sociallane/widget-<slug> package (npx).
4
- * Ensures SocialLane Elements plugin is installed, then copies this package's widget/
5
- * into the plugin's packages/widgets/<slug>, runs sync-widgets and npm install.
4
+ * Ensures SocialLane Elements plugin is installed in base mode, then delegates
5
+ * widget import to the main CLI add command.
6
6
  */
7
7
 
8
- import { cpSync, existsSync, mkdirSync, readFileSync } from 'fs';
8
+ import { existsSync, readFileSync } from 'fs';
9
9
  import { spawnSync } from 'child_process';
10
10
  import path from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
 
13
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const packageRoot = path.resolve(__dirname);
15
- const widgetDir = path.join(packageRoot, 'widget');
16
15
 
17
16
  function getSlugFromPackage() {
18
17
  const pkgPath = path.join(packageRoot, 'package.json');
@@ -56,62 +55,29 @@ function resolvePluginDir() {
56
55
  return path.join(cwd, 'wp-content', 'plugins', 'sociallane-elements');
57
56
  }
58
57
 
59
- function main() {
60
- const slug = getSlugFromPackage();
61
- if (!existsSync(widgetDir)) {
62
- console.error('Widget folder not found:', widgetDir);
63
- process.exit(1);
64
- }
65
- if (!existsSync(path.join(widgetDir, `${slug}.php`))) {
66
- console.error('Invalid widget: missing', path.join(widgetDir, `${slug}.php`));
67
- process.exit(1);
58
+ function runNpx(args, cwd) {
59
+ const r = spawnSync('npx', ['--yes', ...args], {
60
+ cwd,
61
+ stdio: 'inherit',
62
+ shell: true,
63
+ });
64
+ if (r.status !== 0) {
65
+ process.exit(r.status ?? 1);
68
66
  }
67
+ }
69
68
 
69
+ function main() {
70
+ const slug = getSlugFromPackage();
70
71
  const pluginDir = resolvePluginDir();
71
- const widgetsDest = path.join(pluginDir, 'packages', 'widgets');
72
72
  const pluginExists = existsSync(path.join(pluginDir, 'sociallane-elements.php'));
73
73
 
74
74
  if (!pluginExists) {
75
75
  console.log('SocialLane Elements not found. Installing base plugin...');
76
- const r = spawnSync('npx', ['--yes', '@sociallane/elements', '--minimal'], {
77
- cwd: path.dirname(pluginDir),
78
- stdio: 'inherit',
79
- shell: true,
80
- });
81
- if (r.status !== 0) {
82
- process.exit(r.status ?? 1);
83
- }
76
+ runNpx(['@sociallane/elements', '--base', pluginDir], path.dirname(pluginDir));
84
77
  }
85
78
 
86
- if (!existsSync(widgetsDest)) {
87
- mkdirSync(widgetsDest, { recursive: true });
88
- }
89
-
90
- const dest = path.join(widgetsDest, slug);
91
- cpSync(widgetDir, dest, { recursive: true, force: true });
92
- console.log('Added widget:', slug);
93
-
94
- console.log('Updating widgets.json and building...');
95
- const syncResult = spawnSync('node', [path.join(pluginDir, 'scripts', 'sync-widgets.js')], {
96
- cwd: pluginDir,
97
- stdio: 'inherit',
98
- });
99
- if (syncResult.status !== 0) {
100
- process.exit(syncResult.status ?? 1);
101
- }
102
-
103
- const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
104
- const installResult = spawnSync(npmCmd, ['install'], {
105
- cwd: pluginDir,
106
- stdio: 'inherit',
107
- shell: true,
108
- });
109
- if (installResult.status !== 0) {
110
- if (installResult.error) {
111
- console.error('Failed to run npm:', installResult.error.message);
112
- }
113
- process.exit(installResult.status ?? 1);
114
- }
79
+ console.log('Adding widget:', slug);
80
+ runNpx(['@sociallane/elements', 'add', '--target', pluginDir, slug], path.dirname(pluginDir));
115
81
 
116
82
  console.log('');
117
83
  console.log('Done. Widget', slug, 'installed. Activate the plugin in WordPress → Plugins if needed.');