@hominis/fireforge 0.10.0 → 0.11.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +93 -1
  2. package/README.md +126 -239
  3. package/dist/bin/fireforge.js +26 -0
  4. package/dist/src/cli.d.ts +1 -1
  5. package/dist/src/cli.js +131 -52
  6. package/dist/src/commands/bootstrap.js +6 -2
  7. package/dist/src/commands/build.js +4 -2
  8. package/dist/src/commands/discard.js +16 -4
  9. package/dist/src/commands/doctor-furnace.d.ts +8 -0
  10. package/dist/src/commands/doctor-furnace.js +422 -0
  11. package/dist/src/commands/doctor.d.ts +115 -0
  12. package/dist/src/commands/doctor.js +327 -258
  13. package/dist/src/commands/download.js +16 -1
  14. package/dist/src/commands/export-all.js +15 -0
  15. package/dist/src/commands/export-flow.d.ts +91 -0
  16. package/dist/src/commands/export-flow.js +344 -0
  17. package/dist/src/commands/export.js +151 -5
  18. package/dist/src/commands/furnace/apply.d.ts +3 -2
  19. package/dist/src/commands/furnace/apply.js +169 -36
  20. package/dist/src/commands/furnace/create.js +162 -52
  21. package/dist/src/commands/furnace/deploy.js +156 -144
  22. package/dist/src/commands/furnace/diff.d.ts +8 -4
  23. package/dist/src/commands/furnace/diff.js +142 -73
  24. package/dist/src/commands/furnace/index.d.ts +6 -2
  25. package/dist/src/commands/furnace/index.js +76 -25
  26. package/dist/src/commands/furnace/init.d.ts +11 -0
  27. package/dist/src/commands/furnace/init.js +76 -0
  28. package/dist/src/commands/furnace/list.d.ts +4 -1
  29. package/dist/src/commands/furnace/list.js +35 -3
  30. package/dist/src/commands/furnace/override.d.ts +8 -0
  31. package/dist/src/commands/furnace/override.js +216 -26
  32. package/dist/src/commands/furnace/preview.js +184 -30
  33. package/dist/src/commands/furnace/refresh.d.ts +10 -0
  34. package/dist/src/commands/furnace/refresh.js +268 -0
  35. package/dist/src/commands/furnace/remove.js +285 -89
  36. package/dist/src/commands/furnace/rename.d.ts +5 -0
  37. package/dist/src/commands/furnace/rename.js +308 -0
  38. package/dist/src/commands/furnace/scan.d.ts +4 -1
  39. package/dist/src/commands/furnace/scan.js +72 -11
  40. package/dist/src/commands/furnace/status.js +85 -20
  41. package/dist/src/commands/furnace/sync.d.ts +12 -0
  42. package/dist/src/commands/furnace/sync.js +77 -0
  43. package/dist/src/commands/furnace/validate.d.ts +4 -1
  44. package/dist/src/commands/furnace/validate.js +99 -3
  45. package/dist/src/commands/furnace/validation-output.d.ts +24 -1
  46. package/dist/src/commands/furnace/validation-output.js +93 -1
  47. package/dist/src/commands/import.js +37 -4
  48. package/dist/src/commands/lint.js +11 -2
  49. package/dist/src/commands/manifest.d.ts +39 -0
  50. package/dist/src/commands/manifest.js +59 -0
  51. package/dist/src/commands/patch/delete.d.ts +28 -0
  52. package/dist/src/commands/patch/delete.js +209 -0
  53. package/dist/src/commands/patch/index.d.ts +17 -0
  54. package/dist/src/commands/patch/index.js +25 -0
  55. package/dist/src/commands/patch/reorder.d.ts +30 -0
  56. package/dist/src/commands/patch/reorder.js +377 -0
  57. package/dist/src/commands/re-export-files.d.ts +17 -0
  58. package/dist/src/commands/re-export-files.js +177 -0
  59. package/dist/src/commands/re-export.js +44 -0
  60. package/dist/src/commands/rebase/abort.d.ts +1 -1
  61. package/dist/src/commands/rebase/abort.js +12 -3
  62. package/dist/src/commands/rebase/confirm.d.ts +3 -3
  63. package/dist/src/commands/rebase/confirm.js +4 -4
  64. package/dist/src/commands/rebase/index.js +13 -4
  65. package/dist/src/commands/reset.js +20 -4
  66. package/dist/src/commands/run.js +46 -1
  67. package/dist/src/commands/setup-support.js +5 -5
  68. package/dist/src/commands/status.js +97 -6
  69. package/dist/src/commands/test.js +5 -37
  70. package/dist/src/commands/verify.d.ts +31 -0
  71. package/dist/src/commands/verify.js +126 -0
  72. package/dist/src/core/build-prepare.js +40 -16
  73. package/dist/src/core/destructive.d.ts +96 -0
  74. package/dist/src/core/destructive.js +137 -0
  75. package/dist/src/core/diff-hunks.d.ts +73 -0
  76. package/dist/src/core/diff-hunks.js +268 -0
  77. package/dist/src/core/firefox.d.ts +1 -1
  78. package/dist/src/core/firefox.js +1 -1
  79. package/dist/src/core/furnace-apply-helpers.d.ts +89 -6
  80. package/dist/src/core/furnace-apply-helpers.js +302 -57
  81. package/dist/src/core/furnace-apply-output.d.ts +16 -0
  82. package/dist/src/core/furnace-apply-output.js +57 -0
  83. package/dist/src/core/furnace-apply.d.ts +21 -3
  84. package/dist/src/core/furnace-apply.js +260 -29
  85. package/dist/src/core/furnace-checksum-utils.d.ts +4 -0
  86. package/dist/src/core/furnace-checksum-utils.js +24 -0
  87. package/dist/src/core/furnace-config.d.ts +28 -1
  88. package/dist/src/core/furnace-config.js +180 -17
  89. package/dist/src/core/furnace-constants.d.ts +22 -0
  90. package/dist/src/core/furnace-constants.js +36 -0
  91. package/dist/src/core/furnace-graph-utils.d.ts +11 -0
  92. package/dist/src/core/furnace-graph-utils.js +94 -0
  93. package/dist/src/core/furnace-operation.d.ts +108 -0
  94. package/dist/src/core/furnace-operation.js +220 -0
  95. package/dist/src/core/furnace-refresh.d.ts +20 -0
  96. package/dist/src/core/furnace-refresh.js +118 -0
  97. package/dist/src/core/furnace-registration-ast.d.ts +5 -0
  98. package/dist/src/core/furnace-registration-ast.js +134 -4
  99. package/dist/src/core/furnace-registration-remove.d.ts +25 -3
  100. package/dist/src/core/furnace-registration-remove.js +196 -62
  101. package/dist/src/core/furnace-registration-validate.d.ts +13 -1
  102. package/dist/src/core/furnace-registration-validate.js +15 -3
  103. package/dist/src/core/furnace-registration.d.ts +27 -4
  104. package/dist/src/core/furnace-registration.js +93 -11
  105. package/dist/src/core/furnace-rollback.d.ts +11 -0
  106. package/dist/src/core/furnace-rollback.js +78 -7
  107. package/dist/src/core/furnace-scanner.d.ts +8 -2
  108. package/dist/src/core/furnace-scanner.js +152 -55
  109. package/dist/src/core/furnace-stories.js +7 -5
  110. package/dist/src/core/furnace-validate-accessibility.js +7 -1
  111. package/dist/src/core/furnace-validate-compatibility.d.ts +1 -1
  112. package/dist/src/core/furnace-validate-compatibility.js +85 -1
  113. package/dist/src/core/furnace-validate-helpers.d.ts +4 -0
  114. package/dist/src/core/furnace-validate-helpers.js +31 -0
  115. package/dist/src/core/furnace-validate-registration.d.ts +17 -2
  116. package/dist/src/core/furnace-validate-registration.js +73 -3
  117. package/dist/src/core/furnace-validate-structure.d.ts +10 -2
  118. package/dist/src/core/furnace-validate-structure.js +45 -3
  119. package/dist/src/core/furnace-validate.d.ts +10 -1
  120. package/dist/src/core/furnace-validate.js +80 -6
  121. package/dist/src/core/furnace-version-drift.d.ts +55 -0
  122. package/dist/src/core/furnace-version-drift.js +101 -0
  123. package/dist/src/core/git-file-ops.d.ts +8 -0
  124. package/dist/src/core/git-file-ops.js +19 -6
  125. package/dist/src/core/lint-projection.d.ts +25 -0
  126. package/dist/src/core/lint-projection.js +44 -0
  127. package/dist/src/core/mach.d.ts +4 -2
  128. package/dist/src/core/mach.js +17 -2
  129. package/dist/src/core/markdown-table.d.ts +104 -0
  130. package/dist/src/core/markdown-table.js +266 -0
  131. package/dist/src/core/ownership-table.d.ts +53 -0
  132. package/dist/src/core/ownership-table.js +144 -0
  133. package/dist/src/core/patch-apply.d.ts +17 -3
  134. package/dist/src/core/patch-apply.js +86 -8
  135. package/dist/src/core/patch-export.d.ts +119 -5
  136. package/dist/src/core/patch-export.js +183 -25
  137. package/dist/src/core/patch-lint-cross.d.ts +195 -0
  138. package/dist/src/core/patch-lint-cross.js +428 -0
  139. package/dist/src/core/patch-lint-diff.d.ts +33 -0
  140. package/dist/src/core/patch-lint-diff.js +84 -0
  141. package/dist/src/core/patch-lint.d.ts +2 -4
  142. package/dist/src/core/patch-lint.js +12 -50
  143. package/dist/src/core/patch-lock.js +2 -1
  144. package/dist/src/core/patch-manifest-io.d.ts +102 -1
  145. package/dist/src/core/patch-manifest-io.js +270 -2
  146. package/dist/src/core/patch-manifest-query.d.ts +1 -1
  147. package/dist/src/core/patch-manifest-query.js +1 -1
  148. package/dist/src/core/patch-manifest.d.ts +1 -1
  149. package/dist/src/core/patch-manifest.js +1 -1
  150. package/dist/src/core/patch-transform.d.ts +12 -0
  151. package/dist/src/core/patch-transform.js +21 -7
  152. package/dist/src/core/token-manager.js +67 -69
  153. package/dist/src/core/wire-destroy.js +6 -3
  154. package/dist/src/core/wire-init.js +10 -4
  155. package/dist/src/core/wire-subscript.js +9 -3
  156. package/dist/src/core/wire-utils.d.ts +52 -5
  157. package/dist/src/core/wire-utils.js +69 -6
  158. package/dist/src/errors/base.d.ts +20 -0
  159. package/dist/src/errors/base.js +24 -0
  160. package/dist/src/errors/furnace.js +7 -1
  161. package/dist/src/errors/rebase.js +6 -1
  162. package/dist/src/types/commands/index.d.ts +1 -1
  163. package/dist/src/types/commands/options.d.ts +125 -4
  164. package/dist/src/types/commands/patches.d.ts +11 -1
  165. package/dist/src/types/config.d.ts +1 -1
  166. package/dist/src/types/furnace.d.ts +55 -1
  167. package/dist/src/utils/fs.d.ts +12 -0
  168. package/dist/src/utils/fs.js +30 -1
  169. package/dist/src/utils/package-root.d.ts +5 -0
  170. package/dist/src/utils/package-root.js +12 -0
  171. package/dist/src/utils/process.js +9 -4
  172. package/dist/src/utils/validation.d.ts +20 -2
  173. package/dist/src/utils/validation.js +26 -3
  174. package/package.json +1 -1
