norn-cli 2.2.2 → 2.4.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/settings.local.json +18 -0
- package/.claude/skills/norn-social-campaign/SKILL.md +70 -0
- package/CHANGELOG.md +22 -1
- package/LICENSE +20 -29
- package/README.md +32 -1
- package/demos/nornenv-region-refactor/README.md +64 -0
- package/demos/nornenv-showcase/README.md +62 -0
- package/demos/nornenv-showcase/norn.config.json +16 -0
- package/demos/nornenv-showcase/showcase.norn +70 -0
- package/demos/nornenv-showcase/showcase.nornapi +26 -0
- package/demos/nornenv-showcase/showcase.nornsql +20 -0
- package/dist/cli.js +564 -54
- package/out/apiResponseIntellisenseCache.js +394 -0
- package/out/assertionRunner.js +567 -0
- package/out/cacheDir.js +136 -0
- package/out/chatParticipant.js +763 -0
- package/out/cli/colors.js +127 -0
- package/out/cli/formatters/assertion.js +102 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +246 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +689 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +226 -0
- package/out/codeLensProvider.js +351 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +3739 -0
- package/out/contractAssertionSummary.js +225 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +879 -0
- package/out/coveragePanel.js +597 -0
- package/out/debug/breakpointResolver.js +84 -0
- package/out/debug/breakpoints.js +52 -0
- package/out/debug/nornDebugAdapter.js +166 -0
- package/out/debug/nornDebugSession.js +613 -0
- package/out/debug/sequenceLocationIndex.js +77 -0
- package/out/debug/types.js +3 -0
- package/out/deepClone.js +21 -0
- package/out/diagnosticProvider.js +2554 -0
- package/out/environmentParser.js +736 -0
- package/out/environmentProvider.js +544 -0
- package/out/environmentTemplates.js +146 -0
- package/out/errors/formatError.js +113 -0
- package/out/errors/nornError.js +29 -0
- package/out/formUrlEncoded.js +89 -0
- package/out/httpClient.js +348 -0
- package/out/httpRuntimeOptions.js +16 -0
- package/out/importErrors.js +31 -0
- package/out/inlayHintResolver.js +70 -0
- package/out/jsonFileReader.js +323 -0
- package/out/mcpClient.js +193 -0
- package/out/mcpConfig.js +184 -0
- package/out/mcpToolIntellisenseCache.js +96 -0
- package/out/mcpToolSchema.js +50 -0
- package/out/nornConfig.js +132 -0
- package/out/nornHoverProvider.js +124 -0
- package/out/nornInlayHintsProvider.js +191 -0
- package/out/nornPrompt.js +755 -0
- package/out/nornSqlParser.js +286 -0
- package/out/nornapiHoverProvider.js +135 -0
- package/out/nornapiInlayHintsProvider.js +94 -0
- package/out/nornapiParser.js +324 -0
- package/out/nornenvCodeActionProvider.js +101 -0
- package/out/nornenvDecorationProvider.js +239 -0
- package/out/nornenvFoldingProvider.js +63 -0
- package/out/nornenvHoverProvider.js +114 -0
- package/out/nornenvInlayHintsProvider.js +99 -0
- package/out/nornenvLanguageModel.js +187 -0
- package/out/nornenvRegionRefactor.js +267 -0
- package/out/nornsqlHoverProvider.js +95 -0
- package/out/nornsqlInlayHintsProvider.js +114 -0
- package/out/parser.js +839 -0
- package/out/pathAccess.js +28 -0
- package/out/postmanImportPanel.js +732 -0
- package/out/postmanImportPlanner.js +1155 -0
- package/out/postmanImportSidebarView.js +532 -0
- package/out/quotedString.js +35 -0
- package/out/requestPreparation.js +179 -0
- package/out/requestValidation.js +146 -0
- package/out/responsePanel.js +7754 -0
- package/out/schemaGenerator.js +562 -0
- package/out/scriptRunner.js +419 -0
- package/out/secrets/cliSecrets.js +415 -0
- package/out/secrets/crypto.js +105 -0
- package/out/secrets/envFileSecrets.js +177 -0
- package/out/secrets/keyStore.js +259 -0
- package/out/sequenceDeclaration.js +15 -0
- package/out/sequenceRunner.js +3590 -0
- package/out/sqlAdapterRunner.js +122 -0
- package/out/sqlBuiltInAdapters.js +604 -0
- package/out/sqlConfig.js +184 -0
- package/out/starterCatalog.js +554 -0
- package/out/stringUtils.js +25 -0
- package/out/swaggerBodyIntellisenseCache.js +114 -0
- package/out/swaggerParser.js +464 -0
- package/out/testProvider.js +767 -0
- package/out/theoryCaseLoader.js +113 -0
- package/out/validationCache.js +211 -0
- package/package.json +38 -11
- package/.kanbn/index.md +0 -31
- package/.kanbn/tasks/book-first-mentor-session.md +0 -13
- package/.kanbn/tasks/decide-what-success-in-a-pilot-looks-like.md +0 -9
- package/.kanbn/tasks/do-5-customer-conversations.md +0 -9
- package/.kanbn/tasks/finalise-the-one-line-pitch.md +0 -11
- package/.kanbn/tasks/interview-script.md +0 -49
- package/.kanbn/tasks/make-a-list-of-10-people-to-speak-to.md +0 -11
- package/.kanbn/tasks/prepare-your-customer-interview-questions.md +0 -11
- package/.kanbn/tasks/recruit-2/342/200/2233-pilot-users.md +0 -9
- package/.kanbn/tasks/refine-your-pitch.md +0 -9
- package/.kanbn/tasks/use-the-shiplight-website-as-a-template-to-improve-norn-website.md +0 -9
- package/.kanbn/tasks/write-down-repeated-wording.md +0 -9
- package/.kanbn/tasks/write-the-one-pager.md +0 -27
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PostmanImportPanel = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const vscode = __importStar(require("vscode"));
|
|
39
|
+
const postmanImportPlanner_1 = require("./postmanImportPlanner");
|
|
40
|
+
const IMPORT_STEPS = [
|
|
41
|
+
{ id: 'select', label: 'Select' },
|
|
42
|
+
{ id: 'review', label: 'Review' },
|
|
43
|
+
{ id: 'result', label: 'Result' },
|
|
44
|
+
];
|
|
45
|
+
class PostmanImportPanel {
|
|
46
|
+
static currentPanel;
|
|
47
|
+
static show(extensionUri, mode) {
|
|
48
|
+
if (PostmanImportPanel.currentPanel) {
|
|
49
|
+
PostmanImportPanel.currentPanel._panel.reveal(vscode.ViewColumn.One);
|
|
50
|
+
PostmanImportPanel.currentPanel._setMode(mode);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const panel = vscode.window.createWebviewPanel('norn.postmanImport', PostmanImportPanel.getPanelTitle(mode), vscode.ViewColumn.One, {
|
|
54
|
+
enableScripts: true,
|
|
55
|
+
retainContextWhenHidden: true,
|
|
56
|
+
});
|
|
57
|
+
PostmanImportPanel.currentPanel = new PostmanImportPanel(panel, extensionUri, mode);
|
|
58
|
+
}
|
|
59
|
+
_panel;
|
|
60
|
+
_mode;
|
|
61
|
+
_stepIndex = 0;
|
|
62
|
+
_sourceFiles = [];
|
|
63
|
+
_destinationFolder;
|
|
64
|
+
_isDialogOpen = false;
|
|
65
|
+
_destinationSelectedByUser = false;
|
|
66
|
+
_plan;
|
|
67
|
+
_writeResult;
|
|
68
|
+
_lastErrorMessage;
|
|
69
|
+
_isBusy = false;
|
|
70
|
+
constructor(panel, _extensionUri, mode) {
|
|
71
|
+
this._panel = panel;
|
|
72
|
+
this._mode = mode;
|
|
73
|
+
this._panel.onDidDispose(() => {
|
|
74
|
+
if (PostmanImportPanel.currentPanel === this) {
|
|
75
|
+
PostmanImportPanel.currentPanel = undefined;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
this._panel.webview.onDidReceiveMessage(async (message) => {
|
|
79
|
+
switch (message.command) {
|
|
80
|
+
case 'pickSource':
|
|
81
|
+
await this._pickSourceFiles();
|
|
82
|
+
break;
|
|
83
|
+
case 'pickDestination':
|
|
84
|
+
await this._pickDestinationFolder();
|
|
85
|
+
break;
|
|
86
|
+
case 'executeImport':
|
|
87
|
+
await this._executeImport();
|
|
88
|
+
break;
|
|
89
|
+
case 'next':
|
|
90
|
+
if (this._stepIndex === IMPORT_STEPS.length - 1) {
|
|
91
|
+
this._panel.dispose();
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (this._stepIndex === 0 && this._sourceFiles.length === 0) {
|
|
95
|
+
void vscode.window.showWarningMessage('Select a Postman JSON file before continuing.');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (this._stepIndex === 0) {
|
|
99
|
+
await this._rebuildPlan();
|
|
100
|
+
}
|
|
101
|
+
if (this._stepIndex === 1 && !this._writeResult) {
|
|
102
|
+
await this._executeImport();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
this._stepIndex = Math.min(this._stepIndex + 1, IMPORT_STEPS.length - 1);
|
|
106
|
+
this._render();
|
|
107
|
+
break;
|
|
108
|
+
case 'back':
|
|
109
|
+
this._stepIndex = Math.max(this._stepIndex - 1, 0);
|
|
110
|
+
this._render();
|
|
111
|
+
break;
|
|
112
|
+
case 'reset':
|
|
113
|
+
this._stepIndex = 0;
|
|
114
|
+
this._render();
|
|
115
|
+
break;
|
|
116
|
+
case 'close':
|
|
117
|
+
this._panel.dispose();
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
this._render();
|
|
124
|
+
}
|
|
125
|
+
_setMode(mode) {
|
|
126
|
+
this._mode = mode;
|
|
127
|
+
this._stepIndex = 0;
|
|
128
|
+
this._sourceFiles = [];
|
|
129
|
+
this._destinationFolder = undefined;
|
|
130
|
+
this._destinationSelectedByUser = false;
|
|
131
|
+
this._plan = undefined;
|
|
132
|
+
this._writeResult = undefined;
|
|
133
|
+
this._lastErrorMessage = undefined;
|
|
134
|
+
this._render();
|
|
135
|
+
}
|
|
136
|
+
_render() {
|
|
137
|
+
this._panel.title = PostmanImportPanel.getPanelTitle(this._mode);
|
|
138
|
+
this._panel.webview.html = this._getHtml();
|
|
139
|
+
}
|
|
140
|
+
_getHtml() {
|
|
141
|
+
const modeTitle = this._mode === 'collection'
|
|
142
|
+
? 'Postman Collection JSON Import'
|
|
143
|
+
: 'Postman Environment JSON Import';
|
|
144
|
+
const modeSubtitle = this._mode === 'collection'
|
|
145
|
+
? 'Collection import will convert Postman requests/folders into Norn `.nornapi` + `.norn` output.'
|
|
146
|
+
: 'Environment import will convert Postman environment JSON files into a Norn `.nornenv` file.';
|
|
147
|
+
const currentStep = IMPORT_STEPS[this._stepIndex];
|
|
148
|
+
const stepListHtml = IMPORT_STEPS.map((step, index) => {
|
|
149
|
+
const state = index < this._stepIndex ? 'done' : index === this._stepIndex ? 'current' : 'todo';
|
|
150
|
+
return `<li class="step ${state}"><span class="step-index">${index + 1}</span><span>${step.label}</span></li>`;
|
|
151
|
+
}).join('');
|
|
152
|
+
const bodyHtml = this._getStepBody(currentStep);
|
|
153
|
+
const canGoBack = this._stepIndex > 0 && !this._isBusy;
|
|
154
|
+
const canGoNext = this._canGoNext();
|
|
155
|
+
const canImport = !this._isBusy && !this._writeResult && Boolean(this._plan && this._plan.blockers.length === 0);
|
|
156
|
+
const isResultStep = this._stepIndex === IMPORT_STEPS.length - 1;
|
|
157
|
+
const isReviewStep = this._stepIndex === 1;
|
|
158
|
+
const nextButtonLabel = isResultStep
|
|
159
|
+
? 'Finish'
|
|
160
|
+
: (isReviewStep && !this._writeResult ? 'Import' : 'Next');
|
|
161
|
+
return `<!DOCTYPE html>
|
|
162
|
+
<html lang="en">
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="UTF-8" />
|
|
165
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
166
|
+
<title>${modeTitle}</title>
|
|
167
|
+
<style>
|
|
168
|
+
:root {
|
|
169
|
+
color-scheme: light dark;
|
|
170
|
+
}
|
|
171
|
+
body {
|
|
172
|
+
font-family: var(--vscode-font-family);
|
|
173
|
+
font-size: var(--vscode-font-size);
|
|
174
|
+
color: var(--vscode-foreground);
|
|
175
|
+
background: var(--vscode-editor-background);
|
|
176
|
+
margin: 0;
|
|
177
|
+
padding: 0;
|
|
178
|
+
}
|
|
179
|
+
.wrap {
|
|
180
|
+
padding: 18px;
|
|
181
|
+
max-width: 980px;
|
|
182
|
+
margin: 0 auto;
|
|
183
|
+
}
|
|
184
|
+
h1 {
|
|
185
|
+
margin: 0 0 8px 0;
|
|
186
|
+
font-size: 18px;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
}
|
|
189
|
+
.subtitle {
|
|
190
|
+
margin: 0 0 16px 0;
|
|
191
|
+
color: var(--vscode-descriptionForeground);
|
|
192
|
+
line-height: 1.4;
|
|
193
|
+
}
|
|
194
|
+
.shell-badge {
|
|
195
|
+
display: inline-block;
|
|
196
|
+
margin-bottom: 12px;
|
|
197
|
+
padding: 3px 8px;
|
|
198
|
+
border-radius: 999px;
|
|
199
|
+
background: color-mix(in srgb, var(--vscode-button-background) 18%, transparent);
|
|
200
|
+
color: var(--vscode-foreground);
|
|
201
|
+
border: 1px solid var(--vscode-panel-border);
|
|
202
|
+
font-size: 11px;
|
|
203
|
+
letter-spacing: 0.04em;
|
|
204
|
+
text-transform: uppercase;
|
|
205
|
+
}
|
|
206
|
+
.grid {
|
|
207
|
+
display: grid;
|
|
208
|
+
grid-template-columns: 260px 1fr;
|
|
209
|
+
gap: 14px;
|
|
210
|
+
}
|
|
211
|
+
.card {
|
|
212
|
+
border: 1px solid var(--vscode-panel-border);
|
|
213
|
+
border-radius: 10px;
|
|
214
|
+
background: var(--vscode-sideBar-background);
|
|
215
|
+
padding: 14px;
|
|
216
|
+
}
|
|
217
|
+
.card h2 {
|
|
218
|
+
margin: 0 0 10px 0;
|
|
219
|
+
font-size: 13px;
|
|
220
|
+
text-transform: uppercase;
|
|
221
|
+
letter-spacing: 0.04em;
|
|
222
|
+
color: var(--vscode-descriptionForeground);
|
|
223
|
+
}
|
|
224
|
+
.steps {
|
|
225
|
+
list-style: none;
|
|
226
|
+
margin: 0;
|
|
227
|
+
padding: 0;
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
}
|
|
232
|
+
.step {
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 10px;
|
|
236
|
+
padding: 8px 10px;
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
border: 1px solid transparent;
|
|
239
|
+
}
|
|
240
|
+
.step.current {
|
|
241
|
+
border-color: var(--vscode-focusBorder);
|
|
242
|
+
background: color-mix(in srgb, var(--vscode-focusBorder) 12%, transparent);
|
|
243
|
+
}
|
|
244
|
+
.step.done {
|
|
245
|
+
opacity: 0.9;
|
|
246
|
+
}
|
|
247
|
+
.step.todo {
|
|
248
|
+
opacity: 0.7;
|
|
249
|
+
}
|
|
250
|
+
.step-index {
|
|
251
|
+
width: 20px;
|
|
252
|
+
height: 20px;
|
|
253
|
+
border-radius: 50%;
|
|
254
|
+
display: inline-flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: center;
|
|
257
|
+
background: var(--vscode-badge-background);
|
|
258
|
+
color: var(--vscode-badge-foreground);
|
|
259
|
+
font-size: 11px;
|
|
260
|
+
flex-shrink: 0;
|
|
261
|
+
}
|
|
262
|
+
.content h3 {
|
|
263
|
+
margin: 0 0 8px 0;
|
|
264
|
+
font-size: 16px;
|
|
265
|
+
}
|
|
266
|
+
.content p {
|
|
267
|
+
margin: 0 0 10px 0;
|
|
268
|
+
line-height: 1.45;
|
|
269
|
+
}
|
|
270
|
+
.muted {
|
|
271
|
+
color: var(--vscode-descriptionForeground);
|
|
272
|
+
}
|
|
273
|
+
.placeholder-box {
|
|
274
|
+
border: 1px dashed var(--vscode-panel-border);
|
|
275
|
+
border-radius: 8px;
|
|
276
|
+
padding: 12px;
|
|
277
|
+
background: color-mix(in srgb, var(--vscode-editor-background) 75%, var(--vscode-sideBar-background));
|
|
278
|
+
}
|
|
279
|
+
.path-list {
|
|
280
|
+
margin: 8px 0 0 16px;
|
|
281
|
+
padding: 0;
|
|
282
|
+
}
|
|
283
|
+
.path-list li {
|
|
284
|
+
margin: 0 0 6px 0;
|
|
285
|
+
line-height: 1.35;
|
|
286
|
+
}
|
|
287
|
+
.button-row {
|
|
288
|
+
display: flex;
|
|
289
|
+
gap: 8px;
|
|
290
|
+
margin-top: 14px;
|
|
291
|
+
flex-wrap: wrap;
|
|
292
|
+
}
|
|
293
|
+
.button-row.picker-actions {
|
|
294
|
+
justify-content: center;
|
|
295
|
+
margin-bottom: 10px;
|
|
296
|
+
}
|
|
297
|
+
.button-row.picker-actions button {
|
|
298
|
+
min-width: 220px;
|
|
299
|
+
}
|
|
300
|
+
button {
|
|
301
|
+
border: 1px solid var(--vscode-button-border, transparent);
|
|
302
|
+
background: var(--vscode-button-background);
|
|
303
|
+
color: var(--vscode-button-foreground);
|
|
304
|
+
padding: 6px 12px;
|
|
305
|
+
border-radius: 6px;
|
|
306
|
+
cursor: pointer;
|
|
307
|
+
text-align: center;
|
|
308
|
+
}
|
|
309
|
+
button.secondary {
|
|
310
|
+
background: var(--vscode-button-secondaryBackground);
|
|
311
|
+
color: var(--vscode-button-secondaryForeground);
|
|
312
|
+
}
|
|
313
|
+
button:disabled {
|
|
314
|
+
cursor: default;
|
|
315
|
+
opacity: 0.5;
|
|
316
|
+
}
|
|
317
|
+
code {
|
|
318
|
+
font-family: var(--vscode-editor-font-family);
|
|
319
|
+
font-size: 90%;
|
|
320
|
+
}
|
|
321
|
+
@media (max-width: 700px) {
|
|
322
|
+
.grid {
|
|
323
|
+
grid-template-columns: 1fr;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
</style>
|
|
327
|
+
</head>
|
|
328
|
+
<body>
|
|
329
|
+
<div class="wrap">
|
|
330
|
+
<div class="shell-badge">Review Before Write</div>
|
|
331
|
+
<h1>${modeTitle}</h1>
|
|
332
|
+
<p class="subtitle">${modeSubtitle}</p>
|
|
333
|
+
<div class="grid">
|
|
334
|
+
<section class="card">
|
|
335
|
+
<h2>Steps</h2>
|
|
336
|
+
<ol class="steps">${stepListHtml}</ol>
|
|
337
|
+
</section>
|
|
338
|
+
<section class="card content">
|
|
339
|
+
${bodyHtml}
|
|
340
|
+
<div class="button-row">
|
|
341
|
+
<button class="secondary" data-command="back" ${canGoBack ? '' : 'disabled'}>Back</button>
|
|
342
|
+
<button data-command="next" ${canGoNext ? '' : 'disabled'}>${nextButtonLabel}</button>
|
|
343
|
+
</div>
|
|
344
|
+
</section>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
<script>
|
|
348
|
+
const vscode = acquireVsCodeApi();
|
|
349
|
+
document.querySelectorAll('button[data-command]').forEach((button) => {
|
|
350
|
+
button.addEventListener('click', () => {
|
|
351
|
+
const command = button.getAttribute('data-command');
|
|
352
|
+
vscode.postMessage({ command });
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
</script>
|
|
356
|
+
</body>
|
|
357
|
+
</html>`;
|
|
358
|
+
}
|
|
359
|
+
_getStepBody(step) {
|
|
360
|
+
const modeLabel = this._mode === 'collection' ? 'collection JSON file' : 'environment JSON file(s)';
|
|
361
|
+
const sourceTitle = this._mode === 'collection' ? 'Collection JSON' : 'Environment JSON files';
|
|
362
|
+
const sourceButtonLabel = this._mode === 'collection' ? 'Choose Collection JSON' : 'Choose Environment JSON File(s)';
|
|
363
|
+
const selectedSourceHtml = this._sourceFiles.length > 0
|
|
364
|
+
? `
|
|
365
|
+
<div class="placeholder-box">
|
|
366
|
+
<p><strong>${sourceTitle} selected:</strong></p>
|
|
367
|
+
<ul class="path-list">
|
|
368
|
+
${this._sourceFiles.map((uri) => `<li><code>${this._escapeHtml(uri.fsPath)}</code></li>`).join('')}
|
|
369
|
+
</ul>
|
|
370
|
+
</div>
|
|
371
|
+
`
|
|
372
|
+
: `
|
|
373
|
+
<div class="placeholder-box">
|
|
374
|
+
<p>No Postman ${modeLabel} selected yet.</p>
|
|
375
|
+
</div>
|
|
376
|
+
`;
|
|
377
|
+
const destinationHtml = this._destinationFolder
|
|
378
|
+
? `<code>${this._escapeHtml(this._destinationFolder.fsPath)}</code>`
|
|
379
|
+
: '<em>Not selected yet</em>';
|
|
380
|
+
const planSummaryHtml = this._plan ? this._renderPlanSummaryHtml() : '';
|
|
381
|
+
const planWarningsHtml = this._plan ? this._renderWarningsHtml() : '';
|
|
382
|
+
const planBlockersHtml = this._plan ? this._renderBlockersHtml() : '';
|
|
383
|
+
const busyHtml = this._isBusy ? '<p><strong>Working...</strong></p>' : '';
|
|
384
|
+
const errorHtml = this._lastErrorMessage
|
|
385
|
+
? `<div class="placeholder-box"><p><strong>Error:</strong> ${this._escapeHtml(this._lastErrorMessage)}</p></div>`
|
|
386
|
+
: '';
|
|
387
|
+
switch (step.id) {
|
|
388
|
+
case 'select':
|
|
389
|
+
return `
|
|
390
|
+
<h3>Select Source</h3>
|
|
391
|
+
<p>Select the Postman ${modeLabel} and a destination folder.</p>
|
|
392
|
+
<div class="button-row picker-actions">
|
|
393
|
+
<button data-command="pickSource">${sourceButtonLabel}</button>
|
|
394
|
+
<button class="secondary" data-command="pickDestination">Choose Destination Folder</button>
|
|
395
|
+
</div>
|
|
396
|
+
${selectedSourceHtml}
|
|
397
|
+
<div class="placeholder-box">
|
|
398
|
+
<p><strong>Destination folder:</strong> ${destinationHtml}</p>
|
|
399
|
+
</div>
|
|
400
|
+
${errorHtml}
|
|
401
|
+
`;
|
|
402
|
+
case 'review':
|
|
403
|
+
return `
|
|
404
|
+
<h3>Review</h3>
|
|
405
|
+
<p>Review the import summary before writing files.</p>
|
|
406
|
+
${busyHtml}
|
|
407
|
+
${planSummaryHtml || `<div class="placeholder-box"><p>No import plan is available yet.</p></div>`}
|
|
408
|
+
${planWarningsHtml}
|
|
409
|
+
${planBlockersHtml}
|
|
410
|
+
${errorHtml}
|
|
411
|
+
`;
|
|
412
|
+
case 'result':
|
|
413
|
+
return `
|
|
414
|
+
<h3>Result</h3>
|
|
415
|
+
<p>Import result summary.</p>
|
|
416
|
+
${this._renderResultHtml()}
|
|
417
|
+
${planWarningsHtml}
|
|
418
|
+
${errorHtml}
|
|
419
|
+
`;
|
|
420
|
+
default:
|
|
421
|
+
return '<h3>Postman Import</h3><p>Unknown step.</p>';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async _pickSourceFiles() {
|
|
425
|
+
if (this._isDialogOpen) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this._isDialogOpen = true;
|
|
429
|
+
try {
|
|
430
|
+
const picked = await vscode.window.showOpenDialog({
|
|
431
|
+
canSelectFiles: true,
|
|
432
|
+
canSelectFolders: false,
|
|
433
|
+
canSelectMany: this._mode === 'environment',
|
|
434
|
+
filters: { JSON: ['json'] },
|
|
435
|
+
title: this._mode === 'collection'
|
|
436
|
+
? 'Select Postman Collection JSON'
|
|
437
|
+
: 'Select Postman Environment JSON File(s)',
|
|
438
|
+
openLabel: this._mode === 'collection' ? 'Select Collection' : 'Select Environment File(s)',
|
|
439
|
+
defaultUri: this._getWorkspaceDefaultUri() ?? this._sourceFiles[0],
|
|
440
|
+
});
|
|
441
|
+
if (!picked || picked.length === 0) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
this._sourceFiles = this._mode === 'collection' ? [picked[0]] : picked;
|
|
445
|
+
this._writeResult = undefined;
|
|
446
|
+
this._plan = undefined;
|
|
447
|
+
this._lastErrorMessage = undefined;
|
|
448
|
+
if (!this._destinationSelectedByUser) {
|
|
449
|
+
this._destinationFolder = this._suggestDestinationFolderFromSources();
|
|
450
|
+
}
|
|
451
|
+
this._stepIndex = 0;
|
|
452
|
+
this._render();
|
|
453
|
+
}
|
|
454
|
+
finally {
|
|
455
|
+
this._isDialogOpen = false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async _pickDestinationFolder() {
|
|
459
|
+
if (this._isDialogOpen) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
this._isDialogOpen = true;
|
|
463
|
+
try {
|
|
464
|
+
const picked = await vscode.window.showOpenDialog({
|
|
465
|
+
canSelectFiles: false,
|
|
466
|
+
canSelectFolders: true,
|
|
467
|
+
canSelectMany: false,
|
|
468
|
+
title: 'Select Destination Folder for Generated Norn Files',
|
|
469
|
+
openLabel: 'Select Destination',
|
|
470
|
+
defaultUri: this._getWorkspaceDefaultUri() ?? this._destinationFolder ?? this._sourceFiles[0],
|
|
471
|
+
});
|
|
472
|
+
if (!picked || picked.length === 0) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
this._destinationFolder = picked[0];
|
|
476
|
+
this._destinationSelectedByUser = true;
|
|
477
|
+
this._writeResult = undefined;
|
|
478
|
+
if (this._sourceFiles.length > 0) {
|
|
479
|
+
await this._rebuildPlan();
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
this._render();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
this._isDialogOpen = false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async _rebuildPlan() {
|
|
490
|
+
if (!this._destinationFolder || this._sourceFiles.length === 0) {
|
|
491
|
+
this._plan = undefined;
|
|
492
|
+
this._writeResult = undefined;
|
|
493
|
+
this._render();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
this._isBusy = true;
|
|
497
|
+
this._lastErrorMessage = undefined;
|
|
498
|
+
this._render();
|
|
499
|
+
try {
|
|
500
|
+
this._plan = (0, postmanImportPlanner_1.buildPostmanImportPlan)(this._mode, this._sourceFiles.map(uri => uri.fsPath), this._destinationFolder.fsPath);
|
|
501
|
+
// After parse, refine collection destination suggestion if the user hasn't picked one manually.
|
|
502
|
+
if (!this._destinationSelectedByUser && this._mode === 'collection' && this._plan.summary.title) {
|
|
503
|
+
const suggested = this._suggestCollectionDestinationFromPlanTitle(this._plan.summary.title);
|
|
504
|
+
if (suggested && (!this._destinationFolder || this._destinationFolder.fsPath !== suggested.fsPath)) {
|
|
505
|
+
this._destinationFolder = suggested;
|
|
506
|
+
this._plan = (0, postmanImportPlanner_1.buildPostmanImportPlan)(this._mode, this._sourceFiles.map(uri => uri.fsPath), this._destinationFolder.fsPath);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
this._writeResult = undefined;
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
this._plan = undefined;
|
|
513
|
+
this._lastErrorMessage = error instanceof Error ? error.message : String(error);
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
this._isBusy = false;
|
|
517
|
+
this._render();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async _executeImport() {
|
|
521
|
+
if (!this._plan) {
|
|
522
|
+
void vscode.window.showWarningMessage('No import plan is available yet. Select a source file and review the plan first.');
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (this._plan.blockers.length > 0) {
|
|
526
|
+
void vscode.window.showWarningMessage('Import is blocked. Resolve blockers (for example file overlaps) before importing.');
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
this._isBusy = true;
|
|
530
|
+
this._lastErrorMessage = undefined;
|
|
531
|
+
this._render();
|
|
532
|
+
try {
|
|
533
|
+
this._writeResult = (0, postmanImportPlanner_1.writePostmanImportPlan)(this._plan);
|
|
534
|
+
this._stepIndex = IMPORT_STEPS.length - 1;
|
|
535
|
+
this._render();
|
|
536
|
+
void vscode.window.showInformationMessage(`Norn: Imported ${this._writeResult.createdFiles.length} file(s) from Postman.`);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
this._lastErrorMessage = error instanceof Error ? error.message : String(error);
|
|
540
|
+
}
|
|
541
|
+
finally {
|
|
542
|
+
this._isBusy = false;
|
|
543
|
+
this._render();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
_getWorkspaceDefaultUri() {
|
|
547
|
+
return vscode.workspace.workspaceFolders?.[0]?.uri;
|
|
548
|
+
}
|
|
549
|
+
_suggestDestinationFolderFromSources() {
|
|
550
|
+
if (this._sourceFiles.length === 0) {
|
|
551
|
+
return this._getWorkspaceDefaultUri();
|
|
552
|
+
}
|
|
553
|
+
const sourceDir = path.dirname(this._sourceFiles[0].fsPath);
|
|
554
|
+
if (this._mode === 'collection') {
|
|
555
|
+
const sourceBaseName = path.basename(this._sourceFiles[0].fsPath, path.extname(this._sourceFiles[0].fsPath));
|
|
556
|
+
const folderName = this._toKebab(sourceBaseName) || 'postman-import';
|
|
557
|
+
return vscode.Uri.file(path.join(sourceDir, folderName));
|
|
558
|
+
}
|
|
559
|
+
return vscode.Uri.file(sourceDir);
|
|
560
|
+
}
|
|
561
|
+
_suggestCollectionDestinationFromPlanTitle(title) {
|
|
562
|
+
if (this._sourceFiles.length === 0) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
const sourceDir = path.dirname(this._sourceFiles[0].fsPath);
|
|
566
|
+
const folderName = this._toKebab(title) || 'postman-import';
|
|
567
|
+
return vscode.Uri.file(path.join(sourceDir, folderName));
|
|
568
|
+
}
|
|
569
|
+
_canGoNext() {
|
|
570
|
+
if (this._isBusy) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
if (this._stepIndex >= IMPORT_STEPS.length - 1) {
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
if (this._stepIndex === 0) {
|
|
577
|
+
return this._sourceFiles.length > 0;
|
|
578
|
+
}
|
|
579
|
+
if (this._stepIndex === 1) {
|
|
580
|
+
if (this._writeResult) {
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
return Boolean(this._plan && this._plan.blockers.length === 0);
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
_renderPlanSummaryHtml() {
|
|
588
|
+
if (!this._plan) {
|
|
589
|
+
return '';
|
|
590
|
+
}
|
|
591
|
+
const summary = this._plan.summary;
|
|
592
|
+
const fileList = this._plan.files.slice(0, 12).map(file => {
|
|
593
|
+
const badges = [];
|
|
594
|
+
if (file.writeMode === 'append') {
|
|
595
|
+
badges.push('append');
|
|
596
|
+
}
|
|
597
|
+
else if (file.existsCollision) {
|
|
598
|
+
badges.push('exists');
|
|
599
|
+
}
|
|
600
|
+
const badgeText = badges.length > 0 ? ` <strong>(${badges.join(', ')})</strong>` : '';
|
|
601
|
+
return `<li><code>${this._escapeHtml(file.relativePath)}</code>${badgeText}</li>`;
|
|
602
|
+
}).join('');
|
|
603
|
+
const moreFiles = this._plan.files.length > 12 ? `<li>...and ${this._plan.files.length - 12} more</li>` : '';
|
|
604
|
+
const detailRows = [];
|
|
605
|
+
if (summary.kind === 'collection') {
|
|
606
|
+
detailRows.push(`<p><strong>Folders:</strong> ${summary.folderCount}</p>`);
|
|
607
|
+
detailRows.push(`<p><strong>Requests:</strong> ${summary.requestCount}</p>`);
|
|
608
|
+
detailRows.push(`<p><strong>Collection variables:</strong> ${summary.variableCount}</p>`);
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
detailRows.push(`<p><strong>Environments:</strong> ${summary.environmentCount}</p>`);
|
|
612
|
+
detailRows.push(`<p><strong>Imported values:</strong> ${summary.variableCount}</p>`);
|
|
613
|
+
detailRows.push(`<p><strong>Marked secret:</strong> ${summary.secretCount}</p>`);
|
|
614
|
+
}
|
|
615
|
+
return `
|
|
616
|
+
<div class="placeholder-box">
|
|
617
|
+
<p><strong>Title:</strong> ${this._escapeHtml(summary.title)}</p>
|
|
618
|
+
<p><strong>Destination:</strong> <code>${this._escapeHtml(summary.destinationFolder)}</code></p>
|
|
619
|
+
<p><strong>Source files:</strong> ${summary.sourceFileCount}</p>
|
|
620
|
+
<p><strong>Files to create:</strong> ${summary.fileCount}</p>
|
|
621
|
+
${detailRows.join('')}
|
|
622
|
+
</div>
|
|
623
|
+
<div class="placeholder-box">
|
|
624
|
+
<p><strong>Planned files</strong></p>
|
|
625
|
+
<ul class="path-list">${fileList}${moreFiles}</ul>
|
|
626
|
+
</div>
|
|
627
|
+
`;
|
|
628
|
+
}
|
|
629
|
+
_renderWarningsHtml() {
|
|
630
|
+
if (!this._plan || this._plan.warnings.length === 0) {
|
|
631
|
+
return '';
|
|
632
|
+
}
|
|
633
|
+
const warningItems = this._plan.warnings.slice(0, 10).map(warning => {
|
|
634
|
+
const contextBits = [warning.requestName, warning.sourcePath].filter(Boolean).map(value => this._escapeHtml(String(value)));
|
|
635
|
+
const contextText = contextBits.length > 0 ? ` <span class="muted">(${contextBits.join(' | ')})</span>` : '';
|
|
636
|
+
return `<li>${this._escapeHtml(warning.message)}${contextText}</li>`;
|
|
637
|
+
}).join('');
|
|
638
|
+
const moreWarnings = this._plan.warnings.length > 10 ? `<li>...and ${this._plan.warnings.length - 10} more warning(s)</li>` : '';
|
|
639
|
+
return `
|
|
640
|
+
<div class="placeholder-box">
|
|
641
|
+
<p><strong>Warnings (${this._plan.warnings.length})</strong></p>
|
|
642
|
+
<ul class="path-list">${warningItems}${moreWarnings}</ul>
|
|
643
|
+
</div>
|
|
644
|
+
`;
|
|
645
|
+
}
|
|
646
|
+
_renderBlockersHtml() {
|
|
647
|
+
if (!this._plan || this._plan.blockers.length === 0) {
|
|
648
|
+
return '';
|
|
649
|
+
}
|
|
650
|
+
const blockerItems = this._plan.blockers.map(blocker => {
|
|
651
|
+
const pathHtml = blocker.path ? ` <span class="muted">(${this._escapeHtml(blocker.path)})</span>` : '';
|
|
652
|
+
return `<li>${this._escapeHtml(blocker.message)}${pathHtml}</li>`;
|
|
653
|
+
}).join('');
|
|
654
|
+
return `
|
|
655
|
+
<div class="placeholder-box">
|
|
656
|
+
<p><strong>Blockers (${this._plan.blockers.length})</strong> - Import cannot continue until these are resolved.</p>
|
|
657
|
+
<ul class="path-list">${blockerItems}</ul>
|
|
658
|
+
</div>
|
|
659
|
+
`;
|
|
660
|
+
}
|
|
661
|
+
_renderImportActionHtml() {
|
|
662
|
+
if (!this._plan) {
|
|
663
|
+
return '';
|
|
664
|
+
}
|
|
665
|
+
if (this._writeResult) {
|
|
666
|
+
return `
|
|
667
|
+
<div class="placeholder-box">
|
|
668
|
+
<p><strong>Import already completed.</strong> Use Next to view the result summary.</p>
|
|
669
|
+
</div>
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
const disabled = this._isBusy || this._plan.blockers.length > 0;
|
|
673
|
+
const reason = this._plan.blockers.length > 0
|
|
674
|
+
? '<p>Import is blocked. Resolve blockers (usually file overlaps or invalid input) before retrying.</p>'
|
|
675
|
+
: '<p>Ready to write files to disk.</p>';
|
|
676
|
+
return `
|
|
677
|
+
<div class="placeholder-box">
|
|
678
|
+
${reason}
|
|
679
|
+
<div class="button-row">
|
|
680
|
+
<button data-command="executeImport" ${disabled ? 'disabled' : ''}>Import</button>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
`;
|
|
684
|
+
}
|
|
685
|
+
_renderResultHtml() {
|
|
686
|
+
if (!this._writeResult) {
|
|
687
|
+
return `
|
|
688
|
+
<div class="placeholder-box">
|
|
689
|
+
<p>No import has been executed yet.</p>
|
|
690
|
+
</div>
|
|
691
|
+
`;
|
|
692
|
+
}
|
|
693
|
+
const created = this._writeResult.createdFiles.map(file => `<li><code>${this._escapeHtml(file)}</code></li>`).join('');
|
|
694
|
+
const skipped = this._writeResult.skippedFiles.length > 0
|
|
695
|
+
? `<p><strong>Skipped files:</strong> ${this._writeResult.skippedFiles.length}</p>`
|
|
696
|
+
: '';
|
|
697
|
+
return `
|
|
698
|
+
<div class="placeholder-box">
|
|
699
|
+
<p><strong>Created files:</strong> ${this._writeResult.createdFiles.length}</p>
|
|
700
|
+
${skipped}
|
|
701
|
+
<p><strong>Warnings:</strong> ${this._writeResult.warningCount}</p>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="placeholder-box">
|
|
704
|
+
<p><strong>Created file list</strong></p>
|
|
705
|
+
<ul class="path-list">${created || '<li>(none)</li>'}</ul>
|
|
706
|
+
</div>
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
_escapeHtml(value) {
|
|
710
|
+
return value
|
|
711
|
+
.replace(/&/g, '&')
|
|
712
|
+
.replace(/</g, '<')
|
|
713
|
+
.replace(/>/g, '>')
|
|
714
|
+
.replace(/"/g, '"')
|
|
715
|
+
.replace(/'/g, ''');
|
|
716
|
+
}
|
|
717
|
+
_toKebab(value) {
|
|
718
|
+
return value
|
|
719
|
+
.trim()
|
|
720
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
721
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
722
|
+
.replace(/^-+|-+$/g, '')
|
|
723
|
+
.toLowerCase();
|
|
724
|
+
}
|
|
725
|
+
static getPanelTitle(mode) {
|
|
726
|
+
return mode === 'collection'
|
|
727
|
+
? 'Norn: Import Postman Collection'
|
|
728
|
+
: 'Norn: Import Postman Environment';
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
exports.PostmanImportPanel = PostmanImportPanel;
|
|
732
|
+
//# sourceMappingURL=postmanImportPanel.js.map
|