bmad-method 6.12.1 → 6.12.3-next.0
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/.claude-plugin/marketplace.json +1 -1
- package/bmad-modules.yaml +7 -0
- package/body.md +17 -0
- package/package.json +1 -1
- package/src/bmm-skills/plan/bmad-architecture/scripts/lint_spine.py +4 -0
- 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/sprint_plan.py +4 -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/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/git_evidence.py +4 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/sprint_status.py +4 -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/core-skills/bmad-customize/scripts/list_customizable_skills.py +4 -0
- package/src/core-skills/bmad-deep-recon/scripts/recon_kit.py +4 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/memlog.py +4 -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/core/installer.js +4 -1
- package/tools/installer/install-messages.yaml +13 -6
- package/tools/installer/modules/channel-plan.js +17 -4
- package/tools/installer/modules/external-manager.js +2 -0
- package/tools/installer/ui.js +21 -28
- package/tools/tests/__pycache__/test_validate_skills.cpython-311.pyc +0 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"displayName": "BMad Method Analyze Plan Build",
|
|
15
15
|
"source": "./",
|
|
16
16
|
"description": "Full-lifecycle AI development framework — agents and workflows for product analysis, planning, architecture, and implementation.",
|
|
17
|
-
"version": "6.12.
|
|
17
|
+
"version": "6.12.3",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Brian (BMad) Madison"
|
|
20
20
|
},
|
package/bmad-modules.yaml
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
# override with --channel/--pin/--next. Valid values: stable | next.
|
|
9
9
|
# Omit to inherit the installer's hardcoded default (stable).
|
|
10
10
|
#
|
|
11
|
+
# pin (optional) — a git tag to install instead of the channel. Use it when
|
|
12
|
+
# newer releases no longer work with this installer. The pin is the default for
|
|
13
|
+
# every module that gets no explicit choice. --pin, --next=CODE and a stable or
|
|
14
|
+
# next pick in the interactive picker override it; --channel and --all-* do not.
|
|
15
|
+
#
|
|
11
16
|
# deprecated (optional, default false) — when true, the module is hidden from
|
|
12
17
|
# the installer picker UNLESS it is already installed (so existing users can
|
|
13
18
|
# still see/manage it, but new users are not offered it).
|
|
@@ -55,6 +60,7 @@ modules:
|
|
|
55
60
|
type: bmad-org
|
|
56
61
|
npmPackage: bmad-creative-intelligence-suite
|
|
57
62
|
default_channel: stable
|
|
63
|
+
pin: v0.3.2
|
|
58
64
|
|
|
59
65
|
bmad-method-test-architecture-enterprise:
|
|
60
66
|
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
|
|
@@ -66,6 +72,7 @@ modules:
|
|
|
66
72
|
type: bmad-org
|
|
67
73
|
npmPackage: bmad-method-test-architecture-enterprise
|
|
68
74
|
default_channel: stable
|
|
75
|
+
pin: v1.27.2
|
|
69
76
|
|
|
70
77
|
bmad-loop:
|
|
71
78
|
url: https://github.com/bmad-code-org/bmad-loop
|
package/body.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Picking a channel in the picker is ignored when the module has a registry `pin`.
|
|
2
|
+
The `tea` pin (v1.27.2) wins, and the manifest records `channel: pinned` with no message.
|
|
3
|
+
|
|
4
|
+
`decideChannelForModule` checked `registryPin` before `--next=CODE`, and the picker never recorded a `stable` pick.
|
|
5
|
+
|
|
6
|
+
New order: `--pin` > `--next=CODE` or picker stable > registry pin > `--channel`/`--all-*` > registry default > `stable`.
|
|
7
|
+
|
|
8
|
+
- `channel-plan.js`: explicit per-module picks resolve before the registry pin. New `stableSet` holds a `stable` pick.
|
|
9
|
+
- `ui.js`: `next` is gone from the install picker and from the update flow's channel review. A stable pick fills `stableSet`. A legacy install that tracks `main` moves to stable without a prompt.
|
|
10
|
+
- Tests: "registry pin beats --next" splits into three cases. Picker-level tests drive the install and update flows, including the all-stable fast path that keeps the pin.
|
|
11
|
+
|
|
12
|
+
`--yes`, the all-stable fast path and `--channel` still get the pin.
|
|
13
|
+
|
|
14
|
+
`--next=CODE` stays for headless use.
|
|
15
|
+
On 6.12 it now installs `main` for a pinned module, which fails for TEA v2+ because that layout needs the v7 installer.
|
|
16
|
+
|
|
17
|
+
`npm run quality` passes.
|
package/package.json
CHANGED
|
@@ -254,4 +254,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
254
254
|
|
|
255
255
|
|
|
256
256
|
if __name__ == "__main__":
|
|
257
|
+
if sys.platform == "win32":
|
|
258
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
259
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
260
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
257
261
|
sys.exit(main())
|
package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc
CHANGED
|
Binary file
|
|
@@ -694,4 +694,8 @@ def main(argv=None):
|
|
|
694
694
|
|
|
695
695
|
|
|
696
696
|
if __name__ == "__main__":
|
|
697
|
+
if sys.platform == "win32":
|
|
698
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
699
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
700
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
697
701
|
main()
|
|
Binary file
|
package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc
CHANGED
|
Binary file
|
|
@@ -301,4 +301,8 @@ def main(argv=None):
|
|
|
301
301
|
|
|
302
302
|
|
|
303
303
|
if __name__ == "__main__":
|
|
304
|
+
if sys.platform == "win32":
|
|
305
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
306
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
307
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
304
308
|
main()
|
|
@@ -743,4 +743,8 @@ def main(argv=None):
|
|
|
743
743
|
|
|
744
744
|
|
|
745
745
|
if __name__ == "__main__":
|
|
746
|
+
if sys.platform == "win32":
|
|
747
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
748
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
749
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
746
750
|
main()
|
|
Binary file
|
|
Binary file
|
|
@@ -228,4 +228,8 @@ def main(argv: list[str]) -> int:
|
|
|
228
228
|
|
|
229
229
|
|
|
230
230
|
if __name__ == "__main__":
|
|
231
|
+
if sys.platform == "win32":
|
|
232
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
233
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
234
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
231
235
|
sys.exit(main(sys.argv[1:]))
|
|
@@ -319,4 +319,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
319
319
|
|
|
320
320
|
|
|
321
321
|
if __name__ == "__main__":
|
|
322
|
+
if sys.platform == "win32":
|
|
323
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
324
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
325
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
322
326
|
sys.exit(main())
|
|
Binary file
|
package/src/scripts/memlog.py
CHANGED
|
@@ -221,4 +221,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
if __name__ == "__main__":
|
|
224
|
+
if sys.platform == "win32":
|
|
225
|
+
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
|
226
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
227
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
224
228
|
sys.exit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1326,6 +1326,9 @@ class Installer {
|
|
|
1326
1326
|
rounded: true,
|
|
1327
1327
|
formatBorder: color.green,
|
|
1328
1328
|
});
|
|
1329
|
+
|
|
1330
|
+
const { MessageLoader } = require('../message-loader');
|
|
1331
|
+
await new MessageLoader().displayEndMessage();
|
|
1329
1332
|
}
|
|
1330
1333
|
|
|
1331
1334
|
/**
|
|
@@ -1471,7 +1474,7 @@ class Installer {
|
|
|
1471
1474
|
// 'pinned' would trigger a stable-channel tag lookup at config-collection
|
|
1472
1475
|
// time, burning GitHub API quota and potentially failing.
|
|
1473
1476
|
const manifestData = await this.manifest.read(bmadDir);
|
|
1474
|
-
const channelOptions = { global: null, nextSet: new Set(), pins: new Map(), warnings: [] };
|
|
1477
|
+
const channelOptions = { global: null, nextSet: new Set(), stableSet: new Set(), pins: new Map(), warnings: [] };
|
|
1475
1478
|
if (manifestData?.modulesDetailed) {
|
|
1476
1479
|
const { fetchStableTags, classifyUpgrade, parseGitHubRepo } = require('../modules/channel-resolver');
|
|
1477
1480
|
for (const entry of manifestData.modulesDetailed) {
|
|
@@ -6,16 +6,19 @@
|
|
|
6
6
|
startMessage: |
|
|
7
7
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
8
8
|
|
|
9
|
-
Agile AI-Driven Development. Powered by BMad Core and a growing module ecosystem.
|
|
9
|
+
The Breakthrough Method of Agile AI-Driven Development - the original and best AI SDLC. Powered by BMad Core and a growing module ecosystem.
|
|
10
10
|
Install official and community modules during setup to customize your experience.
|
|
11
11
|
|
|
12
12
|
🌟 100% free. 100% open source. Always.
|
|
13
13
|
No paywalls. No gated content. Knowledge shared, not sold.
|
|
14
14
|
|
|
15
|
+
🚀 BMad v7 is close, and you can preview and test it now:
|
|
16
|
+
npx skills add bmad-code-org/BMAD-METHOD
|
|
17
|
+
then ask your agent for `bmad setup`. After setup, `bmad migrate`
|
|
18
|
+
tests the v6 to v7 migration (still in beta).
|
|
19
|
+
|
|
15
20
|
🐍 REQUIRED: uv (https://docs.astral.sh/uv/) runs the Python scripts BMAD
|
|
16
|
-
skills rely on
|
|
17
|
-
Without it, bmad-build and bmad-build-auto halt on activation.
|
|
18
|
-
If it's not set up yet, ask your AI agent to "install and set up uv for me".
|
|
21
|
+
skills rely on. If it's not set up yet, ask your AI agent to "install and set up uv for me".
|
|
19
22
|
|
|
20
23
|
🌐 CONNECT:
|
|
21
24
|
Website: https://bmadcode.com/
|
|
@@ -33,5 +36,9 @@ startMessage: |
|
|
|
33
36
|
|
|
34
37
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
35
38
|
|
|
36
|
-
#
|
|
37
|
-
endMessage:
|
|
39
|
+
# Display at the END of installation (after the install summary)
|
|
40
|
+
endMessage: |
|
|
41
|
+
🚀 BMad v7 is close, and you can preview and test it now:
|
|
42
|
+
npx skills add bmad-code-org/BMAD-METHOD
|
|
43
|
+
then ask your agent for `bmad setup`. After setup, `bmad migrate`
|
|
44
|
+
tests the v6 to v7 migration (still in beta).
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* 3. Registry defaults (default_channel from bmad-modules.yaml)
|
|
11
11
|
* 4. Hardcoded fallback 'stable'
|
|
12
12
|
*
|
|
13
|
-
* Precedence: --pin > --next=CODE
|
|
13
|
+
* Precedence: --pin > explicit per-module choice (--next=CODE, picker next/stable) > registry pin
|
|
14
|
+
* > --channel (global) > registry default > 'stable'.
|
|
14
15
|
*
|
|
15
16
|
* This module is pure. No prompts, no git, no filesystem.
|
|
16
17
|
*/
|
|
@@ -24,6 +25,7 @@ const VALID_CHANNELS = new Set(['stable', 'next']);
|
|
|
24
25
|
* @returns {{
|
|
25
26
|
* global: 'stable'|'next'|null,
|
|
26
27
|
* nextSet: Set<string>,
|
|
28
|
+
* stableSet: Set<string>,
|
|
27
29
|
* pins: Map<string, string>,
|
|
28
30
|
* warnings: string[]
|
|
29
31
|
* }}
|
|
@@ -58,6 +60,9 @@ function parseChannelOptions(options = {}) {
|
|
|
58
60
|
nextSet.add(trimmed);
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
// Filled only by the interactive per-module picker (no CLI flag).
|
|
64
|
+
const stableSet = new Set();
|
|
65
|
+
|
|
61
66
|
// --pin CODE=TAG (repeatable)
|
|
62
67
|
const pins = new Map();
|
|
63
68
|
for (const spec of options.pin || []) {
|
|
@@ -76,7 +81,7 @@ function parseChannelOptions(options = {}) {
|
|
|
76
81
|
// headless installs with --next=/--pin for a community module don't hang.
|
|
77
82
|
const acceptBypass = options.yes === true || options.acceptBypass === true;
|
|
78
83
|
|
|
79
|
-
return { global, nextSet, pins, warnings, acceptBypass };
|
|
84
|
+
return { global, nextSet, stableSet, pins, warnings, acceptBypass };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
function normalizeChannel(raw, warnings, flagName) {
|
|
@@ -104,11 +109,12 @@ function parsePinSpec(spec) {
|
|
|
104
109
|
* @param {string} args.code
|
|
105
110
|
* @param {Object} args.channelOptions - from parseChannelOptions
|
|
106
111
|
* @param {string} [args.registryDefault] - module's default_channel, if any
|
|
112
|
+
* @param {string} [args.registryPin] - module's registry pin, if any
|
|
107
113
|
* @returns {{channel: 'stable'|'next'|'pinned', pin?: string, source: string}}
|
|
108
114
|
* source describes where the decision came from, for logging / debugging.
|
|
109
115
|
*/
|
|
110
|
-
function decideChannelForModule({ code, channelOptions, registryDefault }) {
|
|
111
|
-
const { global, nextSet, pins } = channelOptions || { nextSet: new Set(), pins: new Map() };
|
|
116
|
+
function decideChannelForModule({ code, channelOptions, registryDefault, registryPin }) {
|
|
117
|
+
const { global, nextSet, stableSet, pins } = channelOptions || { nextSet: new Set(), stableSet: new Set(), pins: new Map() };
|
|
112
118
|
|
|
113
119
|
if (pins && pins.has(code)) {
|
|
114
120
|
return { channel: 'pinned', pin: pins.get(code), source: 'flag:--pin' };
|
|
@@ -116,6 +122,12 @@ function decideChannelForModule({ code, channelOptions, registryDefault }) {
|
|
|
116
122
|
if (nextSet && nextSet.has(code)) {
|
|
117
123
|
return { channel: 'next', source: 'flag:--next' };
|
|
118
124
|
}
|
|
125
|
+
if (stableSet && stableSet.has(code)) {
|
|
126
|
+
return { channel: 'stable', source: 'picker:stable' };
|
|
127
|
+
}
|
|
128
|
+
if (registryPin) {
|
|
129
|
+
return { channel: 'pinned', pin: registryPin, source: 'registry:pin' };
|
|
130
|
+
}
|
|
119
131
|
if (global) {
|
|
120
132
|
return { channel: global, source: 'flag:--channel' };
|
|
121
133
|
}
|
|
@@ -144,6 +156,7 @@ function buildPlan({ modules, channelOptions }) {
|
|
|
144
156
|
code: mod.code,
|
|
145
157
|
channelOptions,
|
|
146
158
|
registryDefault: mod.defaultChannel,
|
|
159
|
+
registryPin: mod.pin,
|
|
147
160
|
}),
|
|
148
161
|
);
|
|
149
162
|
}
|
|
@@ -130,6 +130,7 @@ class ExternalModuleManager {
|
|
|
130
130
|
npmPackage: mod.npm_package || mod.npmPackage || null,
|
|
131
131
|
pluginName: mod.plugin_name || mod.pluginName || null,
|
|
132
132
|
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
|
|
133
|
+
pin: mod.pin || null,
|
|
133
134
|
deprecated: mod.deprecated === true,
|
|
134
135
|
deprecationMessage: mod.deprecation_message || mod['deprecation-message'] || mod.deprecationMessage || null,
|
|
135
136
|
marketplacePlugin: mod.marketplace_plugin === true || mod['marketplace-plugin'] === true || mod.marketplacePlugin === true,
|
|
@@ -273,6 +274,7 @@ class ExternalModuleManager {
|
|
|
273
274
|
code: moduleCode,
|
|
274
275
|
channelOptions: options.channelOptions,
|
|
275
276
|
registryDefault: moduleInfo.defaultChannel,
|
|
277
|
+
registryPin: moduleInfo.pin,
|
|
276
278
|
});
|
|
277
279
|
|
|
278
280
|
// Same-plan short-circuit: a single install calls cloneExternalModule
|
package/tools/installer/ui.js
CHANGED
|
@@ -70,15 +70,17 @@ function buildModuleLabel(name, latestVersion, installedVersion = '') {
|
|
|
70
70
|
* @param {Object} options
|
|
71
71
|
* @param {string|null} [options.repoUrl] - Module repository URL for tag resolution
|
|
72
72
|
* @param {string|null} [options.registryDefault] - Registry default channel
|
|
73
|
+
* @param {string|null} [options.registryPin] - Registry pinned tag
|
|
73
74
|
* @param {Object|null} [options.channelOptions] - Parsed installer channel options
|
|
74
75
|
* @returns {Promise<{version: string, lookupAttempted: boolean, lookupSucceeded: boolean}>}
|
|
75
76
|
*/
|
|
76
|
-
async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault = null, channelOptions = null } = {}) {
|
|
77
|
+
async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault = null, registryPin = null, channelOptions = null } = {}) {
|
|
77
78
|
if (repoUrl) {
|
|
78
79
|
const plan = decideChannelForModule({
|
|
79
80
|
code: moduleCode,
|
|
80
81
|
channelOptions,
|
|
81
82
|
registryDefault,
|
|
83
|
+
registryPin,
|
|
82
84
|
});
|
|
83
85
|
|
|
84
86
|
try {
|
|
@@ -291,6 +293,13 @@ class UI {
|
|
|
291
293
|
}
|
|
292
294
|
confirmedDirectory = expandedDir;
|
|
293
295
|
await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
|
|
296
|
+
} else if (options.yes) {
|
|
297
|
+
confirmedDirectory = process.cwd();
|
|
298
|
+
const validation = this.validateDirectorySync(confirmedDirectory);
|
|
299
|
+
if (validation) {
|
|
300
|
+
throw new Error(`Invalid directory: ${validation}`);
|
|
301
|
+
}
|
|
302
|
+
await prompts.log.info(`Using current directory: ${confirmedDirectory}`);
|
|
294
303
|
} else {
|
|
295
304
|
confirmedDirectory = await this.getConfirmedDirectory();
|
|
296
305
|
}
|
|
@@ -1058,10 +1067,10 @@ class UI {
|
|
|
1058
1067
|
const allOptions = [];
|
|
1059
1068
|
const initialValues = [];
|
|
1060
1069
|
|
|
1061
|
-
const buildModuleEntry = async (code, name, description, isDefault, repoUrl = null, registryDefault = null) => {
|
|
1070
|
+
const buildModuleEntry = async (code, name, description, isDefault, repoUrl = null, registryDefault = null, registryPin = null) => {
|
|
1062
1071
|
const isInstalled = installedModuleIds.has(code);
|
|
1063
1072
|
const installedVersion = installedModuleVersions.get(code) || '';
|
|
1064
|
-
const versionState = await getModuleVersion(code, { repoUrl, registryDefault, channelOptions });
|
|
1073
|
+
const versionState = await getModuleVersion(code, { repoUrl, registryDefault, registryPin, channelOptions });
|
|
1065
1074
|
const label = buildModuleLabel(name, versionState.version, installedVersion);
|
|
1066
1075
|
return {
|
|
1067
1076
|
label,
|
|
@@ -1109,6 +1118,7 @@ class UI {
|
|
|
1109
1118
|
mod.defaultSelected,
|
|
1110
1119
|
mod.url || null,
|
|
1111
1120
|
mod.defaultChannel || null,
|
|
1121
|
+
mod.pin || null,
|
|
1112
1122
|
);
|
|
1113
1123
|
if (mod.deprecated && mod.deprecationMessage) {
|
|
1114
1124
|
entry.hint = entry.hint ? `${entry.hint} — ${mod.deprecationMessage}` : mod.deprecationMessage;
|
|
@@ -1970,15 +1980,12 @@ class UI {
|
|
|
1970
1980
|
message: `${code}: choose a channel`,
|
|
1971
1981
|
choices: [
|
|
1972
1982
|
{ name: stableLabel, value: 'stable' },
|
|
1973
|
-
{ name: 'next (main HEAD \u2014 current development)', value: 'next' },
|
|
1974
1983
|
{ name: 'pin (specific version)', value: 'pin' },
|
|
1975
1984
|
],
|
|
1976
1985
|
default: 'stable',
|
|
1977
1986
|
});
|
|
1978
1987
|
|
|
1979
|
-
if (choice === '
|
|
1980
|
-
channelOptions.nextSet.add(code);
|
|
1981
|
-
} else if (choice === 'pin') {
|
|
1988
|
+
if (choice === 'pin') {
|
|
1982
1989
|
const pinValue = await prompts.text({
|
|
1983
1990
|
message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
|
|
1984
1991
|
validate: (value) => {
|
|
@@ -1988,8 +1995,9 @@ class UI {
|
|
|
1988
1995
|
},
|
|
1989
1996
|
});
|
|
1990
1997
|
channelOptions.pins.set(code, String(pinValue).trim());
|
|
1998
|
+
} else {
|
|
1999
|
+
channelOptions.stableSet.add(code);
|
|
1991
2000
|
}
|
|
1992
|
-
// 'stable' is the default; nothing to record.
|
|
1993
2001
|
}
|
|
1994
2002
|
}
|
|
1995
2003
|
|
|
@@ -2004,8 +2012,7 @@ class UI {
|
|
|
2004
2012
|
* frozen unless the user also passes `--pin CODE=NEW_TAG`.
|
|
2005
2013
|
* - On `next`: no prompt (pull HEAD).
|
|
2006
2014
|
* - On `pinned`: no prompt (stays pinned).
|
|
2007
|
-
* - No channel recorded and `version: null`:
|
|
2008
|
-
* ("Switch to stable / Keep on next").
|
|
2015
|
+
* - No channel recorded and `version: null`: moves to stable, no prompt.
|
|
2009
2016
|
*
|
|
2010
2017
|
* Decisions that freeze the current version are applied by adding a pin to
|
|
2011
2018
|
* `channelOptions.pins` so downstream clone logic honors them.
|
|
@@ -2040,7 +2047,7 @@ class UI {
|
|
|
2040
2047
|
});
|
|
2041
2048
|
if (existingWithChannel.length > 0) {
|
|
2042
2049
|
reviewChannels = await prompts.confirm({
|
|
2043
|
-
message: 'Review channel assignments (stable /
|
|
2050
|
+
message: 'Review channel assignments (stable / pin) for your existing modules?',
|
|
2044
2051
|
default: false,
|
|
2045
2052
|
});
|
|
2046
2053
|
}
|
|
@@ -2061,7 +2068,7 @@ class UI {
|
|
|
2061
2068
|
const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
|
|
2062
2069
|
|
|
2063
2070
|
// Legacy migration: manifest carries no channel and a null/empty
|
|
2064
|
-
// version.
|
|
2071
|
+
// version. The next channel is gone from the picker, so move it to stable.
|
|
2065
2072
|
const recordedChannel = prev.channel || null;
|
|
2066
2073
|
const needsMigration = !recordedChannel && (prev.version == null || prev.version === '');
|
|
2067
2074
|
if (needsMigration) {
|
|
@@ -2069,15 +2076,7 @@ class UI {
|
|
|
2069
2076
|
// Conservative headless default: stable.
|
|
2070
2077
|
continue;
|
|
2071
2078
|
}
|
|
2072
|
-
|
|
2073
|
-
message: `${code}: your existing install tracks the main branch. Switch to stable releases (recommended for production), or keep on main?`,
|
|
2074
|
-
choices: [
|
|
2075
|
-
{ name: 'Switch to stable', value: 'stable' },
|
|
2076
|
-
{ name: 'Keep on main (next)', value: 'next' },
|
|
2077
|
-
],
|
|
2078
|
-
default: 'stable',
|
|
2079
|
-
});
|
|
2080
|
-
if (chosen === 'next') channelOptions.nextSet.add(code);
|
|
2079
|
+
channelOptions.stableSet.add(code);
|
|
2081
2080
|
continue;
|
|
2082
2081
|
}
|
|
2083
2082
|
|
|
@@ -2095,9 +2094,6 @@ class UI {
|
|
|
2095
2094
|
if (recordedChannel !== 'stable') {
|
|
2096
2095
|
switchChoices.push({ name: 'Switch to stable (released version)', value: 'stable' });
|
|
2097
2096
|
}
|
|
2098
|
-
if (recordedChannel !== 'next') {
|
|
2099
|
-
switchChoices.push({ name: 'Switch to next (main HEAD)', value: 'next' });
|
|
2100
|
-
}
|
|
2101
2097
|
switchChoices.push({ name: 'Pin to a specific version tag', value: 'pin' });
|
|
2102
2098
|
|
|
2103
2099
|
const choice = await prompts.select({
|
|
@@ -2106,10 +2102,6 @@ class UI {
|
|
|
2106
2102
|
default: 'keep',
|
|
2107
2103
|
});
|
|
2108
2104
|
|
|
2109
|
-
if (choice === 'next') {
|
|
2110
|
-
channelOptions.nextSet.add(code);
|
|
2111
|
-
continue;
|
|
2112
|
-
}
|
|
2113
2105
|
if (choice === 'pin') {
|
|
2114
2106
|
const pinValue = await prompts.text({
|
|
2115
2107
|
message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
|
|
@@ -2125,6 +2117,7 @@ class UI {
|
|
|
2125
2117
|
if (choice === 'stable') {
|
|
2126
2118
|
// Switch to stable: install at the top stable tag without an
|
|
2127
2119
|
// upgrade-classification prompt (the user explicitly opted in).
|
|
2120
|
+
channelOptions.stableSet.add(code);
|
|
2128
2121
|
// Also warm the tag cache here so the actual clone step doesn't
|
|
2129
2122
|
// need a second GitHub API call (can hit rate limits).
|
|
2130
2123
|
if (parsed) {
|
|
Binary file
|