package/dist/src/cli.js CHANGED
@@ -1,31 +1,8 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { existsSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
- import { Command } from 'commander';
5
- import { registerBootstrap } from './commands/bootstrap.js';
6
- import { registerBuild } from './commands/build.js';
7
- import { registerConfig } from './commands/config.js';
8
- import { registerDiscard } from './commands/discard.js';
9
- import { registerDoctor } from './commands/doctor.js';
10
- import { registerDownload } from './commands/download.js';
11
- import { registerExport } from './commands/export.js';
12
- import { registerExportAll } from './commands/export-all.js';
13
- import { registerFurnace } from './commands/furnace/index.js';
14
- import { registerImport } from './commands/import.js';
15
- import { registerLint } from './commands/lint.js';
16
- import { registerPackage } from './commands/package.js';
17
- import { registerReExport } from './commands/re-export.js';
18
- import { registerRebase } from './commands/rebase.js';
19
- import { registerRegister } from './commands/register.js';
20
- import { registerReset } from './commands/reset.js';
21
- import { registerResolve } from './commands/resolve.js';
22
- import { registerRun } from './commands/run.js';
23
- import { registerSetup } from './commands/setup.js';
24
- import { registerStatus } from './commands/status.js';
25
- import { registerTest } from './commands/test.js';
26
- import { registerToken } from './commands/token.js';
27
- import { registerWatch } from './commands/watch.js';
28
- import { registerWire } from './commands/wire.js';
4
+ import { Command, Help } from 'commander';
5
+ import { COMMAND_MANIFEST } from './commands/manifest.js';
29
6
  import { CancellationError, CommandError, FireForgeError } from './errors/base.js';
30
7
  import { ExitCode } from './errors/codes.js';
31
8
  import { toError } from './utils/errors.js';
@@ -78,23 +55,31 @@ export function resetBrokenPipeHandlerForTests() {
78
55
  state[brokenPipeInstalledKey] = undefined;
79
56
  state[brokenPipeListenerKey] = undefined;
80
57
  }
58
+ /**
59
+ * Maximum number of directory levels to walk when searching for
60
+ * `fireforge.json`. Guards against symlink cycles and pathologically
61
+ * deep trees.
62
+ */
63
+ const MAX_PROJECT_ROOT_WALK_DEPTH = 50;
81
64
  /**
82
65
  * Gets the project root directory.
83
66
  * Walks up from the current working directory until a fireforge.json is found.
84
- * Falls back to the current working directory when no project root is found.
67
+ * Throws when no fireforge.json is found within the walk depth limit.
85
68
  */
86
69
  export function getProjectRoot() {
87
70
  const start = resolve(process.cwd());
88
71
  let current = start;
89
- for (;;) {
72
+ for (let depth = 0; depth < MAX_PROJECT_ROOT_WALK_DEPTH; depth++) {
90
73
  if (existsSync(join(current, 'fireforge.json'))) {
91
74
  return current;
92
75
  }
93
76
  const parent = dirname(current);
94
77
  if (parent === current)
95
- return start;
78
+ break;
96
79
  current = parent;
97
80
  }
81
+ throw new Error('Could not find fireforge.json in any parent directory. ' +
82
+ 'Are you inside a FireForge project?');
98
83
  }
99
84
  /**
100
85
  * Wraps a command handler with error handling.
@@ -127,6 +112,117 @@ export function withErrorHandling(handler) {
127
112
  }
128
113
  };
129
114
  }
115
+ /** Human-readable labels for command groups, in display order. */
116
+ const GROUP_LABELS = new Map([
117
+ ['project', 'Project'],
118
+ ['engine', 'Engine'],
119
+ ['workflow', 'Workflow'],
120
+ ['components', 'Components'],
121
+ ['diagnostics', 'Diagnostics'],
122
+ ]);
123
+ /**
124
+ * Builds a grouped help formatter that replaces Commander's flat command
125
+ * list with sections labelled by manifest group.
126
+ */
127
+ function buildGroupedHelpFormatter(manifest) {
128
+ const commandGroupMap = new Map();
129
+ for (const entry of manifest) {
130
+ commandGroupMap.set(entry.name, entry.group);
131
+ }
132
+ return (cmd, helper) => {
133
+ // For subcommands (e.g. `furnace --help`), fall back to Commander's
134
+ // default formatting by calling the prototype method directly.
135
+ if (cmd.parent) {
136
+ return Help.prototype.formatHelp.call(helper, cmd, helper);
137
+ }
138
+ const termWidth = helper.padWidth(cmd, helper);
139
+ const helpWidth = helper.helpWidth ?? 80;
140
+ const output = [];
141
+ // Usage
142
+ output.push(`Usage: ${helper.commandUsage(cmd)}`, '');
143
+ // Description
144
+ const desc = helper.commandDescription(cmd);
145
+ if (desc) {
146
+ output.push(desc, '');
147
+ }
148
+ // Options
149
+ const optionLines = helper.visibleOptions(cmd).map((opt) => {
150
+ const term = helper.optionTerm(opt);
151
+ const desc = helper.optionDescription(opt);
152
+ return formatHelpLine(term, desc, termWidth, helpWidth);
153
+ });
154
+ if (optionLines.length > 0) {
155
+ output.push('Options:');
156
+ output.push(...optionLines);
157
+ output.push('');
158
+ }
159
+ // Grouped commands
160
+ const visibleCommands = helper.visibleCommands(cmd);
161
+ const grouped = new Map();
162
+ const otherLines = [];
163
+ for (const sub of visibleCommands) {
164
+ const name = sub.name();
165
+ const group = commandGroupMap.get(name);
166
+ const term = helper.subcommandTerm(sub);
167
+ const desc = helper.subcommandDescription(sub);
168
+ const line = formatHelpLine(term, desc, termWidth, helpWidth);
169
+ if (!group) {
170
+ // Built-in commands (e.g. "help") go to the end
171
+ otherLines.push(line);
172
+ continue;
173
+ }
174
+ const label = GROUP_LABELS.get(group) ?? group;
175
+ const lines = grouped.get(label) ?? [];
176
+ lines.push(line);
177
+ grouped.set(label, lines);
178
+ }
179
+ for (const [, displayLabel] of GROUP_LABELS) {
180
+ const lines = grouped.get(displayLabel);
181
+ if (!lines || lines.length === 0)
182
+ continue;
183
+ output.push(`${displayLabel}:`);
184
+ output.push(...lines);
185
+ output.push('');
186
+ }
187
+ if (otherLines.length > 0) {
188
+ output.push(...otherLines);
189
+ output.push('');
190
+ }
191
+ return output.join('\n');
192
+ };
193
+ }
194
+ /** Formats a single help line with term and description, wrapping as needed. */
195
+ function formatHelpLine(term, description, termWidth, helpWidth) {
196
+ const padding = ' '.repeat(termWidth - term.length);
197
+ const fullLine = ` ${term}${padding} ${description}`;
198
+ if (fullLine.length <= helpWidth || !description) {
199
+ return fullLine;
200
+ }
201
+ // Wrap long descriptions
202
+ const descWidth = helpWidth - termWidth - 4;
203
+ if (descWidth < 20) {
204
+ return fullLine;
205
+ }
206
+ const words = description.split(' ');
207
+ const lines = [];
208
+ let currentLine = '';
209
+ for (const word of words) {
210
+ if (currentLine.length + word.length + 1 > descWidth && currentLine.length > 0) {
211
+ lines.push(currentLine);
212
+ currentLine = word;
213
+ }
214
+ else {
215
+ currentLine = currentLine.length > 0 ? `${currentLine} ${word}` : word;
216
+ }
217
+ }
218
+ if (currentLine.length > 0) {
219
+ lines.push(currentLine);
220
+ }
221
+ const indent = ' '.repeat(termWidth + 4);
222
+ const first = lines[0] ?? '';
223
+ const rest = lines.slice(1).map((l) => `${indent}${l}`);
224
+ return [` ${term}${padding} ${first}`, ...rest].join('\n');
225
+ }
130
226
  /**
131
227
  * Creates and configures the CLI program.
132
228
  */
@@ -143,31 +239,14 @@ export function createProgram() {
143
239
  setVerbose(true);
144
240
  }
145
241
  });
