@newlogic-digital/cli 1.5.0-next.4 → 1.5.0-next.5

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/index.mjs CHANGED
@@ -150,11 +150,12 @@ if (!command) {
150
150
 
151
151
  -- blocks --
152
152
  ${styleText('green', 'newlogic blocks list')} - Lists all available installable blocks with descriptions
153
- ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name...>')} - Installs one or more blocks by kebab-case or PascalCase name
153
+ ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name[@variant]...>')} - Installs one or more blocks by kebab-case or PascalCase name
154
154
  ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', '<name...>')} - Removes one or more blocks and orphaned dependencies
155
155
  ${styleText('green', 'newlogic blocks update')} - Reinstalls all configured blocks from ${styleText('yellow', 'newlogic.config.json')}
156
156
  ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'about-accordion')}
157
157
  ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'AboutAccordion')}
158
+ ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left@stimulus')}
158
159
  ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left contact-info-card hero-floating-text')}
159
160
  ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'about-accordion')}
160
161
  ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'header-nav-left hero-floating-text')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newlogic-digital/cli",
3
- "version": "1.5.0-next.4",
3
+ "version": "1.5.0-next.5",
4
4
  "main": "index.mjs",
5
5
  "bin": {
6
6
  "newlogic-cli": "index.mjs",
@@ -134,13 +134,14 @@ function printBlocksUsage() {
134
134
  styleText(['white', 'bold'], 'Usage:'),
135
135
  '',
136
136
  ` ${styleText('green', 'newlogic blocks list')} - Lists all available blocks with descriptions`,
137
- ` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name...>')} - Adds one or more blocks by kebab-case or PascalCase name`,
137
+ ` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', '<name[@variant]...>')} - Adds one or more blocks by kebab-case or PascalCase name`,
138
138
  ` ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', '<name...>')} - Removes one or more blocks and orphaned dependencies`,
139
139
  ` ${styleText('green', 'newlogic blocks update')} - Reinstalls all configured blocks from ${styleText('yellow', 'newlogic.config.json')}`,
140
140
  '',
141
141
  styleText(['white', 'bold'], 'Examples:'),
142
142
  '',
143
143
  ` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left contact-info-card hero-floating-text')}`,
144
+ ` ${styleText('green', 'newlogic blocks add')} ${styleText('yellow', 'header-nav-left@stimulus')}`,
144
145
  ` ${styleText('green', 'newlogic blocks remove')} ${styleText('yellow', 'header-nav-left hero-floating-text')}`,
145
146
  ].join('\n'))
146
147
  }
@@ -83,6 +83,51 @@ function assertBlockName(name, label = 'block name') {
83
83
  }
84
84
  }
85
85
 
86
+ function parseBlockSpecifier(rawValue, {
87
+ label = 'block name',
88
+ allowObject = false,
89
+ } = {}) {
90
+ if (allowObject && rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
91
+ const name = normalizeBlockName(rawValue.name)
92
+ const variant = rawValue.variant == null ? '' : `${rawValue.variant}`.trim()
93
+
94
+ assertBlockName(name, 'configured block name')
95
+
96
+ if (rawValue.variant != null && !variant) {
97
+ throw new Error(`Configured block "${name}" has an empty variant`)
98
+ }
99
+
100
+ return {
101
+ name,
102
+ variant: variant || undefined,
103
+ variantExplicit: rawValue.variant != null,
104
+ }
105
+ }
106
+
107
+ const value = `${rawValue ?? ''}`.trim()
108
+
109
+ if (!value) {
110
+ throw new Error(`Missing ${label}`)
111
+ }
112
+
113
+ const variantSeparator = value.indexOf('@')
114
+ const rawName = variantSeparator === -1 ? value : value.slice(0, variantSeparator)
115
+ const rawVariant = variantSeparator === -1 ? '' : value.slice(variantSeparator + 1).trim()
116
+ const name = normalizeBlockName(rawName)
117
+
118
+ assertBlockName(name, label)
119
+
120
+ if (variantSeparator !== -1 && !rawVariant) {
121
+ throw new Error(`Invalid ${label}: "${value}"`)
122
+ }
123
+
124
+ return {
125
+ name,
126
+ variant: rawVariant || undefined,
127
+ variantExplicit: variantSeparator !== -1,
128
+ }
129
+ }
130
+
86
131
  function toBlockKey(block) {
87
132
  return `${block.name}@${block.variant}`
88
133
  }
@@ -151,20 +196,14 @@ function loadProjectConfig(projectRoot) {
151
196
  }
152
197
 
