@geoqiao/pi-ask 1.1.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/CHANGELOG.md +194 -0
- package/LICENSE +22 -0
- package/README.md +282 -0
- package/docs/README.md +33 -0
- package/docs/configuration.md +406 -0
- package/docs/contract.md +309 -0
- package/docs/remote-events.md +187 -0
- package/package.json +130 -0
- package/skills/ask-user/SKILL.md +110 -0
- package/src/answer-commands.ts +361 -0
- package/src/answer-extraction.ts +354 -0
- package/src/ask-payload-store.ts +86 -0
- package/src/ask-settings-command.ts +14 -0
- package/src/ask-tool-helpers.ts +172 -0
- package/src/ask-tool.ts +84 -0
- package/src/config/defaults.ts +216 -0
- package/src/config/migrate.ts +70 -0
- package/src/config/migrations/index.ts +139 -0
- package/src/config/migrations/types.ts +10 -0
- package/src/config/schema.ts +287 -0
- package/src/config/store.ts +227 -0
- package/src/constants/keymaps.ts +721 -0
- package/src/constants/text.ts +12 -0
- package/src/constants/ui.ts +22 -0
- package/src/index.ts +30 -0
- package/src/math.ts +3 -0
- package/src/notifications.ts +119 -0
- package/src/remote-ask.ts +563 -0
- package/src/result-format.ts +157 -0
- package/src/result.ts +23 -0
- package/src/schema.ts +74 -0
- package/src/state/answers.ts +251 -0
- package/src/state/create.ts +18 -0
- package/src/state/editor.ts +70 -0
- package/src/state/navigation.ts +86 -0
- package/src/state/normalize.ts +326 -0
- package/src/state/question-type.ts +128 -0
- package/src/state/result.ts +263 -0
- package/src/state/selectors.ts +135 -0
- package/src/state/transitions.ts +330 -0
- package/src/state/view.ts +28 -0
- package/src/text.ts +98 -0
- package/src/types.ts +169 -0
- package/src/ui/auto-submit.ts +36 -0
- package/src/ui/autocomplete.ts +52 -0
- package/src/ui/controller.ts +645 -0
- package/src/ui/dismiss-guard.ts +26 -0
- package/src/ui/input.ts +160 -0
- package/src/ui/render-frame.ts +235 -0
- package/src/ui/render-helpers.ts +385 -0
- package/src/ui/render-question.ts +288 -0
- package/src/ui/render-submit.ts +168 -0
- package/src/ui/render-types.ts +33 -0
- package/src/ui/render.ts +53 -0
- package/src/ui/review-shortcuts.ts +43 -0
- package/src/ui/settings-list.ts +461 -0
- package/src/ui/show-settings.ts +37 -0
- package/src/ui/view-models/question.ts +203 -0
- package/src/ui/view-models/review.ts +100 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ASK_KEYMAPS,
|
|
3
|
+
normalizeConfiguredKeymaps,
|
|
4
|
+
} from "../constants/keymaps.ts";
|
|
5
|
+
import type {
|
|
6
|
+
AskConfig,
|
|
7
|
+
AskConfigFileV5,
|
|
8
|
+
AskConfigKeymaps,
|
|
9
|
+
AskNotificationChannel,
|
|
10
|
+
} from "./schema.ts";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_EXTRACTION_RETRIES = 1;
|
|
13
|
+
const MAX_EXTRACTION_RETRIES = 3;
|
|
14
|
+
const DEFAULT_EXTRACTION_TIMEOUT_MS = 30_000;
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_ASK_CONFIG: AskConfig = {
|
|
17
|
+
answer: {
|
|
18
|
+
extractionModels: [
|
|
19
|
+
{ provider: "openai-codex", id: "gpt-5.4-mini" },
|
|
20
|
+
{ provider: "github-copilot", id: "gpt-5.4-mini" },
|
|
21
|
+
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
22
|
+
],
|
|
23
|
+
extractionRetries: DEFAULT_EXTRACTION_RETRIES,
|
|
24
|
+
extractionTimeoutMs: DEFAULT_EXTRACTION_TIMEOUT_MS,
|
|
25
|
+
},
|
|
26
|
+
behaviour: {
|
|
27
|
+
autoSubmitWhenAnsweredWithoutNotes: false,
|
|
28
|
+
confirmDismissWhenDirty: true,
|
|
29
|
+
doublePressReviewShortcuts: true,
|
|
30
|
+
presentSingleAsMulti: false,
|
|
31
|
+
showFooterHints: true,
|
|
32
|
+
},
|
|
33
|
+
keymaps: cloneKeymaps(DEFAULT_ASK_KEYMAPS),
|
|
34
|
+
notifications: {
|
|
35
|
+
channels: ["bell"],
|
|
36
|
+
enabled: true,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function normalizeAskConfig(
|
|
41
|
+
config?: Partial<AskConfigFileV5> | AskConfig
|
|
42
|
+
): AskConfig {
|
|
43
|
+
return {
|
|
44
|
+
answer: {
|
|
45
|
+
extractionModels:
|
|
46
|
+
config?.answer?.extractionModels?.filter(isValidModelPreference) ??
|
|
47
|
+
DEFAULT_ASK_CONFIG.answer.extractionModels,
|
|
48
|
+
extractionRetries: clampInteger(
|
|
49
|
+
config?.answer?.extractionRetries,
|
|
50
|
+
0,
|
|
51
|
+
MAX_EXTRACTION_RETRIES,
|
|
52
|
+
DEFAULT_EXTRACTION_RETRIES
|
|
53
|
+
),
|
|
54
|
+
extractionTimeoutMs: positiveNumberOrDefault(
|
|
55
|
+
config?.answer?.extractionTimeoutMs,
|
|
56
|
+
DEFAULT_EXTRACTION_TIMEOUT_MS
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
behaviour: {
|
|
60
|
+
autoSubmitWhenAnsweredWithoutNotes:
|
|
61
|
+
config?.behaviour?.autoSubmitWhenAnsweredWithoutNotes ??
|
|
62
|
+
DEFAULT_ASK_CONFIG.behaviour.autoSubmitWhenAnsweredWithoutNotes,
|
|
63
|
+
confirmDismissWhenDirty:
|
|
64
|
+
config?.behaviour?.confirmDismissWhenDirty ??
|
|
65
|
+
DEFAULT_ASK_CONFIG.behaviour.confirmDismissWhenDirty,
|
|
66
|
+
doublePressReviewShortcuts:
|
|
67
|
+
config?.behaviour?.doublePressReviewShortcuts ??
|
|
68
|
+
DEFAULT_ASK_CONFIG.behaviour.doublePressReviewShortcuts,
|
|
69
|
+
showFooterHints:
|
|
70
|
+
config?.behaviour?.showFooterHints ??
|
|
71
|
+
DEFAULT_ASK_CONFIG.behaviour.showFooterHints,
|
|
72
|
+
presentSingleAsMulti:
|
|
73
|
+
config?.behaviour?.presentSingleAsMulti ??
|
|
74
|
+
DEFAULT_ASK_CONFIG.behaviour.presentSingleAsMulti,
|
|
75
|
+
},
|
|
76
|
+
keymaps: mergeKeymaps(config?.keymaps),
|
|
77
|
+
notifications: {
|
|
78
|
+
channels: normalizeNotificationChannels(config?.notifications?.channels),
|
|
79
|
+
enabled:
|
|
80
|
+
config?.notifications?.enabled ??
|
|
81
|
+
DEFAULT_ASK_CONFIG.notifications.enabled,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function toAskConfigFileV5(config: AskConfig): AskConfigFileV5 {
|
|
87
|
+
const normalized = normalizeAskConfig(config);
|
|
88
|
+
return {
|
|
89
|
+
schemaVersion: 5,
|
|
90
|
+
answer: {
|
|
91
|
+
extractionModels: normalized.answer.extractionModels,
|
|
92
|
+
extractionRetries: normalized.answer.extractionRetries,
|
|
93
|
+
extractionTimeoutMs: normalized.answer.extractionTimeoutMs,
|
|
94
|
+
},
|
|
95
|
+
behaviour: {
|
|
96
|
+
autoSubmitWhenAnsweredWithoutNotes:
|
|
97
|
+
normalized.behaviour.autoSubmitWhenAnsweredWithoutNotes,
|
|
98
|
+
confirmDismissWhenDirty: normalized.behaviour.confirmDismissWhenDirty,
|
|
99
|
+
doublePressReviewShortcuts:
|
|
100
|
+
normalized.behaviour.doublePressReviewShortcuts,
|
|
101
|
+
presentSingleAsMulti: normalized.behaviour.presentSingleAsMulti,
|
|
102
|
+
showFooterHints: normalized.behaviour.showFooterHints,
|
|
103
|
+
},
|
|
104
|
+
keymaps: cloneKeymaps(normalized.keymaps),
|
|
105
|
+
notifications: {
|
|
106
|
+
channels: normalized.notifications.channels,
|
|
107
|
+
enabled: normalized.notifications.enabled,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function mergeKeymaps(keymaps: unknown): AskConfigKeymaps {
|
|
113
|
+
const normalized = normalizeConfiguredKeymaps(keymaps);
|
|
114
|
+
return normalized.ok ? normalized.keymaps : cloneKeymaps(DEFAULT_ASK_KEYMAPS);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function cloneKeymaps(keymaps: AskConfigKeymaps): AskConfigKeymaps {
|
|
118
|
+
return {
|
|
119
|
+
global: {
|
|
120
|
+
dismiss: [...keymaps.global.dismiss],
|
|
121
|
+
settings: [...keymaps.global.settings],
|
|
122
|
+
},
|
|
123
|
+
main: {
|
|
124
|
+
cancel: [...keymaps.main.cancel],
|
|
125
|
+
changeQuestionType: [...keymaps.main.changeQuestionType],
|
|
126
|
+
confirm: [...keymaps.main.confirm],
|
|
127
|
+
nextOption: [...keymaps.main.nextOption],
|
|
128
|
+
nextTab: [...keymaps.main.nextTab],
|
|
129
|
+
optionNote: [...keymaps.main.optionNote],
|
|
130
|
+
previousOption: [...keymaps.main.previousOption],
|
|
131
|
+
previousTab: [...keymaps.main.previousTab],
|
|
132
|
+
questionNote: [...keymaps.main.questionNote],
|
|
133
|
+
toggle: [...keymaps.main.toggle],
|
|
134
|
+
},
|
|
135
|
+
editor: {
|
|
136
|
+
close: [...keymaps.editor.close],
|
|
137
|
+
nextOptionWhenEmpty: [...keymaps.editor.nextOptionWhenEmpty],
|
|
138
|
+
nextTabWhenEmpty: [...keymaps.editor.nextTabWhenEmpty],
|
|
139
|
+
previousOptionWhenEmpty: [...keymaps.editor.previousOptionWhenEmpty],
|
|
140
|
+
previousTabWhenEmpty: [...keymaps.editor.previousTabWhenEmpty],
|
|
141
|
+
submit: [...keymaps.editor.submit],
|
|
142
|
+
},
|
|
143
|
+
noteEditor: {
|
|
144
|
+
close: [...keymaps.noteEditor.close],
|
|
145
|
+
nextOptionWhenEmpty: [...keymaps.noteEditor.nextOptionWhenEmpty],
|
|
146
|
+
nextTabWhenEmpty: [...keymaps.noteEditor.nextTabWhenEmpty],
|
|
147
|
+
previousOptionWhenEmpty: [...keymaps.noteEditor.previousOptionWhenEmpty],
|
|
148
|
+
previousTabWhenEmpty: [...keymaps.noteEditor.previousTabWhenEmpty],
|
|
149
|
+
save: [...keymaps.noteEditor.save],
|
|
150
|
+
},
|
|
151
|
+
settingsModal: {
|
|
152
|
+
close: [...keymaps.settingsModal.close],
|
|
153
|
+
nextOption: [...keymaps.settingsModal.nextOption],
|
|
154
|
+
previousOption: [...keymaps.settingsModal.previousOption],
|
|
155
|
+
toggle: [...keymaps.settingsModal.toggle],
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeNotificationChannels(
|
|
161
|
+
channels: unknown
|
|
162
|
+
): AskNotificationChannel[] {
|
|
163
|
+
if (!Array.isArray(channels)) {
|
|
164
|
+
return DEFAULT_ASK_CONFIG.notifications.channels;
|
|
165
|
+
}
|
|
166
|
+
const normalized = channels.filter(isValidNotificationChannel);
|
|
167
|
+
return normalized.length > 0
|
|
168
|
+
? normalized
|
|
169
|
+
: DEFAULT_ASK_CONFIG.notifications.channels;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isValidNotificationChannel(
|
|
173
|
+
value: unknown
|
|
174
|
+
): value is AskNotificationChannel {
|
|
175
|
+
if (value === "bell" || value === "osc9" || value === "osc777") {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return (
|
|
179
|
+
!!value &&
|
|
180
|
+
typeof value === "object" &&
|
|
181
|
+
(value as { type?: unknown }).type === "command" &&
|
|
182
|
+
typeof (value as { command?: unknown }).command === "string" &&
|
|
183
|
+
(value as { command: string }).command.trim().length > 0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isValidModelPreference(
|
|
188
|
+
value: unknown
|
|
189
|
+
): value is { id: string; provider: string } {
|
|
190
|
+
return (
|
|
191
|
+
!!value &&
|
|
192
|
+
typeof value === "object" &&
|
|
193
|
+
typeof (value as { id?: unknown }).id === "string" &&
|
|
194
|
+
(value as { id: string }).id.trim().length > 0 &&
|
|
195
|
+
typeof (value as { provider?: unknown }).provider === "string" &&
|
|
196
|
+
(value as { provider: string }).provider.trim().length > 0
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function clampInteger(
|
|
201
|
+
value: unknown,
|
|
202
|
+
min: number,
|
|
203
|
+
max: number,
|
|
204
|
+
fallback: number
|
|
205
|
+
): number {
|
|
206
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
207
|
+
return fallback;
|
|
208
|
+
}
|
|
209
|
+
return Math.max(min, Math.min(max, Math.trunc(value)));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function positiveNumberOrDefault(value: unknown, fallback: number): number {
|
|
213
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
214
|
+
? value
|
|
215
|
+
: fallback;
|
|
216
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { normalizeConfiguredKeymaps } from "../constants/keymaps.ts";
|
|
2
|
+
import { normalizeAskConfig } from "./defaults.ts";
|
|
3
|
+
import {
|
|
4
|
+
AskConfigVersionMigrationError,
|
|
5
|
+
type AskConfigVersionMigrationResult,
|
|
6
|
+
migrateAskConfigFileToCurrent,
|
|
7
|
+
} from "./migrations/index.ts";
|
|
8
|
+
import type { AskConfig, AskConfigFileV5 } from "./schema.ts";
|
|
9
|
+
import { validateAskConfigFileV5 } from "./schema.ts";
|
|
10
|
+
|
|
11
|
+
export class AskConfigMigrationError extends Error {
|
|
12
|
+
readonly reason: "invalid_or_unsupported" | "migration_failed";
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
message: string,
|
|
16
|
+
reason: "invalid_or_unsupported" | "migration_failed"
|
|
17
|
+
) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.reason = reason;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AskConfigMigrationResult {
|
|
24
|
+
config: AskConfig;
|
|
25
|
+
migrated: boolean;
|
|
26
|
+
notice?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function migrateAskConfig(raw: unknown): AskConfigMigrationResult {
|
|
30
|
+
let migratedFile: AskConfigVersionMigrationResult;
|
|
31
|
+
try {
|
|
32
|
+
migratedFile = migrateAskConfigFileToCurrent(raw);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new AskConfigMigrationError(
|
|
35
|
+
"Config was invalid or unsupported.",
|
|
36
|
+
error instanceof AskConfigVersionMigrationError
|
|
37
|
+
? error.reason
|
|
38
|
+
: "migration_failed"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!validateAskConfigFileV5.Check(migratedFile.config)) {
|
|
43
|
+
throw new AskConfigMigrationError(
|
|
44
|
+
"Config was invalid or unsupported.",
|
|
45
|
+
"invalid_or_unsupported"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const currentFile = migratedFile.config as AskConfigFileV5;
|
|
50
|
+
const config = normalizeAskConfig(currentFile);
|
|
51
|
+
const keymapsResult = normalizeConfiguredKeymaps(currentFile.keymaps);
|
|
52
|
+
if (!keymapsResult.ok) {
|
|
53
|
+
return {
|
|
54
|
+
config: {
|
|
55
|
+
...config,
|
|
56
|
+
keymaps: normalizeAskConfig().keymaps,
|
|
57
|
+
},
|
|
58
|
+
migrated: migratedFile.migrated,
|
|
59
|
+
notice: `${keymapsResult.error} Using default ask keymaps for this session. Edit the config and restart pi or run /reload.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
config: {
|
|
65
|
+
...config,
|
|
66
|
+
keymaps: keymapsResult.keymaps,
|
|
67
|
+
},
|
|
68
|
+
migrated: migratedFile.migrated,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { normalizeLegacyFlatKeymaps } from "../../constants/keymaps.ts";
|
|
2
|
+
import type { AskConfigMigration, VersionedAskConfigFile } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export const CURRENT_ASK_CONFIG_SCHEMA_VERSION = 5;
|
|
5
|
+
|
|
6
|
+
const ASK_CONFIG_MIGRATIONS: AskConfigMigration[] = [
|
|
7
|
+
{
|
|
8
|
+
from: 1,
|
|
9
|
+
to: 2,
|
|
10
|
+
migrate: (config) => ({
|
|
11
|
+
...config,
|
|
12
|
+
schemaVersion: 2,
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
from: 2,
|
|
17
|
+
to: 3,
|
|
18
|
+
migrate: (config) => ({
|
|
19
|
+
...config,
|
|
20
|
+
notifications: {
|
|
21
|
+
channels: ["bell"],
|
|
22
|
+
enabled: true,
|
|
23
|
+
},
|
|
24
|
+
schemaVersion: 3,
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
from: 3,
|
|
29
|
+
to: 4,
|
|
30
|
+
migrate: (config) => ({
|
|
31
|
+
...config,
|
|
32
|
+
keymaps: normalizeLegacyFlatKeymaps(config.keymaps) ?? config.keymaps,
|
|
33
|
+
schemaVersion: 4,
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
from: 4,
|
|
38
|
+
to: 5,
|
|
39
|
+
migrate: (config) => ({
|
|
40
|
+
...config,
|
|
41
|
+
behaviour: {
|
|
42
|
+
...((config.behaviour as Record<string, unknown> | undefined) ?? {}),
|
|
43
|
+
presentSingleAsMulti: false,
|
|
44
|
+
},
|
|
45
|
+
keymaps: addV5Keymaps(config.keymaps),
|
|
46
|
+
schemaVersion: 5,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function addV5Keymaps(keymaps: unknown): unknown {
|
|
52
|
+
if (!(keymaps && typeof keymaps === "object" && "main" in keymaps)) {
|
|
53
|
+
return keymaps;
|
|
54
|
+
}
|
|
55
|
+
const current = keymaps as Record<string, unknown>;
|
|
56
|
+
const main = current.main;
|
|
57
|
+
if (!(main && typeof main === "object")) {
|
|
58
|
+
return keymaps;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...current,
|
|
62
|
+
main: {
|
|
63
|
+
...(main as Record<string, unknown>),
|
|
64
|
+
changeQuestionType: (main as Record<string, unknown>)
|
|
65
|
+
.changeQuestionType ?? ["t"],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class AskConfigVersionMigrationError extends Error {
|
|
71
|
+
readonly reason: "invalid_or_unsupported" | "migration_failed";
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
message: string,
|
|
75
|
+
reason: "invalid_or_unsupported" | "migration_failed"
|
|
76
|
+
) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.reason = reason;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface AskConfigVersionMigrationResult {
|
|
83
|
+
config: VersionedAskConfigFile;
|
|
84
|
+
migrated: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function migrateAskConfigFileToCurrent(
|
|
88
|
+
raw: unknown
|
|
89
|
+
): AskConfigVersionMigrationResult {
|
|
90
|
+
const start = getVersionedConfigFile(raw);
|
|
91
|
+
let config = start;
|
|
92
|
+
let migrated = false;
|
|
93
|
+
|
|
94
|
+
while (config.schemaVersion < CURRENT_ASK_CONFIG_SCHEMA_VERSION) {
|
|
95
|
+
const migration = ASK_CONFIG_MIGRATIONS.find(
|
|
96
|
+
(candidate) => candidate.from === config.schemaVersion
|
|
97
|
+
);
|
|
98
|
+
if (!migration) {
|
|
99
|
+
throw new AskConfigVersionMigrationError(
|
|
100
|
+
`No ask config migration from schemaVersion ${config.schemaVersion}`,
|
|
101
|
+
"migration_failed"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
config = migration.migrate(config);
|
|
105
|
+
if (config.schemaVersion !== migration.to) {
|
|
106
|
+
throw new AskConfigVersionMigrationError(
|
|
107
|
+
`Ask config migration from schemaVersion ${migration.from} did not produce schemaVersion ${migration.to}`,
|
|
108
|
+
"migration_failed"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
migrated = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (config.schemaVersion !== CURRENT_ASK_CONFIG_SCHEMA_VERSION) {
|
|
115
|
+
throw new AskConfigVersionMigrationError(
|
|
116
|
+
`Unsupported ask config schemaVersion ${config.schemaVersion}`,
|
|
117
|
+
"invalid_or_unsupported"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { config, migrated };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getVersionedConfigFile(raw: unknown): VersionedAskConfigFile {
|
|
125
|
+
if (!(raw && typeof raw === "object")) {
|
|
126
|
+
throw new AskConfigVersionMigrationError(
|
|
127
|
+
"Ask config must be an object",
|
|
128
|
+
"invalid_or_unsupported"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const schemaVersion = (raw as { schemaVersion?: unknown }).schemaVersion;
|
|
132
|
+
if (!Number.isInteger(schemaVersion) || typeof schemaVersion !== "number") {
|
|
133
|
+
throw new AskConfigVersionMigrationError(
|
|
134
|
+
"Ask config must include an integer schemaVersion",
|
|
135
|
+
"invalid_or_unsupported"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return raw as VersionedAskConfigFile;
|
|
139
|
+
}
|