@niujiajie/freespec 0.0.0-stage → 1.15.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/LICENSE +22 -0
- package/README.md +58 -2
- package/bin/freespec.js +5 -0
- package/dist/cli/index.d.ts +25 -0
- package/dist/cli/index.js +359 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/workflow/default-schema.d.ts +2 -0
- package/dist/commands/workflow/default-schema.js +4 -0
- package/dist/commands/workflow/index.d.ts +13 -0
- package/dist/commands/workflow/index.js +10 -0
- package/dist/commands/workflow/instructions.d.ts +39 -0
- package/dist/commands/workflow/instructions.js +565 -0
- package/dist/commands/workflow/new-change.d.ts +19 -0
- package/dist/commands/workflow/new-change.js +135 -0
- package/dist/commands/workflow/shared.d.ts +94 -0
- package/dist/commands/workflow/shared.js +169 -0
- package/dist/commands/workflow/status.d.ts +18 -0
- package/dist/commands/workflow/status.js +201 -0
- package/dist/core/artifact-graph/graph.d.ts +72 -0
- package/dist/core/artifact-graph/graph.js +165 -0
- package/dist/core/artifact-graph/index.d.ts +9 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +208 -0
- package/dist/core/artifact-graph/instruction-loader.js +268 -0
- package/dist/core/artifact-graph/outputs.d.ts +19 -0
- package/dist/core/artifact-graph/outputs.js +351 -0
- package/dist/core/artifact-graph/resolver.d.ts +89 -0
- package/dist/core/artifact-graph/resolver.js +342 -0
- package/dist/core/artifact-graph/schema.d.ts +27 -0
- package/dist/core/artifact-graph/schema.js +160 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +40 -0
- package/dist/core/artifact-graph/types.js +59 -0
- package/dist/core/available-tools.d.ts +18 -0
- package/dist/core/available-tools.js +49 -0
- package/dist/core/change-layout.d.ts +17 -0
- package/dist/core/change-layout.js +30 -0
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +23 -0
- package/dist/core/change-metadata/schema.js +46 -0
- package/dist/core/change-status-policy.d.ts +49 -0
- package/dist/core/change-status-policy.js +50 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +17 -0
- package/dist/core/command-generation/adapters/amazon-q.js +32 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +27 -0
- package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
- package/dist/core/command-generation/adapters/atomcode.js +46 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +28 -0
- package/dist/core/command-generation/adapters/bob.d.ts +18 -0
- package/dist/core/command-generation/adapters/bob.js +33 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +32 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
- package/dist/core/command-generation/adapters/codeassistant.js +29 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +29 -0
- package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
- package/dist/core/command-generation/adapters/codestudio.js +27 -0
- package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
- package/dist/core/command-generation/adapters/command-code.js +34 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +33 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +28 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +31 -0
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
- package/dist/core/command-generation/adapters/easycode.js +28 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +28 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +27 -0
- package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
- package/dist/core/command-generation/adapters/gigacode.js +31 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +27 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +30 -0
- package/dist/core/command-generation/adapters/index.d.ts +40 -0
- package/dist/core/command-generation/adapters/index.js +40 -0
- package/dist/core/command-generation/adapters/junie.d.ts +13 -0
- package/dist/core/command-generation/adapters/junie.js +27 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +27 -0
- package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
- package/dist/core/command-generation/adapters/lingma.js +30 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +14 -0
- package/dist/core/command-generation/adapters/opencode.js +38 -0
- package/dist/core/command-generation/adapters/pi.d.ts +18 -0
- package/dist/core/command-generation/adapters/pi.js +39 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +17 -0
- package/dist/core/command-generation/adapters/qwen.js +31 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +29 -0
- package/dist/core/command-generation/generator.js +43 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +114 -0
- package/dist/core/command-generation/toml.d.ts +19 -0
- package/dist/core/command-generation/toml.js +39 -0
- package/dist/core/command-generation/types.d.ts +63 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/command-generation/yaml.d.ts +36 -0
- package/dist/core/command-generation/yaml.js +48 -0
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +56 -0
- package/dist/core/config-schema.d.ts +98 -0
- package/dist/core/config-schema.js +308 -0
- package/dist/core/config.d.ts +50 -0
- package/dist/core/config.js +122 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +144 -0
- package/dist/core/file-view.d.ts +13 -0
- package/dist/core/file-view.js +14 -0
- package/dist/core/global-config.d.ts +101 -0
- package/dist/core/global-config.js +182 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +7 -0
- package/dist/core/init.d.ts +57 -0
- package/dist/core/init.js +836 -0
- package/dist/core/list.d.ts +11 -0
- package/dist/core/list.js +197 -0
- package/dist/core/migration.d.ts +121 -0
- package/dist/core/migration.js +540 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.d.ts +47 -0
- package/dist/core/openspec-root.js +196 -0
- package/dist/core/planning-home.d.ts +16 -0
- package/dist/core/planning-home.js +67 -0
- package/dist/core/profile-sync-drift.d.ts +31 -0
- package/dist/core/profile-sync-drift.js +201 -0
- package/dist/core/profiles.d.ts +19 -0
- package/dist/core/profiles.js +33 -0
- package/dist/core/project-config.d.ts +132 -0
- package/dist/core/project-config.js +519 -0
- package/dist/core/references.d.ts +10 -0
- package/dist/core/references.js +46 -0
- package/dist/core/root-selection.d.ts +59 -0
- package/dist/core/root-selection.js +132 -0
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/ide-restart.d.ts +26 -0
- package/dist/core/shared/ide-restart.js +42 -0
- package/dist/core/shared/index.d.ts +11 -0
- package/dist/core/shared/index.js +11 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +45 -0
- package/dist/core/shared/skill-files.d.ts +7 -0
- package/dist/core/shared/skill-files.js +43 -0
- package/dist/core/shared/skill-generation.d.ts +49 -0
- package/dist/core/shared/skill-generation.js +125 -0
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +99 -0
- package/dist/core/shared/tool-detection.js +318 -0
- package/dist/core/shared-skill-target.d.ts +51 -0
- package/dist/core/shared-skill-target.js +239 -0
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/store/errors.js +22 -0
- package/dist/core/store/index.d.ts +2 -0
- package/dist/core/store/index.js +2 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/optional-workflow.d.ts +69 -0
- package/dist/core/templates/optional-workflow.js +140 -0
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/personal.d.ts +22 -0
- package/dist/core/templates/workflows/personal.js +225 -0
- package/dist/core/update-plan.d.ts +127 -0
- package/dist/core/update-plan.js +390 -0
- package/dist/core/update.d.ts +154 -0
- package/dist/core/update.js +723 -0
- package/dist/core/version-check.d.ts +188 -0
- package/dist/core/version-check.js +906 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +35 -0
- package/dist/prompts/searchable-multi-select.js +188 -0
- package/dist/telemetry/config.d.ts +37 -0
- package/dist/telemetry/config.js +148 -0
- package/dist/telemetry/index.d.ts +39 -0
- package/dist/telemetry/index.js +217 -0
- package/dist/telemetry/opt-out.d.ts +17 -0
- package/dist/telemetry/opt-out.js +27 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +18 -0
- package/dist/ui/welcome-screen.js +199 -0
- package/dist/utils/change-metadata.d.ts +116 -0
- package/dist/utils/change-metadata.js +324 -0
- package/dist/utils/change-utils.d.ts +79 -0
- package/dist/utils/change-utils.js +121 -0
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +104 -0
- package/dist/utils/command-references.js +190 -0
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/executable-path.d.ts +5 -0
- package/dist/utils/executable-path.js +44 -0
- package/dist/utils/file-system.d.ts +58 -0
- package/dist/utils/file-system.js +432 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +85 -0
- package/dist/utils/interactive.js +174 -0
- package/dist/utils/item-discovery.d.ts +15 -0
- package/dist/utils/item-discovery.js +33 -0
- package/dist/utils/line-endings.d.ts +34 -0
- package/dist/utils/line-endings.js +46 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/nested-change.d.ts +35 -0
- package/dist/utils/nested-change.js +185 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +94 -0
- package/dist/utils/task-progress.d.ts +67 -0
- package/dist/utils/task-progress.js +184 -0
- package/package.json +74 -4
- package/schemas//351/273/230/350/256/244/schema.yaml +68 -0
- package/schemas//351/273/230/350/256/244/templates//344/273/273/345/212/241/346/270/205/345/215/225.md +25 -0
- package/schemas//351/273/230/350/256/244/templates//346/265/213/350/257/225/347/224/250/344/276/213.md +34 -0
- package/schemas//351/273/230/350/256/244/templates//350/256/276/350/256/241/346/226/207/346/241/243.md +172 -0
- package/schemas//351/273/230/350/256/244/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +19 -0
- package/skill-files//344/273/273/345/212/241/references//346/212/244/346/240/217.md +43 -0
- package/skill-files//344/273/273/345/212/241/references//347/262/222/345/272/246.md +23 -0
- package/skill-files//344/273/273/345/212/241/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//344/273/273/345/212/241/scripts/check-tasks.mjs +83 -0
- package/skill-files//345/206/231Java/references/Java/350/247/204/350/214/203.md +25 -0
- package/skill-files//345/206/231Java/references//345/215/261/351/231/251/345/206/231/346/263/225.md +27 -0
- package/skill-files//345/206/231Java/references//345/256/214/346/210/220/345/257/271/347/205/247.md +15 -0
- package/skill-files//345/206/231Java/references//346/212/244/346/240/217.md +35 -0
- package/skill-files//345/206/231Java/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//345/206/231Java/references//350/270/251/345/235/221.md +17 -0
- package/skill-files//345/256/241Java/references//346/212/244/346/240/217.md +19 -0
- package/skill-files//345/256/241Java/references//347/244/272/344/276/213.md +23 -0
- package/skill-files//345/256/241Java/references//347/272/246/346/235/237.md +7 -0
- package/skill-files//345/256/241Java/references//350/247/204/345/210/231.md +23 -0
- package/skill-files//345/256/241Java/references//351/242/206/345/237/237/346/243/200/351/241/271.md +58 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//346/212/244/346/240/217.md +18 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//347/273/264/345/272/246.md +17 -0
- package/skill-files//345/256/241/346/226/207/346/241/243/references//350/270/251/345/235/221.md +11 -0
- package/skill-files//346/226/260/345/273/272/references//344/272/244/346/216/245.md +11 -0
- package/skill-files//346/226/260/345/273/272/references//346/212/244/346/240/217.md +14 -0
- package/skill-files//346/226/260/345/273/272/references//347/272/246/346/235/237.md +7 -0
- package/skill-files//346/267/261/346/200/235/config/prompts.yaml +114 -0
- package/skill-files//346/267/261/346/200/235/references//344/270/211/346/226/271/346/240/270/345/257/271.md +72 -0
- package/skill-files//346/267/261/346/200/235/references//344/276/213/345/255/220.md +39 -0
- package/skill-files//346/267/261/346/200/235/references//346/200/216/344/271/210/346/203/263.md +61 -0
- package/skill-files//346/267/261/346/200/235/references//346/212/244/346/240/217.md +26 -0
- package/skill-files//346/267/261/346/200/235/references//347/202/271/351/200/211/346/217/220/351/227/256.md +66 -0
- package/skill-files//346/267/261/346/200/235/references//347/231/273/350/256/260.md +45 -0
- package/skill-files//346/267/261/346/200/235/references//347/272/246/346/235/237.md +9 -0
- package/skill-files//347/224/250/344/276/213/references//346/200/216/344/271/210/345/206/231.md +13 -0
- package/skill-files//347/224/250/344/276/213/references//346/212/244/346/240/217.md +42 -0
- package/skill-files//347/224/250/344/276/213/references//347/272/246/346/235/237.md +5 -0
- package/skill-files//347/224/250/344/276/213/scripts/check-cases.mjs +70 -0
- package/skill-files//350/256/276/350/256/241/references//345/207/206/345/244/207.md +25 -0
- package/skill-files//350/256/276/350/256/241/references//346/212/244/346/240/217.md +134 -0
- package/skill-files//350/256/276/350/256/241/references//346/226/207/351/243/216.md +17 -0
- package/skill-files//350/256/276/350/256/241/references//347/225/214/351/235/242.md +18 -0
- package/skill-files//350/256/276/350/256/241/references//347/261/273/345/236/213.md +19 -0
- package/skill-files//350/256/276/350/256/241/references//347/272/246/346/235/237.md +25 -0
- package/skill-files//350/256/276/350/256/241/scripts/_smoke.mjs +72 -0
- package/skill-files//350/256/276/350/256/241/scripts/check-design.mjs +331 -0
- package/skill-files//351/234/200/346/261/202/references//346/212/244/346/240/217.md +26 -0
- package/skill-files//351/234/200/346/261/202/references//346/226/207/351/243/216.md +23 -0
- package/skill-files//351/234/200/346/261/202/references//346/267/261/346/200/235/346/221/204/345/205/245.md +21 -0
- package/skill-files//351/234/200/346/261/202/references//347/272/246/346/235/237.md +8 -0
- package/skill-files//351/234/200/346/261/202/references//351/234/200/346/261/202/345/276/205/345/256/232.md +13 -0
- package/skill-files//351/234/200/346/261/202/scripts/check-requirements.mjs +60 -0
- package/skill-files//351/252/214/346/224/266/references//346/212/244/346/240/217.md +27 -0
- package/skill-files//351/252/214/346/224/266/references//346/240/270/345/257/271.md +17 -0
- package/skill-files//351/252/214/346/224/266/references//347/272/246/346/235/237.md +7 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import * as nodeFs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { detectLineEnding, matchLineEnding } from './line-endings.js';
|
|
4
|
+
const fs = nodeFs.promises;
|
|
5
|
+
const { constants: fsConstants } = nodeFs;
|
|
6
|
+
function hasOwnerGroupOrOtherWriteBit(stats) {
|
|
7
|
+
return (stats.mode & 0o222) !== 0;
|
|
8
|
+
}
|
|
9
|
+
function hasOwnerGroupOrOtherExecuteBit(stats) {
|
|
10
|
+
return (stats.mode & 0o111) !== 0;
|
|
11
|
+
}
|
|
12
|
+
async function hasWritableModeAndAccess(targetPath) {
|
|
13
|
+
try {
|
|
14
|
+
const stats = await fs.stat(targetPath);
|
|
15
|
+
// POSIX root can often write despite mode bits, but FreeSpec should respect
|
|
16
|
+
// explicit read-only file/directory modes when deciding whether an install
|
|
17
|
+
// path is user-writable. This also keeps permission checks deterministic in
|
|
18
|
+
// root-run CI containers. On Windows, chmod mode bits are not authoritative,
|
|
19
|
+
// so rely on fs.access below.
|
|
20
|
+
if (process.platform !== 'win32' && !hasOwnerGroupOrOtherWriteBit(stats)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (process.platform !== 'win32' && stats.isDirectory() && !hasOwnerGroupOrOtherExecuteBit(stats)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const accessMode = stats.isDirectory()
|
|
27
|
+
? fsConstants.W_OK | fsConstants.X_OK
|
|
28
|
+
: fsConstants.W_OK;
|
|
29
|
+
await fs.access(targetPath, accessMode);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isMarkerOnOwnLine(content, markerIndex, markerLength) {
|
|
37
|
+
let leftIndex = markerIndex - 1;
|
|
38
|
+
while (leftIndex >= 0 && content[leftIndex] !== '\n') {
|
|
39
|
+
const char = content[leftIndex];
|
|
40
|
+
if (char !== ' ' && char !== '\t' && char !== '\r') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
leftIndex--;
|
|
44
|
+
}
|
|
45
|
+
let rightIndex = markerIndex + markerLength;
|
|
46
|
+
while (rightIndex < content.length && content[rightIndex] !== '\n') {
|
|
47
|
+
const char = content[rightIndex];
|
|
48
|
+
if (char !== ' ' && char !== '\t' && char !== '\r') {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
rightIndex++;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function findMarkerIndex(content, marker, fromIndex = 0) {
|
|
56
|
+
let currentIndex = content.indexOf(marker, fromIndex);
|
|
57
|
+
while (currentIndex !== -1) {
|
|
58
|
+
if (isMarkerOnOwnLine(content, currentIndex, marker.length)) {
|
|
59
|
+
return currentIndex;
|
|
60
|
+
}
|
|
61
|
+
currentIndex = content.indexOf(marker, currentIndex + marker.length);
|
|
62
|
+
}
|
|
63
|
+
return -1;
|
|
64
|
+
}
|
|
65
|
+
export class FileSystemUtils {
|
|
66
|
+
/**
|
|
67
|
+
* Converts a path to use forward slashes (POSIX style).
|
|
68
|
+
* Essential for cross-platform compatibility with glob libraries like tinyglobby.
|
|
69
|
+
*/
|
|
70
|
+
static toPosixPath(p) {
|
|
71
|
+
return p.replace(/\\/g, '/');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Returns a canonical absolute path when the target exists.
|
|
75
|
+
* Falls back to path.resolve() so callers can still produce a stable absolute path.
|
|
76
|
+
*/
|
|
77
|
+
static canonicalizeExistingPath(targetPath) {
|
|
78
|
+
try {
|
|
79
|
+
// Prefer the native resolver so Windows short-path aliases are expanded.
|
|
80
|
+
return nodeFs.realpathSync.native(targetPath);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
try {
|
|
84
|
+
return nodeFs.realpathSync(targetPath);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return path.resolve(targetPath);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Refuses a target that leaves an allowed directory, including through an
|
|
93
|
+
* existing symlink in either the target or one of its parent directories.
|
|
94
|
+
* Missing suffixes are resolved from their nearest existing ancestor.
|
|
95
|
+
*/
|
|
96
|
+
static assertPathWithin(allowedDirectory, targetPath) {
|
|
97
|
+
const resolvedDirectory = path.resolve(allowedDirectory);
|
|
98
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
99
|
+
if (!this.isPathWithin(resolvedDirectory, resolvedTarget)) {
|
|
100
|
+
throw new Error(`Path is outside the allowed directory: ${targetPath}`);
|
|
101
|
+
}
|
|
102
|
+
const canonicalDirectory = this.canonicalizePotentialPath(resolvedDirectory);
|
|
103
|
+
const canonicalTarget = this.canonicalizePotentialPath(resolvedTarget);
|
|
104
|
+
if (!this.isPathWithin(canonicalDirectory, canonicalTarget)) {
|
|
105
|
+
throw new Error(`Path is outside the allowed directory: ${targetPath}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Like assertPathWithin, but a targetPath that is itself a symlink may
|
|
110
|
+
* resolve outside allowedDirectory, so a project can keep a managed
|
|
111
|
+
* directory (for example freespec) in another repository. Its parent
|
|
112
|
+
* must still be confined and the link must resolve to an existing path.
|
|
113
|
+
*/
|
|
114
|
+
static assertPathWithinAllowingLinkedLeaf(allowedDirectory, targetPath) {
|
|
115
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
116
|
+
let isLink = false;
|
|
117
|
+
try {
|
|
118
|
+
isLink = nodeFs.lstatSync(resolvedTarget).isSymbolicLink();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error.code !== 'ENOENT') {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!isLink) {
|
|
126
|
+
this.assertPathWithin(allowedDirectory, resolvedTarget);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.assertPathWithin(allowedDirectory, path.dirname(resolvedTarget));
|
|
130
|
+
// Throws for a dangling link, which cannot be proven to be a directory.
|
|
131
|
+
nodeFs.realpathSync.native(resolvedTarget);
|
|
132
|
+
}
|
|
133
|
+
static resolveProjectArtifactPath(projectPath, artifactPath) {
|
|
134
|
+
if (path.isAbsolute(artifactPath)) {
|
|
135
|
+
throw new Error(`Refusing to manage an artifact outside the project: ${artifactPath}`);
|
|
136
|
+
}
|
|
137
|
+
const targetPath = path.join(projectPath, artifactPath);
|
|
138
|
+
this.assertPathWithin(projectPath, targetPath);
|
|
139
|
+
return targetPath;
|
|
140
|
+
}
|
|
141
|
+
static assertProjectArtifactPath(projectPath, targetPath) {
|
|
142
|
+
this.assertPathWithin(projectPath, targetPath);
|
|
143
|
+
}
|
|
144
|
+
static isPathWithin(allowedDirectory, targetPath) {
|
|
145
|
+
const relative = path.relative(allowedDirectory, targetPath);
|
|
146
|
+
return (relative === '' ||
|
|
147
|
+
(relative !== '..' &&
|
|
148
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
149
|
+
!path.isAbsolute(relative)));
|
|
150
|
+
}
|
|
151
|
+
static canonicalizePotentialPath(targetPath) {
|
|
152
|
+
let existingPath = targetPath;
|
|
153
|
+
const missingSegments = [];
|
|
154
|
+
while (true) {
|
|
155
|
+
try {
|
|
156
|
+
// lstat distinguishes a missing path from a dangling symlink. A
|
|
157
|
+
// dangling link cannot be proven confined, so realpath must fail it.
|
|
158
|
+
nodeFs.lstatSync(existingPath);
|
|
159
|
+
const canonicalExisting = nodeFs.realpathSync.native(existingPath);
|
|
160
|
+
return path.resolve(canonicalExisting, ...missingSegments);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const code = error.code;
|
|
164
|
+
if (code !== 'ENOENT') {
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
if (nodeFs.lstatSync(existingPath).isSymbolicLink()) {
|
|
169
|
+
throw new Error(`Cannot verify dangling symbolic link: ${existingPath}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (lstatError) {
|
|
173
|
+
if (lstatError.code !== 'ENOENT') {
|
|
174
|
+
throw lstatError;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const parent = path.dirname(existingPath);
|
|
178
|
+
if (parent === existingPath) {
|
|
179
|
+
throw new Error(`Cannot resolve an existing parent for ${targetPath}`);
|
|
180
|
+
}
|
|
181
|
+
missingSegments.unshift(path.basename(existingPath));
|
|
182
|
+
existingPath = parent;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
static isWindowsBasePath(basePath) {
|
|
187
|
+
return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
|
|
188
|
+
}
|
|
189
|
+
static normalizeSegments(segments) {
|
|
190
|
+
return segments
|
|
191
|
+
.flatMap((segment) => segment.split(/[\\/]+/u))
|
|
192
|
+
.filter((part) => part.length > 0);
|
|
193
|
+
}
|
|
194
|
+
static joinPath(basePath, ...segments) {
|
|
195
|
+
const normalizedSegments = this.normalizeSegments(segments);
|
|
196
|
+
if (this.isWindowsBasePath(basePath)) {
|
|
197
|
+
const normalizedBasePath = path.win32.normalize(basePath);
|
|
198
|
+
return normalizedSegments.length
|
|
199
|
+
? path.win32.join(normalizedBasePath, ...normalizedSegments)
|
|
200
|
+
: normalizedBasePath;
|
|
201
|
+
}
|
|
202
|
+
const posixBasePath = basePath.replace(/\\/g, '/');
|
|
203
|
+
return normalizedSegments.length
|
|
204
|
+
? path.posix.join(posixBasePath, ...normalizedSegments)
|
|
205
|
+
: path.posix.normalize(posixBasePath);
|
|
206
|
+
}
|
|
207
|
+
static async createDirectory(dirPath) {
|
|
208
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
209
|
+
}
|
|
210
|
+
static async fileExists(filePath) {
|
|
211
|
+
try {
|
|
212
|
+
await fs.access(filePath);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (error.code !== 'ENOENT') {
|
|
217
|
+
console.debug(`Unable to check if file exists at ${filePath}: ${error.message}`);
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Finds the first existing parent directory by walking up the directory tree.
|
|
224
|
+
* @param dirPath Starting directory path
|
|
225
|
+
* @returns The first existing directory path, or null if root is reached without finding one
|
|
226
|
+
*/
|
|
227
|
+
static async findFirstExistingDirectory(dirPath) {
|
|
228
|
+
let currentDir = dirPath;
|
|
229
|
+
while (true) {
|
|
230
|
+
try {
|
|
231
|
+
const stats = await fs.stat(currentDir);
|
|
232
|
+
if (stats.isDirectory()) {
|
|
233
|
+
return currentDir;
|
|
234
|
+
}
|
|
235
|
+
// Path component exists but is not a directory (edge case)
|
|
236
|
+
console.debug(`Path component ${currentDir} exists but is not a directory`);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (error.code === 'ENOENT') {
|
|
241
|
+
// Directory doesn't exist, move up one level
|
|
242
|
+
const parentDir = path.dirname(currentDir);
|
|
243
|
+
if (parentDir === currentDir) {
|
|
244
|
+
// Reached filesystem root without finding existing directory
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
currentDir = parentDir;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Unexpected error (permissions, I/O error, etc.)
|
|
251
|
+
console.debug(`Error checking directory ${currentDir}: ${error.message}`);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
static async canWriteFile(filePath) {
|
|
258
|
+
try {
|
|
259
|
+
const stats = await fs.stat(filePath);
|
|
260
|
+
if (stats.isDirectory()) {
|
|
261
|
+
return hasWritableModeAndAccess(filePath);
|
|
262
|
+
}
|
|
263
|
+
if (!stats.isFile()) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return hasWritableModeAndAccess(filePath);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error.code === 'ENOENT') {
|
|
270
|
+
// File doesn't exist - find first existing parent directory and check its permissions
|
|
271
|
+
const parentDir = path.dirname(filePath);
|
|
272
|
+
const existingDir = await this.findFirstExistingDirectory(parentDir);
|
|
273
|
+
if (existingDir === null) {
|
|
274
|
+
// No existing parent directory found (edge case)
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
// Check if the existing parent directory is writable.
|
|
278
|
+
return hasWritableModeAndAccess(existingDir);
|
|
279
|
+
}
|
|
280
|
+
console.debug(`Unable to determine write permissions for ${filePath}: ${error.message}`);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
static async directoryExists(dirPath) {
|
|
285
|
+
try {
|
|
286
|
+
const stats = await fs.stat(dirPath);
|
|
287
|
+
return stats.isDirectory();
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error.code !== 'ENOENT') {
|
|
291
|
+
console.debug(`Unable to check if directory exists at ${dirPath}: ${error.message}`);
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
static async writeFile(filePath, content) {
|
|
297
|
+
const dir = path.dirname(filePath);
|
|
298
|
+
await this.createDirectory(dir);
|
|
299
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
300
|
+
}
|
|
301
|
+
static async readFile(filePath) {
|
|
302
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
303
|
+
}
|
|
304
|
+
static async updateFileWithMarkers(filePath, content, startMarker, endMarker) {
|
|
305
|
+
let existingContent = '';
|
|
306
|
+
// The managed block is composed with '\n', so splicing it into a CRLF file
|
|
307
|
+
// would leave mixed endings behind. bash reports a stray '\r' in .bashrc as
|
|
308
|
+
// "$'\r': command not found", so settle the whole file on the convention it
|
|
309
|
+
// already used. A file that does not exist yet stays LF.
|
|
310
|
+
let originalContent;
|
|
311
|
+
if (await this.fileExists(filePath)) {
|
|
312
|
+
existingContent = await this.readFile(filePath);
|
|
313
|
+
originalContent = existingContent;
|
|
314
|
+
const startIndex = findMarkerIndex(existingContent, startMarker);
|
|
315
|
+
const endIndex = startIndex !== -1
|
|
316
|
+
? findMarkerIndex(existingContent, endMarker, startIndex + startMarker.length)
|
|
317
|
+
: findMarkerIndex(existingContent, endMarker);
|
|
318
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
319
|
+
if (endIndex < startIndex) {
|
|
320
|
+
throw new Error(`Invalid marker state in ${filePath}. End marker appears before start marker.`);
|
|
321
|
+
}
|
|
322
|
+
const before = existingContent.substring(0, startIndex);
|
|
323
|
+
const after = existingContent.substring(endIndex + endMarker.length);
|
|
324
|
+
existingContent = before + startMarker + '\n' + content + '\n' + endMarker + after;
|
|
325
|
+
}
|
|
326
|
+
else if (startIndex === -1 && endIndex === -1) {
|
|
327
|
+
existingContent = startMarker + '\n' + content + '\n' + endMarker + '\n\n' + existingContent;
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
throw new Error(`Invalid marker state in ${filePath}. Found start: ${startIndex !== -1}, Found end: ${endIndex !== -1}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
existingContent = startMarker + '\n' + content + '\n' + endMarker;
|
|
335
|
+
}
|
|
336
|
+
await this.writeFile(filePath, originalContent === undefined
|
|
337
|
+
? existingContent
|
|
338
|
+
: matchLineEnding(existingContent, originalContent));
|
|
339
|
+
}
|
|
340
|
+
static async ensureWritePermissions(dirPath) {
|
|
341
|
+
try {
|
|
342
|
+
// If directory doesn't exist, check parent directory permissions
|
|
343
|
+
if (!await this.directoryExists(dirPath)) {
|
|
344
|
+
const parentDir = path.dirname(dirPath);
|
|
345
|
+
if (!await this.directoryExists(parentDir)) {
|
|
346
|
+
await this.createDirectory(parentDir);
|
|
347
|
+
}
|
|
348
|
+
return await this.ensureWritePermissions(parentDir);
|
|
349
|
+
}
|
|
350
|
+
const testFile = path.join(dirPath, '.freespec-test-' + Date.now() + '-' + Math.random().toString(36).slice(2));
|
|
351
|
+
await fs.writeFile(testFile, '');
|
|
352
|
+
// On Windows, file may be temporarily locked by antivirus or indexing services.
|
|
353
|
+
// Retry unlink with a small delay if it fails.
|
|
354
|
+
const maxRetries = 3;
|
|
355
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
356
|
+
try {
|
|
357
|
+
await fs.unlink(testFile);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
catch (unlinkError) {
|
|
361
|
+
if (attempt === maxRetries - 1) {
|
|
362
|
+
// Last attempt failed, but we successfully wrote the file, so permissions are OK
|
|
363
|
+
// Just log and continue - the temp file will be cleaned up eventually
|
|
364
|
+
console.debug(`Could not clean up test file ${testFile}: ${unlinkError.message}`);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
// Wait briefly before retrying (Windows file lock release)
|
|
368
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.debug(`Insufficient permissions to write to ${dirPath}: ${error.message}`);
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Removes a marker block from file content.
|
|
382
|
+
* Only removes markers that are on their own lines (ignores inline mentions).
|
|
383
|
+
* Cleans up double blank lines that may result from removal.
|
|
384
|
+
*
|
|
385
|
+
* @param content - File content with markers
|
|
386
|
+
* @param startMarker - The start marker string
|
|
387
|
+
* @param endMarker - The end marker string
|
|
388
|
+
* @returns Content with marker block removed, or original content if markers not found/invalid
|
|
389
|
+
*/
|
|
390
|
+
export function removeMarkerBlock(content, startMarker, endMarker) {
|
|
391
|
+
const startIndex = findMarkerIndex(content, startMarker);
|
|
392
|
+
const endIndex = startIndex !== -1
|
|
393
|
+
? findMarkerIndex(content, endMarker, startIndex + startMarker.length)
|
|
394
|
+
: findMarkerIndex(content, endMarker);
|
|
395
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
396
|
+
return content;
|
|
397
|
+
}
|
|
398
|
+
// Find the start of the line containing the start marker
|
|
399
|
+
let lineStart = startIndex;
|
|
400
|
+
while (lineStart > 0 && content[lineStart - 1] !== '\n') {
|
|
401
|
+
lineStart--;
|
|
402
|
+
}
|
|
403
|
+
// Find the end of the line containing the end marker
|
|
404
|
+
let lineEnd = endIndex + endMarker.length;
|
|
405
|
+
while (lineEnd < content.length && content[lineEnd] !== '\n') {
|
|
406
|
+
lineEnd++;
|
|
407
|
+
}
|
|
408
|
+
// Include the trailing newline if present
|
|
409
|
+
if (lineEnd < content.length && content[lineEnd] === '\n') {
|
|
410
|
+
lineEnd++;
|
|
411
|
+
}
|
|
412
|
+
const before = content.substring(0, lineStart);
|
|
413
|
+
const after = content.substring(lineEnd);
|
|
414
|
+
// The file's own newline, used for every ending this function writes. The
|
|
415
|
+
// blank-line collapse below rebuilds the separator it matched, so spelling it
|
|
416
|
+
// '\n' would leave a CRLF file with a mixed pair wherever a run was collapsed
|
|
417
|
+
// - the stray '\r' that bash reports as "$'\r': command not found".
|
|
418
|
+
//
|
|
419
|
+
// Dominant rather than "contains a CRLF anywhere", so that one stray CRLF in
|
|
420
|
+
// an otherwise-LF file does not pull the whole rewrite to CRLF. This is the
|
|
421
|
+
// same reading matchLineEnding uses, so both write paths agree.
|
|
422
|
+
const newline = detectLineEnding(content) ?? '\n';
|
|
423
|
+
// Clean up double blank lines (handle both Unix \n and Windows \r\n)
|
|
424
|
+
let result = before + after;
|
|
425
|
+
result = result.replace(/(\r?\n){3,}/g, newline + newline);
|
|
426
|
+
// Trim trailing whitespace but preserve leading whitespace and original newline style
|
|
427
|
+
if (result.trimEnd() === '') {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
return result.trimEnd() + newline;
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=file-system.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { validateChangeName, createChange } from './change-utils.js';
|
|
2
|
+
export type { ValidationResult, CreateChangeOptions } from './change-utils.js';
|
|
3
|
+
export { readChangeMetadata, writeChangeMetadata, resolveSchemaForChange, validateSchemaName, ChangeMetadataError, listUnknownChangeMetadataKeys, readUnknownChangeMetadataKeys, formatUnknownChangeMetadataKeysMessage, CHANGE_METADATA_KNOWN_KEYS, } from './change-metadata.js';
|
|
4
|
+
export { FileSystemUtils, removeMarkerBlock } from './file-system.js';
|
|
5
|
+
export { transformCommandInvocations, transformToSkillReferences, getSkillReferenceTransformer, getTransformerForTool, } from './command-references.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Shared utilities
|
|
2
|
+
export { validateChangeName, createChange } from './change-utils.js';
|
|
3
|
+
// Change metadata utilities
|
|
4
|
+
export { readChangeMetadata, writeChangeMetadata, resolveSchemaForChange, validateSchemaName, ChangeMetadataError, listUnknownChangeMetadataKeys, readUnknownChangeMetadataKeys, formatUnknownChangeMetadataKeysMessage, CHANGE_METADATA_KNOWN_KEYS, } from './change-metadata.js';
|
|
5
|
+
// File system utilities
|
|
6
|
+
export { FileSystemUtils, removeMarkerBlock } from './file-system.js';
|
|
7
|
+
// Command reference utilities
|
|
8
|
+
export { transformCommandInvocations, transformToSkillReferences, getSkillReferenceTransformer, getTransformerForTool, } from './command-references.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Readable, Writable } from 'node:stream';
|
|
2
|
+
export type InteractiveOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Explicit "disable prompts" flag passed by internal callers.
|
|
5
|
+
*/
|
|
6
|
+
noInteractive?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Commander-style negated option: `--no-interactive` sets this to false.
|
|
9
|
+
*/
|
|
10
|
+
interactive?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolves whether non-interactive mode is requested.
|
|
14
|
+
* Handles both explicit `noInteractive: true` and Commander.js style `interactive: false`.
|
|
15
|
+
* Use this helper instead of manually checking options.noInteractive to avoid bugs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveNoInteractive(value?: boolean | InteractiveOptions): boolean;
|
|
18
|
+
export declare function isInteractive(value?: boolean | InteractiveOptions): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* True when a prompt failed because no answer could be read — an agent or a
|
|
21
|
+
* script that ran the command with stdin closed, a CI job, or a shell whose
|
|
22
|
+
* stdin is not a terminal. @inquirer rejects those with `User force closed
|
|
23
|
+
* the prompt with 0 null`, which is accurate and useless: it names no flag
|
|
24
|
+
* and no next step (#1479).
|
|
25
|
+
*
|
|
26
|
+
* Two things it deliberately is not:
|
|
27
|
+
*
|
|
28
|
+
* - It is not a substitute for `isInteractive()`. This classifies a prompt
|
|
29
|
+
* that has *already failed*, so piped answers are unaffected: an answer
|
|
30
|
+
* that arrives resolves the prompt and never reaches this check. Refusing
|
|
31
|
+
* to prompt up front would break `printf 'y\n' | freespec archive ...`,
|
|
32
|
+
* which works today.
|
|
33
|
+
* - It is not a cancellation check. Ctrl-C raises the same error class, and
|
|
34
|
+
* it reaches a process whose stdin is a pipe just as easily as one at a
|
|
35
|
+
* terminal, so the SIGINT signal - not the terminal - is what proves
|
|
36
|
+
* somebody was there and chose to quit.
|
|
37
|
+
*
|
|
38
|
+
* Beyond that it defers to `isInteractive()`, so `CI`, `OPEN_SPEC_INTERACTIVE=0`
|
|
39
|
+
* and `--no-interactive` count even when a runner allocated a pty. It also
|
|
40
|
+
* counts a redirected stdout: `confirmPrompt` drops to the plain reader whenever
|
|
41
|
+
* *either* stream is not a TTY, so a stdin-TTY-but-stdout-redirected run
|
|
42
|
+
* (`freespec archive x > log.txt` from a terminal) that hits EOF must classify
|
|
43
|
+
* the same way the prompt was selected — otherwise it would leak the raw
|
|
44
|
+
* `ExitPromptError` instead of the `--yes` guidance.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isNonInteractivePromptError(error: unknown, value?: boolean | InteractiveOptions): boolean;
|
|
47
|
+
export type ConfirmPrompt = {
|
|
48
|
+
message: string;
|
|
49
|
+
default: boolean;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Ask a yes/no question. A real terminal gets @inquirer's rich prompt;
|
|
53
|
+
* everything else — a pipe, a file redirect, an agent that captures stdout —
|
|
54
|
+
* reads one plain line instead.
|
|
55
|
+
*
|
|
56
|
+
* @inquirer renders `confirm` by writing ANSI cursor-movement escape sequences,
|
|
57
|
+
* and it emits them even when stdout is not a TTY. Redirected to a file those
|
|
58
|
+
* sequences are noise, and in some non-TTY hosts the render loop never settles
|
|
59
|
+
* and repeats `ESC[NNG` cursor moves until the disk fills (#1526). Reading
|
|
60
|
+
* the answer ourselves keeps the single piped answer @inquirer ever supported
|
|
61
|
+
* working (`printf 'y\n' | freespec archive ...`) without emitting any escapes.
|
|
62
|
+
*
|
|
63
|
+
* `io` overrides the streams; it exists for tests and mirrors @inquirer's own
|
|
64
|
+
* `{ input, output }` context. Production callers pass only the prompt.
|
|
65
|
+
*/
|
|
66
|
+
export declare function confirmPrompt(prompt: ConfirmPrompt, io?: {
|
|
67
|
+
input?: Readable;
|
|
68
|
+
output?: Writable;
|
|
69
|
+
}): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a spinner can safely render to `stream`.
|
|
72
|
+
*
|
|
73
|
+
* ora decides how many lines to erase with `ceil(width / stream.columns)`, so a
|
|
74
|
+
* TTY that reports zero columns makes that count `Infinity` and `clear()` never
|
|
75
|
+
* ends: the spinner emits cursor-up/clear-line escapes as fast as it can and the
|
|
76
|
+
* command never finishes (#2106). Zero-width TTYs are real — a pty inherited
|
|
77
|
+
* from a pre-commit runner such as lefthook, or `script -qec` — and nothing can
|
|
78
|
+
* be drawn in zero columns anyway, so refuse the spinner and let the caller fall
|
|
79
|
+
* back to its plain output.
|
|
80
|
+
*/
|
|
81
|
+
export declare function canRenderSpinner(stream?: {
|
|
82
|
+
isTTY?: boolean;
|
|
83
|
+
columns?: number;
|
|
84
|
+
}): boolean;
|
|
85
|
+
//# sourceMappingURL=interactive.d.ts.map
|