bmad-method 6.11.1-next.2 → 6.11.1-next.21
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/package.json +2 -2
- package/src/bmm-skills/agents/bmad-agent-analyst/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-architect/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-dev/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-pm/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-ux-designer/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-architecture/SKILL.md +1 -1
- package/src/bmm-skills/plan/bmad-architecture/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +2 -0
- package/src/bmm-skills/plan/bmad-prd/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-prfaq/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-product-brief/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-project-context/SKILL.md +23 -12
- package/src/bmm-skills/plan/bmad-project-context/references/best-practices.md +26 -7
- package/src/bmm-skills/plan/bmad-project-context/references/template.md +1 -1
- package/src/bmm-skills/plan/bmad-spec/customize.toml +5 -5
- package/src/bmm-skills/plan/bmad-sprint-planning/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/plan/bmad-ux/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-build/SKILL.md +1 -1
- package/src/bmm-skills/ship/bmad-build/customize.toml +13 -16
- package/src/bmm-skills/ship/bmad-build/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-build/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +12 -7
- package/src/bmm-skills/ship/bmad-build/step-02-plan.md +13 -10
- package/src/bmm-skills/ship/bmad-build/step-03-implement.md +3 -1
- package/src/bmm-skills/ship/bmad-build/step-04-review.md +7 -5
- package/src/bmm-skills/ship/bmad-build/step-oneshot.md +7 -8
- package/src/bmm-skills/ship/bmad-build-auto/customize.toml +16 -18
- package/src/bmm-skills/ship/bmad-build-auto/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-build-auto/step-03-implement.md +5 -1
- package/src/bmm-skills/ship/bmad-build-auto/step-04-review.md +7 -5
- package/src/bmm-skills/ship/bmad-checkpoint-preview/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-code-review/customize.toml +14 -17
- package/src/bmm-skills/ship/bmad-code-review/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-code-review/steps/step-01-gather-context.md +22 -15
- package/src/bmm-skills/ship/bmad-code-review/steps/step-02-review.md +2 -2
- package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +11 -3
- package/src/bmm-skills/ship/bmad-correct-course/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-retrospective/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +3 -1
- package/src/bmm-skills/v6-shims/bmad-create-story/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/customize.toml +1 -6
- package/src/bmm-skills/v6-shims/bmad-document-project/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-quick-dev/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/customize.toml +1 -3
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +21 -22
- package/src/core-skills/bmad-brainstorming/customize.toml +5 -6
- package/src/core-skills/bmad-forge-idea/customize.toml +5 -6
- package/src/core-skills/bmad-party-mode/customize.toml +4 -4
- package/src/core-skills/bmad-review/SKILL.md +5 -4
- package/src/core-skills/bmad-review/customize.toml +1 -1
- package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +16 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +2 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
- package/tools/installer/commands/install.js +2 -0
- package/tools/installer/core/config.js +3 -0
- package/tools/installer/core/installer.js +57 -1
- package/tools/installer/core/manifest-generator.js +6 -0
- package/tools/installer/core/manifest.js +6 -0
- package/tools/installer/core/shim-policy.js +159 -0
- package/tools/installer/ide/_config-driven.js +3 -3
- package/tools/installer/ide/platform-codes.yaml +7 -0
- package/tools/installer/modules/official-modules.js +38 -6
- package/tools/installer/prompts.js +15 -2
- package/tools/installer/ui.js +68 -3
|
@@ -5,6 +5,7 @@ const prompts = require('../prompts');
|
|
|
5
5
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../project-root');
|
|
6
6
|
const { CLIUtils } = require('../cli-utils');
|
|
7
7
|
const { ExternalModuleManager } = require('./external-manager');
|
|
8
|
+
const { discoverShims } = require('../core/shim-policy');
|
|
8
9
|
|
|
9
10
|
class OfficialModules {
|
|
10
11
|
constructor(options = {}) {
|
|
@@ -131,6 +132,23 @@ class OfficialModules {
|
|
|
131
132
|
return { modules };
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
async discoverShims(moduleNames = [], options = {}) {
|
|
136
|
+
const shims = [];
|
|
137
|
+
|
|
138
|
+
for (const moduleName of moduleNames) {
|
|
139
|
+
const sourcePath = await this.findModuleSource(moduleName, {
|
|
140
|
+
silent: true,
|
|
141
|
+
channelOptions: options.channelOptions,
|
|
142
|
+
});
|
|
143
|
+
if (!sourcePath) continue;
|
|
144
|
+
|
|
145
|
+
const moduleShims = await discoverShims(sourcePath);
|
|
146
|
+
for (const shim of moduleShims) shims.push({ ...shim, module: moduleName });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return shims;
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
/**
|
|
135
153
|
* Get module information from a module path
|
|
136
154
|
* @param {string} modulePath - Path to the module directory
|
|
@@ -296,9 +314,13 @@ class OfficialModules {
|
|
|
296
314
|
`Aborting to avoid a partial install with no skills.`,
|
|
297
315
|
);
|
|
298
316
|
}
|
|
299
|
-
await this._copyResolvedSkills(pluginResolution, targetPath, fileTrackingCallback, options.moduleConfig
|
|
317
|
+
await this._copyResolvedSkills(pluginResolution, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
318
|
+
installShims: options.installShims,
|
|
319
|
+
});
|
|
300
320
|
} else {
|
|
301
|
-
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig
|
|
321
|
+
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
322
|
+
installShims: options.installShims,
|
|
323
|
+
});
|
|
302
324
|
}
|
|
303
325
|
|
|
304
326
|
if (!options.skipModuleInstaller) {
|
|
@@ -336,7 +358,7 @@ class OfficialModules {
|
|
|
336
358
|
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
337
359
|
* @param {Object} moduleConfig - Module configuration passed to copy filtering
|
|
338
360
|
*/
|
|
339
|
-
async _copyResolvedSkills(resolved, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
|
|
361
|
+
async _copyResolvedSkills(resolved, targetPath, fileTrackingCallback = null, moduleConfig = {}, installOptions = {}) {
|
|
340
362
|
await fs.ensureDir(targetPath);
|
|
341
363
|
|
|
342
364
|
// Copy each skill directory, flattened by leaf name. Leaf names must be
|
|
@@ -354,7 +376,7 @@ class OfficialModules {
|
|
|
354
376
|
}
|
|
355
377
|
seenLeaves.set(skillDirName, skillPath);
|
|
356
378
|
const skillTarget = path.join(targetPath, skillDirName);
|
|
357
|
-
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig);
|
|
379
|
+
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig, installOptions);
|
|
358
380
|
}
|
|
359
381
|
|
|
360
382
|
// Place module-help.csv at the module root.
|
|
@@ -385,7 +407,9 @@ class OfficialModules {
|
|
|
385
407
|
await fs.remove(targetPath);
|
|
386
408
|
}
|
|
387
409
|
|
|
388
|
-
await this._copyResolvedSkills(resolved, targetPath, fileTrackingCallback, options.moduleConfig
|
|
410
|
+
await this._copyResolvedSkills(resolved, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
411
|
+
installShims: options.installShims,
|
|
412
|
+
});
|
|
389
413
|
|
|
390
414
|
// Create directories declared in module.yaml (strategies 1-4 may have these)
|
|
391
415
|
if (!options.skipModuleInstaller) {
|
|
@@ -522,11 +546,19 @@ class OfficialModules {
|
|
|
522
546
|
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
523
547
|
* @param {Object} moduleConfig - Module configuration with conditional flags
|
|
524
548
|
*/
|
|
525
|
-
async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
|
|
549
|
+
async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}, installOptions = {}) {
|
|
526
550
|
// Get all files in source
|
|
527
551
|
const sourceFiles = await this.getFileList(sourcePath);
|
|
552
|
+
const shimDirectories =
|
|
553
|
+
installOptions.installShims === false
|
|
554
|
+
? (await discoverShims(sourcePath)).map((shim) => shim.relativeDirectory.split(path.sep).join('/'))
|
|
555
|
+
: [];
|
|
528
556
|
|
|
529
557
|
for (const file of sourceFiles) {
|
|
558
|
+
const normalizedFile = file.split(path.sep).join('/');
|
|
559
|
+
if (shimDirectories.some((shimDir) => shimDir === '' || normalizedFile === shimDir || normalizedFile.startsWith(`${shimDir}/`))) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
530
562
|
// Skip sub-modules directory - these are IDE-specific and handled separately
|
|
531
563
|
if (file.startsWith('sub-modules/')) {
|
|
532
564
|
continue;
|
|
@@ -316,7 +316,9 @@ async function autocompleteMultiselect(options) {
|
|
|
316
316
|
|
|
317
317
|
switch (this.state) {
|
|
318
318
|
case 'submit': {
|
|
319
|
-
|
|
319
|
+
const count = this.selectedValues.length;
|
|
320
|
+
const emptyHint = count === 0 && options.emptyLabel ? ` (${options.emptyLabel})` : '';
|
|
321
|
+
return `${title}${color.gray(clack.S_BAR)} ${color.dim(`${count} item${count === 1 ? '' : 's'} selected${emptyHint}`)}`;
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
case 'cancel': {
|
|
@@ -331,7 +333,18 @@ async function autocompleteMultiselect(options) {
|
|
|
331
333
|
|
|
332
334
|
const errorLine = this.state === 'error' ? [`${bar} ${color.yellow(this.error)}`] : [];
|
|
333
335
|
|
|
334
|
-
const
|
|
336
|
+
const emptyLine =
|
|
337
|
+
this.selectedValues.length === 0 && options.emptyLabel
|
|
338
|
+
? [`${bar} ${color.dim(`Nothing selected: installs ${options.emptyLabel}`)}`]
|
|
339
|
+
: [];
|
|
340
|
+
|
|
341
|
+
const headerLines = [
|
|
342
|
+
...`${title}${bar}`.split('\n'),
|
|
343
|
+
`${bar} ${searchDisplay}${matchCount}`,
|
|
344
|
+
...noMatchesLine,
|
|
345
|
+
...errorLine,
|
|
346
|
+
...emptyLine,
|
|
347
|
+
];
|
|
335
348
|
|
|
336
349
|
const footerLines = [`${bar} ${color.dim(hints.join(' • '))}`, `${barEnd}`];
|
|
337
350
|
|
package/tools/installer/ui.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
const channelResolver = require('./modules/channel-resolver');
|
|
18
18
|
const prompts = require('./prompts');
|
|
19
19
|
const { parseSetEntries } = require('./set-overrides');
|
|
20
|
+
const { inferShimPreference, readInstalledSkillIds } = require('./core/shim-policy');
|
|
20
21
|
|
|
21
22
|
const manifest = new Manifest();
|
|
22
23
|
|
|
@@ -110,6 +111,43 @@ async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault =
|
|
|
110
111
|
* UI utilities for the installer
|
|
111
112
|
*/
|
|
112
113
|
class UI {
|
|
114
|
+
async _selectShimPreference({ selectedModules, bmadDir, existing, options, channelOptions, quickUpdate = false }) {
|
|
115
|
+
const { OfficialModules } = require('./modules/official-modules');
|
|
116
|
+
const officialModules = new OfficialModules({ channelOptions });
|
|
117
|
+
const availableShims = await officialModules.discoverShims(selectedModules, { channelOptions });
|
|
118
|
+
|
|
119
|
+
// The prompt is capability-driven. Once the last shim leaves the incoming
|
|
120
|
+
// release this becomes an ordinary empty set, regardless of old state.
|
|
121
|
+
if (availableShims.length === 0) return;
|
|
122
|
+
|
|
123
|
+
const previousManifest = existing ? await manifest.read(bmadDir) : null;
|
|
124
|
+
const installedSkillIds = existing ? await readInstalledSkillIds(bmadDir) : new Set();
|
|
125
|
+
const currentValue = inferShimPreference({
|
|
126
|
+
requested: options.shims,
|
|
127
|
+
persisted: previousManifest?.installShims,
|
|
128
|
+
availableShims,
|
|
129
|
+
installedSkillIds,
|
|
130
|
+
existing,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (typeof options.shims === 'boolean' || options.yes) return currentValue;
|
|
134
|
+
|
|
135
|
+
// clack's confirm never resolves without a TTY: a scripted run would exit mid-install.
|
|
136
|
+
if (!process.stdin.isTTY) return currentValue;
|
|
137
|
+
|
|
138
|
+
// Nothing to give up, so nothing to ask on every single update.
|
|
139
|
+
if (quickUpdate && !currentValue) return currentValue;
|
|
140
|
+
|
|
141
|
+
const verb = currentValue ? 'Keep' : 'Install';
|
|
142
|
+
const message =
|
|
143
|
+
`${verb} ${availableShims.length} deprecated compatibility shim skill(s)? Recommended: No. ` +
|
|
144
|
+
`If you say yes, the deprecated skills will exist as a skill that forwards to its replacement skill. ` +
|
|
145
|
+
`Shims will be removed with v7. You should only retain if you customized a shimmed skill and need to ` +
|
|
146
|
+
`still transition it to the replacement.`;
|
|
147
|
+
|
|
148
|
+
return prompts.confirm({ message, default: currentValue });
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
/**
|
|
114
152
|
* Warn once for each selected module the registry marks deprecated.
|
|
115
153
|
*
|
|
@@ -302,7 +340,7 @@ class UI {
|
|
|
302
340
|
throw new Error('No valid actions available for this installation');
|
|
303
341
|
}
|
|
304
342
|
const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
|
|
305
|
-
const needsFullUpdate = !!options.customSource;
|
|
343
|
+
const needsFullUpdate = !!options.customSource || typeof options.shims === 'boolean';
|
|
306
344
|
actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
|
|
307
345
|
await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
|
|
308
346
|
} else {
|
|
@@ -318,10 +356,21 @@ class UI {
|
|
|
318
356
|
// Quick update never shows the module picker, so this is the only
|
|
319
357
|
// place an existing install of a deprecated module hears about it.
|
|
320
358
|
await this._warnDeprecatedModules(existingInstall.moduleIds || []);
|
|
359
|
+
|
|
360
|
+
const installShims = await this._selectShimPreference({
|
|
361
|
+
selectedModules: existingInstall.moduleIds || [],
|
|
362
|
+
bmadDir,
|
|
363
|
+
existing: true,
|
|
364
|
+
options,
|
|
365
|
+
channelOptions,
|
|
366
|
+
quickUpdate: true,
|
|
367
|
+
});
|
|
368
|
+
|
|
321
369
|
return {
|
|
322
370
|
actionType: 'quick-update',
|
|
323
371
|
directory: confirmedDirectory,
|
|
324
372
|
skipPrompts: options.yes || false,
|
|
373
|
+
installShims: installShims === undefined ? options.shims : installShims,
|
|
325
374
|
};
|
|
326
375
|
}
|
|
327
376
|
|
|
@@ -406,6 +455,13 @@ class UI {
|
|
|
406
455
|
...options,
|
|
407
456
|
channelOptions,
|
|
408
457
|
});
|
|
458
|
+
const installShims = await this._selectShimPreference({
|
|
459
|
+
selectedModules,
|
|
460
|
+
bmadDir,
|
|
461
|
+
existing: true,
|
|
462
|
+
options,
|
|
463
|
+
channelOptions,
|
|
464
|
+
});
|
|
409
465
|
|
|
410
466
|
// Warn about --pin/--next flags that refer to modules the user didn't
|
|
411
467
|
// select, or that target bundled modules (core/bmm) where channel
|
|
@@ -432,6 +488,7 @@ class UI {
|
|
|
432
488
|
skipPrompts: options.yes || false,
|
|
433
489
|
channelOptions,
|
|
434
490
|
_preserveModules: preservedModules,
|
|
491
|
+
installShims,
|
|
435
492
|
};
|
|
436
493
|
}
|
|
437
494
|
}
|
|
@@ -487,6 +544,13 @@ class UI {
|
|
|
487
544
|
...options,
|
|
488
545
|
channelOptions,
|
|
489
546
|
});
|
|
547
|
+
const installShims = await this._selectShimPreference({
|
|
548
|
+
selectedModules,
|
|
549
|
+
bmadDir,
|
|
550
|
+
existing: false,
|
|
551
|
+
options,
|
|
552
|
+
channelOptions,
|
|
553
|
+
});
|
|
490
554
|
|
|
491
555
|
// Warn about --pin/--next flags that refer to modules the user didn't
|
|
492
556
|
// select, or that target bundled modules (core/bmm) where channel
|
|
@@ -512,6 +576,7 @@ class UI {
|
|
|
512
576
|
setOverrides,
|
|
513
577
|
skipPrompts: options.yes || false,
|
|
514
578
|
channelOptions,
|
|
579
|
+
installShims,
|
|
515
580
|
};
|
|
516
581
|
}
|
|
517
582
|
|
|
@@ -1071,9 +1136,9 @@ class UI {
|
|
|
1071
1136
|
message: 'Select official modules to install:',
|
|
1072
1137
|
options: allOptions,
|
|
1073
1138
|
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
1074
|
-
//
|
|
1075
|
-
// legitimate "core only" install rather than a mistake to block on.
|
|
1139
|
+
// Core installs either way and is not a row here, so empty is a valid core-only install.
|
|
1076
1140
|
required: false,
|
|
1141
|
+
emptyLabel: 'core only',
|
|
1077
1142
|
maxItems: allOptions.length,
|
|
1078
1143
|
});
|
|
1079
1144
|
|