242
+ const groupedFormatter = buildGroupedHelpFormatter(COMMAND_MANIFEST);
243
+ program.configureHelp({
244
+ formatHelp: groupedFormatter,
245
+ });
146
246
  const ctx = { getProjectRoot, withErrorHandling };
147
- registerSetup(program, ctx);
148
- registerDownload(program, ctx);
149
- registerBootstrap(program, ctx);
150
- registerImport(program, ctx);
151
- registerResolve(program, ctx);
152
- registerBuild(program, ctx);
153
- registerRun(program, ctx);
154
- registerStatus(program, ctx);
155
- registerReset(program, ctx);
156
- registerDiscard(program, ctx);
157
- registerExport(program, ctx);
158
- registerExportAll(program, ctx);
159
- registerReExport(program, ctx);
160
- registerRebase(program, ctx);
161
- registerPackage(program, ctx);
162
- registerWatch(program, ctx);
163
- registerTest(program, ctx);
164
- registerConfig(program, ctx);
165
- registerDoctor(program, ctx);
166
- registerRegister(program, ctx);
167
- registerWire(program, ctx);
168
- registerToken(program, ctx);
169
- registerLint(program, ctx);
170
- registerFurnace(program, ctx);
247
+ for (const entry of COMMAND_MANIFEST) {
248
+ entry.register(program, ctx);
249
+ }
171
250
  return program;
