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,604 @@
|
|
|
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.getBuiltInSqlAdapters = getBuiltInSqlAdapters;
|
|
37
|
+
exports.getBuiltInSqlAdapter = getBuiltInSqlAdapter;
|
|
38
|
+
exports.isBuiltInSqlAdapter = isBuiltInSqlAdapter;
|
|
39
|
+
exports.getBuiltInSqlAdapterIds = getBuiltInSqlAdapterIds;
|
|
40
|
+
exports.runBuiltInSqlAdapter = runBuiltInSqlAdapter;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const pg_1 = require("pg");
|
|
45
|
+
const mssql = __importStar(require("mssql"));
|
|
46
|
+
const BUILT_IN_SQL_ADAPTERS = [
|
|
47
|
+
{
|
|
48
|
+
id: 'postgres',
|
|
49
|
+
label: 'PostgreSQL',
|
|
50
|
+
description: 'Built-in PostgreSQL adapter using a connection string.',
|
|
51
|
+
connectionSetupSummary: 'connectionString',
|
|
52
|
+
optionalConnectionKeys: []
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'sqlserver',
|
|
56
|
+
label: 'SQL Server',
|
|
57
|
+
description: 'Built-in SQL Server adapter using a connection string.',
|
|
58
|
+
connectionSetupSummary: 'connectionString',
|
|
59
|
+
optionalConnectionKeys: []
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'sqlserver-windows',
|
|
63
|
+
label: 'SQL Server (Integrated Auth)',
|
|
64
|
+
description: 'Built-in SQL Server adapter using PowerShell and Microsoft.Data.SqlClient for Windows/Trusted authentication.',
|
|
65
|
+
connectionSetupSummary: 'connectionString',
|
|
66
|
+
optionalConnectionKeys: []
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
const BUILT_IN_SQL_ADAPTER_MAP = new Map(BUILT_IN_SQL_ADAPTERS.map(adapter => [adapter.id.toLowerCase(), adapter]));
|
|
70
|
+
const NAMED_PARAMETER_REGEX = /(?<!:):([A-Za-z_][A-Za-z0-9_]*)\b/g;
|
|
71
|
+
function fail(message) {
|
|
72
|
+
throw new Error(message);
|
|
73
|
+
}
|
|
74
|
+
function getConnectionValue(values, ...keys) {
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const value = values[key];
|
|
77
|
+
if (value !== undefined && value !== null && `${value}`.trim() !== '') {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function getConnectionString(values) {
|
|
84
|
+
return getConnectionValue(values, 'connectionString');
|
|
85
|
+
}
|
|
86
|
+
function usesWindowsIntegratedSqlServerAuth(connectionString) {
|
|
87
|
+
return /(?:^|;)\s*Integrated\s+Security\s*=\s*(?:true|yes|sspi)\s*(?:;|$)/i.test(connectionString)
|
|
88
|
+
|| /(?:^|;)\s*Trusted_Connection\s*=\s*(?:true|yes|sspi)\s*(?:;|$)/i.test(connectionString);
|
|
89
|
+
}
|
|
90
|
+
function buildMissingConnectionError(adapterLabel, profile) {
|
|
91
|
+
return `Missing required ${adapterLabel} connection string. Expected 'connectionString ${profile} = ...' in .nornenv.`;
|
|
92
|
+
}
|
|
93
|
+
function getBundledSqlClientDirectory() {
|
|
94
|
+
return path.resolve(__dirname, '..', 'vendor', 'microsoft.data.sqlclient', 'windows-netfx');
|
|
95
|
+
}
|
|
96
|
+
function ensureBundledSqlClientDirectory() {
|
|
97
|
+
const sqlClientDir = getBundledSqlClientDirectory();
|
|
98
|
+
const mainAssemblyPath = path.join(sqlClientDir, 'Microsoft.Data.SqlClient.dll');
|
|
99
|
+
if (!fs.existsSync(mainAssemblyPath)) {
|
|
100
|
+
fail(`Missing bundled Microsoft.Data.SqlClient runtime at '${mainAssemblyPath}'.`);
|
|
101
|
+
}
|
|
102
|
+
return sqlClientDir;
|
|
103
|
+
}
|
|
104
|
+
let pwshAvailable = null;
|
|
105
|
+
function isPwshAvailable() {
|
|
106
|
+
if (pwshAvailable !== null) {
|
|
107
|
+
return pwshAvailable;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const result = (0, child_process_1.spawnSync)('pwsh', ['--version'], {
|
|
111
|
+
timeout: 5000,
|
|
112
|
+
stdio: 'pipe',
|
|
113
|
+
});
|
|
114
|
+
pwshAvailable = result.status === 0;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
pwshAvailable = false;
|
|
118
|
+
}
|
|
119
|
+
return pwshAvailable;
|
|
120
|
+
}
|
|
121
|
+
function getPowerShellCommand() {
|
|
122
|
+
return isPwshAvailable() ? 'pwsh' : 'powershell';
|
|
123
|
+
}
|
|
124
|
+
function getIntegratedPowerShellCommand() {
|
|
125
|
+
if (process.platform === 'win32') {
|
|
126
|
+
return 'powershell';
|
|
127
|
+
}
|
|
128
|
+
return getPowerShellCommand();
|
|
129
|
+
}
|
|
130
|
+
function encodePowerShellCommand(script) {
|
|
131
|
+
return Buffer.from(script, 'utf16le').toString('base64');
|
|
132
|
+
}
|
|
133
|
+
async function runSqlServerIntegratedViaPowerShell(request) {
|
|
134
|
+
if (process.platform !== 'win32') {
|
|
135
|
+
fail('Built-in adapter sqlserver-windows is supported on Windows only.');
|
|
136
|
+
}
|
|
137
|
+
const sqlClientDir = ensureBundledSqlClientDirectory();
|
|
138
|
+
const script = `
|
|
139
|
+
$ErrorActionPreference = 'Stop'
|
|
140
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
141
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
142
|
+
|
|
143
|
+
$raw = [Console]::In.ReadToEnd()
|
|
144
|
+
if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
145
|
+
throw 'Missing SQL adapter payload.'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
$payload = $raw | ConvertFrom-Json
|
|
149
|
+
$connectionString = [string]$payload.connection.values.connectionString
|
|
150
|
+
if ([string]::IsNullOrWhiteSpace($connectionString)) {
|
|
151
|
+
throw 'Missing SQL Server Integrated Auth connection string.'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
$sqlClientDir = [string]$env:NORN_SQLCLIENT_DIR
|
|
155
|
+
if ([string]::IsNullOrWhiteSpace($sqlClientDir) -or -not (Test-Path $sqlClientDir)) {
|
|
156
|
+
throw 'Missing bundled Microsoft.Data.SqlClient runtime files.'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$env:PATH = "$sqlClientDir;$env:PATH"
|
|
160
|
+
|
|
161
|
+
$resolveHandler = [System.ResolveEventHandler]{
|
|
162
|
+
param($sender, $eventArgs)
|
|
163
|
+
|
|
164
|
+
$assemblyName = ([System.Reflection.AssemblyName]$eventArgs.Name).Name + '.dll'
|
|
165
|
+
$candidate = Join-Path $env:NORN_SQLCLIENT_DIR $assemblyName
|
|
166
|
+
if (Test-Path $candidate) {
|
|
167
|
+
return [System.Reflection.Assembly]::LoadFrom($candidate)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return $null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($resolveHandler)
|
|
174
|
+
[void][System.Reflection.Assembly]::LoadFrom((Join-Path $sqlClientDir 'Microsoft.Data.SqlClient.dll'))
|
|
175
|
+
|
|
176
|
+
$sqlText = [string]$payload.operation.sql
|
|
177
|
+
$mode = [string]$payload.mode
|
|
178
|
+
$paramsObject = $payload.params
|
|
179
|
+
$connectionStringHasUserId = $connectionString -match '(?i)(?:^|;)\s*(?:User ID|UID)\s*='
|
|
180
|
+
$connectionStringHasPassword = $connectionString -match '(?i)(?:^|;)\s*(?:Password|PWD)\s*='
|
|
181
|
+
|
|
182
|
+
$connection = $null
|
|
183
|
+
$connectionBuilder = $null
|
|
184
|
+
try {
|
|
185
|
+
$connectionBuilder = New-Object Microsoft.Data.SqlClient.SqlConnectionStringBuilder $connectionString
|
|
186
|
+
$connection = New-Object Microsoft.Data.SqlClient.SqlConnection $connectionString
|
|
187
|
+
$command = $connection.CreateCommand()
|
|
188
|
+
$command.CommandText = $sqlText
|
|
189
|
+
|
|
190
|
+
if ($null -ne $paramsObject) {
|
|
191
|
+
foreach ($property in $paramsObject.PSObject.Properties) {
|
|
192
|
+
$value = $property.Value
|
|
193
|
+
if ($null -eq $value) {
|
|
194
|
+
$value = [DBNull]::Value
|
|
195
|
+
} elseif ($value -is [datetime]) {
|
|
196
|
+
$value = $value.ToUniversalTime()
|
|
197
|
+
} elseif ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string]) -and -not ($value -is [byte[]])) {
|
|
198
|
+
$value = ($value | ConvertTo-Json -Depth 100 -Compress)
|
|
199
|
+
} elseif ($value -is [pscustomobject]) {
|
|
200
|
+
$value = ($value | ConvertTo-Json -Depth 100 -Compress)
|
|
201
|
+
}
|
|
202
|
+
[void]$command.Parameters.AddWithValue("@$($property.Name)", $value)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
$connection.Open()
|
|
207
|
+
|
|
208
|
+
if ($mode -eq 'query') {
|
|
209
|
+
$reader = $command.ExecuteReader()
|
|
210
|
+
$rows = @()
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
while ($reader.Read()) {
|
|
214
|
+
$row = [ordered]@{}
|
|
215
|
+
for ($i = 0; $i -lt $reader.FieldCount; $i++) {
|
|
216
|
+
$value = $reader.GetValue($i)
|
|
217
|
+
if ($value -is [DBNull]) {
|
|
218
|
+
$value = $null
|
|
219
|
+
} elseif ($value -is [datetime]) {
|
|
220
|
+
$value = $value.ToString('o')
|
|
221
|
+
} elseif ($value -is [byte[]]) {
|
|
222
|
+
$value = [Convert]::ToBase64String($value)
|
|
223
|
+
}
|
|
224
|
+
$row[$reader.GetName($i)] = $value
|
|
225
|
+
}
|
|
226
|
+
$rows += [pscustomobject]$row
|
|
227
|
+
}
|
|
228
|
+
} finally {
|
|
229
|
+
$reader.Close()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
$response = [pscustomobject]@{
|
|
233
|
+
success = $true
|
|
234
|
+
result = [pscustomobject]@{
|
|
235
|
+
kind = 'rows'
|
|
236
|
+
rowCount = @($rows).Count
|
|
237
|
+
rows = @($rows)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
$affectedRows = $command.ExecuteNonQuery()
|
|
242
|
+
if ($affectedRows -lt 0) {
|
|
243
|
+
$affectedRows = 0
|
|
244
|
+
}
|
|
245
|
+
$response = [pscustomobject]@{
|
|
246
|
+
success = $true
|
|
247
|
+
result = [pscustomobject]@{
|
|
248
|
+
kind = 'exec'
|
|
249
|
+
affectedRows = $affectedRows
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
$response | ConvertTo-Json -Depth 100 -Compress
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
$details = New-Object System.Collections.Generic.List[string]
|
|
258
|
+
if ($_.Exception -and $_.Exception.GetType()) {
|
|
259
|
+
$details.Add("type: $($_.Exception.GetType().FullName)")
|
|
260
|
+
}
|
|
261
|
+
if ($_.InvocationInfo -and $_.InvocationInfo.ScriptLineNumber) {
|
|
262
|
+
$details.Add("line: $($_.InvocationInfo.ScriptLineNumber)")
|
|
263
|
+
}
|
|
264
|
+
if ($_.FullyQualifiedErrorId) {
|
|
265
|
+
$details.Add("id: $($_.FullyQualifiedErrorId)")
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
$details.Add("adapter: sqlserver-windows")
|
|
270
|
+
$details.Add("powershell: $($PSVersionTable.PSVersion)")
|
|
271
|
+
$details.Add("sqlClientVersion: $(([Microsoft.Data.SqlClient.SqlConnection]).Assembly.GetName().Version)")
|
|
272
|
+
$details.Add("windowsIdentity: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)")
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if ($null -ne $connectionBuilder) {
|
|
277
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$connectionBuilder.DataSource)) {
|
|
278
|
+
$details.Add("dataSource: $($connectionBuilder.DataSource)")
|
|
279
|
+
}
|
|
280
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$connectionBuilder.InitialCatalog)) {
|
|
281
|
+
$details.Add("database: $($connectionBuilder.InitialCatalog)")
|
|
282
|
+
}
|
|
283
|
+
$details.Add("integratedSecurity: $($connectionBuilder.IntegratedSecurity)")
|
|
284
|
+
$details.Add("encrypt: $($connectionBuilder.Encrypt)")
|
|
285
|
+
$details.Add("trustServerCertificate: $($connectionBuilder.TrustServerCertificate)")
|
|
286
|
+
$details.Add("applicationIntent: $($connectionBuilder.ApplicationIntent)")
|
|
287
|
+
$details.Add("multiSubnetFailover: $($connectionBuilder.MultiSubnetFailover)")
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
$hostNameInCertificate = [string]$connectionBuilder['HostNameInCertificate']
|
|
291
|
+
if (-not [string]::IsNullOrWhiteSpace($hostNameInCertificate)) {
|
|
292
|
+
$details.Add("hostNameInCertificate: $hostNameInCertificate")
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
$details.Add("userIdPresent: $connectionStringHasUserId")
|
|
299
|
+
$details.Add("passwordPresent: $connectionStringHasPassword")
|
|
300
|
+
|
|
301
|
+
$sqlException = $null
|
|
302
|
+
if ($_.Exception -is [Microsoft.Data.SqlClient.SqlException]) {
|
|
303
|
+
$sqlException = [Microsoft.Data.SqlClient.SqlException]$_.Exception
|
|
304
|
+
} elseif ($_.Exception -and $_.Exception.InnerException -is [Microsoft.Data.SqlClient.SqlException]) {
|
|
305
|
+
$sqlException = [Microsoft.Data.SqlClient.SqlException]$_.Exception.InnerException
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if ($null -ne $sqlException) {
|
|
309
|
+
$details.Add("sqlNumber: $($sqlException.Number)")
|
|
310
|
+
$details.Add("sqlState: $($sqlException.State)")
|
|
311
|
+
$details.Add("sqlClass: $($sqlException.Class)")
|
|
312
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$sqlException.Server)) {
|
|
313
|
+
$details.Add("sqlServer: $($sqlException.Server)")
|
|
314
|
+
}
|
|
315
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$sqlException.Procedure)) {
|
|
316
|
+
$details.Add("sqlProcedure: $($sqlException.Procedure)")
|
|
317
|
+
}
|
|
318
|
+
if ($sqlException.LineNumber -gt 0) {
|
|
319
|
+
$details.Add("sqlLineNumber: $($sqlException.LineNumber)")
|
|
320
|
+
}
|
|
321
|
+
if ($sqlException.ClientConnectionId -ne [guid]::Empty) {
|
|
322
|
+
$details.Add("clientConnectionId: $($sqlException.ClientConnectionId)")
|
|
323
|
+
}
|
|
324
|
+
if ($sqlException.Errors -and $sqlException.Errors.Count -gt 0) {
|
|
325
|
+
$messages = @()
|
|
326
|
+
foreach ($sqlError in $sqlException.Errors) {
|
|
327
|
+
if ($sqlError -and -not [string]::IsNullOrWhiteSpace([string]$sqlError.Message)) {
|
|
328
|
+
$messages += [string]$sqlError.Message
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if ($messages.Count -gt 0) {
|
|
332
|
+
$details.Add("sqlErrors: $($messages -join ' | ')")
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if ($_.Exception -and $_.Exception.InnerException) {
|
|
338
|
+
$details.Add("innerType: $($_.Exception.InnerException.GetType().FullName)")
|
|
339
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$_.Exception.InnerException.Message)) {
|
|
340
|
+
$details.Add("innerMessage: $($_.Exception.InnerException.Message)")
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
[Console]::Error.WriteLine("$($_.Exception.Message)")
|
|
345
|
+
foreach ($detail in $details) {
|
|
346
|
+
[Console]::Error.WriteLine(" $detail")
|
|
347
|
+
}
|
|
348
|
+
exit 1
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
if ($null -ne $resolveHandler) {
|
|
352
|
+
[System.AppDomain]::CurrentDomain.remove_AssemblyResolve($resolveHandler)
|
|
353
|
+
}
|
|
354
|
+
if ($null -ne $connection) {
|
|
355
|
+
$connection.Dispose()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
`.trim();
|
|
359
|
+
const payload = JSON.stringify(request);
|
|
360
|
+
const command = getIntegratedPowerShellCommand();
|
|
361
|
+
const commandArgs = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-OutputFormat', 'Text', '-EncodedCommand', encodePowerShellCommand(script)];
|
|
362
|
+
return new Promise((resolve, reject) => {
|
|
363
|
+
const child = (0, child_process_1.spawn)(command, commandArgs, {
|
|
364
|
+
cwd: request.context?.workingDir || process.cwd(),
|
|
365
|
+
shell: false,
|
|
366
|
+
env: {
|
|
367
|
+
...process.env,
|
|
368
|
+
NORN_SQLCLIENT_DIR: sqlClientDir,
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
let stdout = '';
|
|
372
|
+
let stderr = '';
|
|
373
|
+
child.stdout.on('data', (data) => {
|
|
374
|
+
stdout += data.toString();
|
|
375
|
+
});
|
|
376
|
+
child.stderr.on('data', (data) => {
|
|
377
|
+
stderr += data.toString();
|
|
378
|
+
});
|
|
379
|
+
child.on('error', (error) => {
|
|
380
|
+
reject(new Error(`Failed to start PowerShell for SQL Server Integrated Auth: ${error.message}`));
|
|
381
|
+
});
|
|
382
|
+
child.on('close', (code) => {
|
|
383
|
+
if (code !== 0) {
|
|
384
|
+
reject(new Error((stderr || stdout || `PowerShell exited with code ${code}`).trim()));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
const parsed = JSON.parse(stdout.trim());
|
|
389
|
+
resolve(parsed);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
reject(new Error(`Invalid PowerShell SQL adapter response: ${error instanceof Error ? error.message : String(error)}`));
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
child.stdin.write(payload);
|
|
396
|
+
child.stdin.end();
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function shouldPassThroughError(error) {
|
|
400
|
+
if (!(error instanceof Error)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
return /^(Missing required|Missing SQL parameter|Invalid )/.test(error.message);
|
|
404
|
+
}
|
|
405
|
+
function formatBuiltInDriverError(adapterLabel, error) {
|
|
406
|
+
if (shouldPassThroughError(error)) {
|
|
407
|
+
return error;
|
|
408
|
+
}
|
|
409
|
+
const candidate = error;
|
|
410
|
+
const baseMessage = error instanceof Error
|
|
411
|
+
? error.message
|
|
412
|
+
: typeof error === 'string'
|
|
413
|
+
? error
|
|
414
|
+
: 'Unknown database error';
|
|
415
|
+
const details = [];
|
|
416
|
+
const append = (label, value) => {
|
|
417
|
+
if (value === undefined || value === null || `${value}`.trim() === '') {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
details.push(`${label}: ${value}`);
|
|
421
|
+
};
|
|
422
|
+
append('code', candidate?.code);
|
|
423
|
+
append('number', candidate?.number);
|
|
424
|
+
append('state', candidate?.state);
|
|
425
|
+
append('severity', candidate?.severity ?? candidate?.class);
|
|
426
|
+
append('detail', candidate?.detail);
|
|
427
|
+
append('hint', candidate?.hint);
|
|
428
|
+
append('schema', candidate?.schema);
|
|
429
|
+
append('table', candidate?.table);
|
|
430
|
+
append('column', candidate?.column);
|
|
431
|
+
append('constraint', candidate?.constraint);
|
|
432
|
+
append('routine', candidate?.routine);
|
|
433
|
+
append('line', candidate?.lineNumber);
|
|
434
|
+
append('procedure', candidate?.procName);
|
|
435
|
+
append('server', candidate?.serverName);
|
|
436
|
+
const precedingErrors = Array.isArray(candidate?.precedingErrors)
|
|
437
|
+
? (candidate?.precedingErrors)
|
|
438
|
+
.map(item => item?.message)
|
|
439
|
+
.filter((message) => message !== undefined && message !== null && `${message}`.trim() !== '')
|
|
440
|
+
: [];
|
|
441
|
+
if (precedingErrors.length > 0) {
|
|
442
|
+
details.push(`precedingErrors: ${precedingErrors.join(' | ')}`);
|
|
443
|
+
}
|
|
444
|
+
const suffix = details.length > 0 ? ` (${details.join(', ')})` : '';
|
|
445
|
+
return new Error(`${adapterLabel} driver error: ${baseMessage}${suffix}`);
|
|
446
|
+
}
|
|
447
|
+
function compilePostgresSql(sql, params) {
|
|
448
|
+
const values = [];
|
|
449
|
+
const positions = new Map();
|
|
450
|
+
const text = sql.replace(NAMED_PARAMETER_REGEX, (_match, paramName) => {
|
|
451
|
+
if (!Object.prototype.hasOwnProperty.call(params, paramName)) {
|
|
452
|
+
fail(`Missing SQL parameter '${paramName}'`);
|
|
453
|
+
}
|
|
454
|
+
if (!positions.has(paramName)) {
|
|
455
|
+
positions.set(paramName, values.length + 1);
|
|
456
|
+
values.push(params[paramName]);
|
|
457
|
+
}
|
|
458
|
+
return `$${positions.get(paramName)}`;
|
|
459
|
+
});
|
|
460
|
+
return { text, values };
|
|
461
|
+
}
|
|
462
|
+
function compileSqlServerSql(sql, params) {
|
|
463
|
+
const names = [];
|
|
464
|
+
const seen = new Set();
|
|
465
|
+
const text = sql.replace(NAMED_PARAMETER_REGEX, (_match, paramName) => {
|
|
466
|
+
if (!Object.prototype.hasOwnProperty.call(params, paramName)) {
|
|
467
|
+
fail(`Missing SQL parameter '${paramName}'`);
|
|
468
|
+
}
|
|
469
|
+
if (!seen.has(paramName)) {
|
|
470
|
+
seen.add(paramName);
|
|
471
|
+
names.push(paramName);
|
|
472
|
+
}
|
|
473
|
+
return `@${paramName}`;
|
|
474
|
+
});
|
|
475
|
+
return { text, names };
|
|
476
|
+
}
|
|
477
|
+
async function runBuiltInPostgresAdapter(request) {
|
|
478
|
+
const values = request.connection.values;
|
|
479
|
+
const connectionString = getConnectionString(values);
|
|
480
|
+
if (!connectionString) {
|
|
481
|
+
fail(buildMissingConnectionError('Postgres', request.connection.profile));
|
|
482
|
+
}
|
|
483
|
+
const client = new pg_1.Client(connectionString);
|
|
484
|
+
try {
|
|
485
|
+
await client.connect();
|
|
486
|
+
const compiled = compilePostgresSql(request.operation.sql, request.params || {});
|
|
487
|
+
const result = await client.query(compiled.text, compiled.values);
|
|
488
|
+
if (request.mode === 'query') {
|
|
489
|
+
return {
|
|
490
|
+
success: true,
|
|
491
|
+
result: {
|
|
492
|
+
kind: 'rows',
|
|
493
|
+
rowCount: result.rowCount ?? result.rows.length,
|
|
494
|
+
rows: result.rows
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
success: true,
|
|
500
|
+
result: {
|
|
501
|
+
kind: 'exec',
|
|
502
|
+
affectedRows: result.rowCount ?? 0
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
throw formatBuiltInDriverError('Postgres', error);
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
await client.end().catch(() => undefined);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function runBuiltInSqlServerAdapter(request) {
|
|
514
|
+
const values = request.connection.values;
|
|
515
|
+
const connectionString = getConnectionString(values);
|
|
516
|
+
if (!connectionString) {
|
|
517
|
+
fail(buildMissingConnectionError('SQL Server', request.connection.profile));
|
|
518
|
+
}
|
|
519
|
+
if (usesWindowsIntegratedSqlServerAuth(connectionString)) {
|
|
520
|
+
fail("This SQL Server connection string uses Windows/Integrated authentication. Use adapter 'sqlserver-windows' in norn.config.json sql.connections.");
|
|
521
|
+
}
|
|
522
|
+
const pool = new mssql.ConnectionPool(connectionString);
|
|
523
|
+
try {
|
|
524
|
+
await pool.connect();
|
|
525
|
+
const compiled = compileSqlServerSql(request.operation.sql, request.params || {});
|
|
526
|
+
const sqlRequest = pool.request();
|
|
527
|
+
for (const name of compiled.names) {
|
|
528
|
+
sqlRequest.input(name, request.params[name]);
|
|
529
|
+
}
|
|
530
|
+
const result = await sqlRequest.query(compiled.text);
|
|
531
|
+
if (request.mode === 'query') {
|
|
532
|
+
const rows = result.recordset ?? [];
|
|
533
|
+
return {
|
|
534
|
+
success: true,
|
|
535
|
+
result: {
|
|
536
|
+
kind: 'rows',
|
|
537
|
+
rowCount: rows.length,
|
|
538
|
+
rows
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const affectedRows = (result.rowsAffected || []).reduce((sum, count) => sum + count, 0);
|
|
543
|
+
return {
|
|
544
|
+
success: true,
|
|
545
|
+
result: {
|
|
546
|
+
kind: 'exec',
|
|
547
|
+
affectedRows
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
throw formatBuiltInDriverError('SQL Server', error);
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
await pool.close().catch(() => undefined);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function runBuiltInSqlServerIntegratedAdapter(request) {
|
|
559
|
+
const values = request.connection.values;
|
|
560
|
+
const connectionString = getConnectionString(values);
|
|
561
|
+
if (!connectionString) {
|
|
562
|
+
fail(buildMissingConnectionError('SQL Server (Windows)', request.connection.profile));
|
|
563
|
+
}
|
|
564
|
+
const compiled = compileSqlServerSql(request.operation.sql, request.params || {});
|
|
565
|
+
const compiledRequest = {
|
|
566
|
+
...request,
|
|
567
|
+
operation: {
|
|
568
|
+
...request.operation,
|
|
569
|
+
sql: compiled.text
|
|
570
|
+
},
|
|
571
|
+
params: Object.fromEntries(compiled.names.map(name => [name, request.params?.[name] ?? null]))
|
|
572
|
+
};
|
|
573
|
+
try {
|
|
574
|
+
return await runSqlServerIntegratedViaPowerShell(compiledRequest);
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
throw formatBuiltInDriverError('SQL Server (Windows)', error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function getBuiltInSqlAdapters() {
|
|
581
|
+
return [...BUILT_IN_SQL_ADAPTERS];
|
|
582
|
+
}
|
|
583
|
+
function getBuiltInSqlAdapter(adapterId) {
|
|
584
|
+
return BUILT_IN_SQL_ADAPTER_MAP.get(adapterId.toLowerCase());
|
|
585
|
+
}
|
|
586
|
+
function isBuiltInSqlAdapter(adapterId) {
|
|
587
|
+
return BUILT_IN_SQL_ADAPTER_MAP.has(adapterId.toLowerCase());
|
|
588
|
+
}
|
|
589
|
+
function getBuiltInSqlAdapterIds() {
|
|
590
|
+
return BUILT_IN_SQL_ADAPTERS.map(adapter => adapter.id);
|
|
591
|
+
}
|
|
592
|
+
async function runBuiltInSqlAdapter(adapterId, request) {
|
|
593
|
+
switch (adapterId.toLowerCase()) {
|
|
594
|
+
case 'postgres':
|
|
595
|
+
return runBuiltInPostgresAdapter(request);
|
|
596
|
+
case 'sqlserver':
|
|
597
|
+
return runBuiltInSqlServerAdapter(request);
|
|
598
|
+
case 'sqlserver-windows':
|
|
599
|
+
return runBuiltInSqlServerIntegratedAdapter(request);
|
|
600
|
+
default:
|
|
601
|
+
fail(`Built-in SQL adapter '${adapterId}' is not supported`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
//# sourceMappingURL=sqlBuiltInAdapters.js.map
|