153
198
  const blocks = blocksRaw.map((entry, index) => {
154
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
199
+ if (typeof entry !== 'string' && (!entry || typeof entry !== 'object' || Array.isArray(entry))) {
155
200
  throw new Error(`Invalid block config entry at index ${index}`)
156
201
  }
157
202
 
158
- const name = normalizeBlockName(entry.name)
159
- const variant = `${entry.variant ?? ''}`.trim()
160
-
161
- assertBlockName(name, 'configured block name')
162
-
163
- if (!variant) {
164
- throw new Error(`Configured block "${name}" is missing a variant`)
165
- }
166
-
167
- return { name, variant }
203
+ return parseBlockSpecifier(entry, {
204
+ label: `block config entry at index ${index}`,
205
+ allowObject: true,
206
+ })
168
207
  })
169
208
 
170
209
  return { configPath, config, blocks: dedupeExplicitBlocks(blocks) }
@@ -173,7 +212,7 @@ function loadProjectConfig(projectRoot) {
173
212
  function saveProjectConfig(configPath, config, blocks) {
174
213
  const nextConfig = {
175
214
  ...config,
176
- blocks,
215
+ blocks: blocks.map(block => block.variantExplicit ? `${block.name}@${block.variant}` : block.name),
177
216
  }
178
217
 
179
218
  fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`)
@@ -194,15 +233,25 @@ function normalizeRequestedBlockNames(rawNames, label = 'block name') {
194
233
  throw new Error(`Missing ${label}`)
195
234
  }
196
235
 
197
- const normalizedNames = rawNames.map((rawName) => {
198
- const name = normalizeBlockName(rawName)
236
+ const normalizedNames = rawNames
237
+ .map(rawName => parseBlockSpecifier(rawName, { label }).name)
199
238
 
200
- assertBlockName(name, label)
239
+ return [...new Set(normalizedNames)]
240
+ }
201
241
 
202
- return name
203
- })
242
+ function normalizeRequestedBlocks(rawNames, label = 'block name') {
243
+ if (!Array.isArray(rawNames) || rawNames.length === 0) {
244
+ throw new Error(`Missing ${label}`)
245
+ }
204
246
 
205
- return [...new Set(normalizedNames)]
247
+ const blocks = rawNames.map(rawName => parseBlockSpecifier(rawName, { label }))
248
+ const byName = new Map()
249
+
250
+ for (const block of blocks) {
251
+ byName.set(block.name, block)
252
+ }
253
+
254
+ return [...byName.values()]
206
255
  }
207
256
 
208
257
  async function createMetaValidator(repository) {
@@ -488,16 +537,21 @@ export function createBlocksService({
488
537
  }
489
538
 
490
539
  async function addBlocks(rawNames) {
491
- const names = normalizeRequestedBlockNames(rawNames)
540
+ const requestedBlocks = normalizeRequestedBlocks(rawNames)
492
541
  const rootBlocks = []
493
542
 
494
- for (const name of names) {
495
- const meta = await getMeta(name)
543
+ for (const requestedBlock of requestedBlocks) {
544
+ const meta = await getMeta(requestedBlock.name)
496
545
 
497
546
  rootBlocks.push({
498
- name,
499
- variant: meta.install.defaultVariant,
547
+ name: requestedBlock.name,
548
+ variant: requestedBlock.variant,
549
+ variantExplicit: requestedBlock.variantExplicit,
500
550
  })
551
+
552
+ if (requestedBlock.variant && !meta.install?.variants?.[requestedBlock.variant]) {
553
+ throw new Error(`Variant "${requestedBlock.variant}" is not available for block "${requestedBlock.name}"`)
554
+ }
501
555
  }
502
556
 
503
557
  const plan = await resolveInstallPlan(rootBlocks)
@@ -506,12 +560,12 @@ export function createBlocksService({
506
560
 
507
561
  const { configPath, config, blocks } = loadProjectConfig(projectRoot)
508
562
  const nextBlocks = dedupeExplicitBlocks([
509
- ...blocks.filter(block => !names.includes(block.name)),
563
+ ...blocks.filter(block => !requestedBlocks.some(requestedBlock => requestedBlock.name === block.name)),
510
564
  ...rootBlocks,
511
565
  ])
512
566
 
513
567
  saveProjectConfig(configPath, config, nextBlocks)
514
- logger.info(`added ${rootBlocks.map(block => `${block.name}@${block.variant}`).join(', ')}`)
568
+ logger.info(`added ${rootBlocks.map(block => block.variantExplicit ? `${block.name}@${block.variant}` : block.name).join(', ')}`)
515
569
 
516
570
  return {
517
571
  blocks: rootBlocks,