172
251
  }
173
252
  /**
@@ -52,12 +52,16 @@ export async function bootstrapCommand(projectRoot) {
52
52
  throw new BootstrapError();
53
53
  }
54
54
  // mach bootstrap may exit 0 even when sub-downloads fail (e.g. HTTP 403).
55
- // Scan output for known failure patterns and surface them as warnings.
55
+ // Scan output for known failure patterns and surface them prominently.
56
56
  const softFailure = buildBootstrapFailureMessage(`${result.stdout}\n${result.stderr}`);
57
57
  if (softFailure) {
58
58
  info('');
59
+ error('Bootstrap completed with issues:');
59
60
  info(softFailure);
60
- info('Bootstrap exited successfully but the issues above may cause build failures.');
61
+ info('Run "fireforge doctor" to verify your build environment. ' +
62
+ 'These issues may cause build failures if not resolved.');
63
+ outro('Build dependencies installed with warnings');
64
+ return;
61
65
  }
62
66
  outro('Build dependencies installed successfully!');
63
67
  }
@@ -6,8 +6,8 @@ import { getProjectPaths, loadConfig } from '../core/config.js';
6
6
  import { build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts } from '../core/mach.js';
7
7
  import { GeneralError } from '../errors/base.js';
8
8
  import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
9
- import { pathExists } from '../utils/fs.js';
10
- import { error, info, intro, outro, verbose } from '../utils/logger.js';
9
+ import { checkDiskSpace, pathExists } from '../utils/fs.js';
10
+ import { error, info, intro, outro, verbose, warn } from '../utils/logger.js';
11
11
  import { pickDefined } from '../utils/options.js';
12
12
  import { isPositiveInteger } from '../utils/validation.js';
13
13
  function parseJobCount(value) {
@@ -40,6 +40,8 @@ export async function buildCommand(projectRoot, options) {
40
40
  const config = await loadConfig(projectRoot);
41
41
  const paths = getProjectPaths(projectRoot);
42
42
  validateBrandOverride(config.binaryName, options.brand);
43
+ // Disk space pre-flight: a full Firefox build can be ~20 GB
44
+ await checkDiskSpace(projectRoot, 20 * 1024 * 1024 * 1024, warn);
43
45
  // Check if engine exists
44
46
  if (!(await pathExists(paths.engine))) {
45
47
  throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
@@ -1,13 +1,14 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { confirm } from '@clack/prompts';
3
3
  import { getProjectPaths } from '../core/config.js';
4
+ import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
4
5
  import { isGitRepository } from '../core/git.js';
5
6
  import { discardStatusEntry } from '../core/git-file-ops.js';
6
7
  import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
7
8
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
8
9
  import { GitError } from '../errors/git.js';
9
10
  import { pathExists } from '../utils/fs.js';
10
- import { info, intro, isCancel, outro, spinner } from '../utils/logger.js';
11
+ import { info, intro, isCancel, outro, spinner, warn } from '../utils/logger.js';
11
12
  import { pickDefined } from '../utils/options.js';
12
13
  /**
13
14
  * Runs the discard command to revert changes to a specific file.
@@ -32,10 +33,10 @@ export async function discardCommand(projectRoot, file, options = {}) {
32
33
  if (!statusEntry) {
33
34
  throw new GeneralError(`File "${file}" has no changes to discard.`);
34
35
  }
35
- if (!options.force && !options.dryRun) {
36
+ if (!options.yes && !options.dryRun) {
36
37
  const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
37
38
  if (!isInteractive) {
38
- throw new InvalidArgumentError('Interactive confirmation not available. Use --force flag to discard without confirmation.', 'Use: fireforge discard <file> --force');
39
+ throw new InvalidArgumentError('Interactive confirmation not available. Use --yes flag to discard without confirmation.', 'Use: fireforge discard <file> --yes');
39
40
  }
40
41
  const confirmed = await confirm({
41
42
  message: `Discard all changes to ${statusEntry.file}?`,
@@ -58,6 +59,17 @@ export async function discardCommand(projectRoot, file, options = {}) {
58
59
  try {
59
60
  await discardStatusEntry(paths.engine, statusEntry);
60
61
  s.stop(`Discarded changes to ${file}`);
62
+ // Warn when the discarded file is managed by Furnace so the user knows
63
+ // to re-apply if they want the component's deployed state back.
64
+ try {
65
+ const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
66
+ if ([...furnacePrefixes].some((prefix) => file.startsWith(prefix))) {
67
+ warn('This file is managed by Furnace. Run "fireforge furnace apply" to restore it.');
68
+ }
69
+ }
70
+ catch {
71
+ // Furnace config may not exist — skip silently
72
+ }
61
73
  outro('File restored to original state');
62
74
  }
63
75
  catch (error) {
@@ -76,7 +88,7 @@ export function registerDiscard(program, { getProjectRoot, withErrorHandling })
76
88
  .command('discard <file>')
77
89
  .description('Discard changes to a specific file (deletes untracked files)')
78
90
  .option('--dry-run', 'Show what would be discarded without doing it')
79
- .option('--force', 'Skip confirmation prompt')
91
+ .option('-y, --yes', 'Skip confirmation prompt')
80
92
  .action(withErrorHandling(async (file, options) => {
81
93
  await discardCommand(getProjectRoot(), file, pickDefined(options));
82
94
  }));
@@ -0,0 +1,8 @@
1
+ import type { DoctorCheckDefinition } from './doctor.js';
2
+ /**
3
+ * The ordered furnace check group. Exported as an array so `doctor.ts`
4
+ * can splice it into the main registry at the right position. The order
5
+ * here matters: `Furnace configuration` must run before the consumers
6
+ * that read `ctx.furnaceConfig`.
7
+ */
8
+ export declare const FURNACE_DOCTOR_CHECKS: readonly DoctorCheckDefinition[];