norn-cli 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +390 -176
  2. package/package.json +7 -1
package/dist/cli.js CHANGED
@@ -109375,6 +109375,9 @@ init_quotedString();
109375
109375
  var fs8 = __toESM(require("fs"));
109376
109376
  var path6 = __toESM(require("path"));
109377
109377
 
109378
+ // src/sqlBuiltInAdapters.ts
109379
+ var import_child_process2 = require("child_process");
109380
+
109378
109381
  // node_modules/pg/esm/index.mjs
109379
109382
  var import_lib = __toESM(require_lib2(), 1);
109380
109383
  var Client = import_lib.default.Client;
@@ -109395,16 +109398,23 @@ var BUILT_IN_SQL_ADAPTERS = [
109395
109398
  {
109396
109399
  id: "postgres",
109397
109400
  label: "PostgreSQL",
109398
- description: "Built-in PostgreSQL adapter using either a connection string or split connection values.",
109399
- connectionSetupSummary: "connectionString OR host/server + database + user + password",
109400
- optionalConnectionKeys: ["port", "ssl", "rejectUnauthorized", "schema", "applicationName"]
109401
+ description: "Built-in PostgreSQL adapter using a connection string.",
109402
+ connectionSetupSummary: "connectionString",
109403
+ optionalConnectionKeys: []
109401
109404
  },
109402
109405
  {
109403
109406
  id: "sqlserver",
109404
109407
  label: "SQL Server",
109405
- description: "Built-in SQL Server adapter using either a connection string or split connection values.",
109406
- connectionSetupSummary: "connectionString OR host/server + database + user + password",
109407
- optionalConnectionKeys: ["port", "encrypt", "trustServerCertificate", "instanceName", "connectionTimeout", "requestTimeout", "appName", "domain"]
109408
+ description: "Built-in SQL Server adapter using a connection string.",
109409
+ connectionSetupSummary: "connectionString",
109410
+ optionalConnectionKeys: []
109411
+ },
109412
+ {
109413
+ id: "sqlserver-integrated",
109414
+ label: "SQL Server (Integrated Auth)",
109415
+ description: "Built-in SQL Server adapter using PowerShell for Windows/Trusted authentication.",
109416
+ connectionSetupSummary: "connectionString",
109417
+ optionalConnectionKeys: []
109408
109418
  }
109409
109419
  ];
109410
109420
  var BUILT_IN_SQL_ADAPTER_MAP = new Map(
@@ -109426,31 +109436,222 @@ function getConnectionValue(values, ...keys) {
109426
109436
  function getConnectionString(values) {
109427
109437
  return getConnectionValue(values, "connectionString");
109428
109438
  }
109429
- function parseBoolean(value, defaultValue) {
109430
- if (value === void 0 || value === null || value === "") {
109431
- return defaultValue;
109439
+ function buildMissingConnectionError(adapterLabel, profile) {
109440
+ return `Missing required ${adapterLabel} connection string. Expected 'connectionString ${profile} = ...' in .nornenv.`;
109441
+ }
109442
+ var pwshAvailable2 = null;
109443
+ function isPwshAvailable2() {
109444
+ if (pwshAvailable2 !== null) {
109445
+ return pwshAvailable2;
109432
109446
  }
109433
- const normalized = value.trim().toLowerCase();
109434
- if (["true", "1", "yes", "on"].includes(normalized)) {
109435
- return true;
109447
+ try {
109448
+ const result = (0, import_child_process2.spawnSync)("pwsh", ["--version"], {
109449
+ timeout: 5e3,
109450
+ stdio: "pipe"
109451
+ });
109452
+ pwshAvailable2 = result.status === 0;
109453
+ } catch (error) {
109454
+ pwshAvailable2 = false;
109436
109455
  }
109437
- if (["false", "0", "no", "off"].includes(normalized)) {
109456
+ return pwshAvailable2;
109457
+ }
109458
+ function getPowerShellCommand() {
109459
+ return isPwshAvailable2() ? "pwsh" : "powershell";
109460
+ }
109461
+ function encodePowerShellCommand(script) {
109462
+ return Buffer.from(script, "utf16le").toString("base64");
109463
+ }
109464
+ async function runSqlServerIntegratedViaPowerShell(request) {
109465
+ const script = `
109466
+ $ErrorActionPreference = 'Stop'
109467
+ [Console]::InputEncoding = [System.Text.Encoding]::UTF8
109468
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
109469
+
109470
+ $raw = [Console]::In.ReadToEnd()
109471
+ if ([string]::IsNullOrWhiteSpace($raw)) {
109472
+ throw 'Missing SQL adapter payload.'
109473
+ }
109474
+
109475
+ $payload = $raw | ConvertFrom-Json -Depth 100
109476
+ $connectionString = [string]$payload.connection.values.connectionString
109477
+ if ([string]::IsNullOrWhiteSpace($connectionString)) {
109478
+ throw 'Missing SQL Server Integrated Auth connection string.'
109479
+ }
109480
+
109481
+ $sqlText = [string]$payload.operation.sql
109482
+ $mode = [string]$payload.mode
109483
+ $paramsObject = $payload.params
109484
+
109485
+ $connection = $null
109486
+ try {
109487
+ $connection = New-Object System.Data.SqlClient.SqlConnection $connectionString
109488
+ $command = $connection.CreateCommand()
109489
+ $command.CommandText = $sqlText
109490
+
109491
+ if ($null -ne $paramsObject) {
109492
+ foreach ($property in $paramsObject.PSObject.Properties) {
109493
+ $value = $property.Value
109494
+ if ($null -eq $value) {
109495
+ $value = [DBNull]::Value
109496
+ } elseif ($value -is [datetime]) {
109497
+ $value = $value.ToUniversalTime()
109498
+ } elseif ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string]) -and -not ($value -is [byte[]])) {
109499
+ $value = ($value | ConvertTo-Json -Depth 100 -Compress)
109500
+ } elseif ($value -is [pscustomobject]) {
109501
+ $value = ($value | ConvertTo-Json -Depth 100 -Compress)
109502
+ }
109503
+ [void]$command.Parameters.AddWithValue("@$($property.Name)", $value)
109504
+ }
109505
+ }
109506
+
109507
+ $connection.Open()
109508
+
109509
+ if ($mode -eq 'query') {
109510
+ $reader = $command.ExecuteReader()
109511
+ $rows = @()
109512
+
109513
+ try {
109514
+ while ($reader.Read()) {
109515
+ $row = [ordered]@{}
109516
+ for ($i = 0; $i -lt $reader.FieldCount; $i++) {
109517
+ $value = $reader.GetValue($i)
109518
+ if ($value -is [DBNull]) {
109519
+ $value = $null
109520
+ } elseif ($value -is [datetime]) {
109521
+ $value = $value.ToString('o')
109522
+ } elseif ($value -is [byte[]]) {
109523
+ $value = [Convert]::ToBase64String($value)
109524
+ }
109525
+ $row[$reader.GetName($i)] = $value
109526
+ }
109527
+ $rows += [pscustomobject]$row
109528
+ }
109529
+ } finally {
109530
+ $reader.Close()
109531
+ }
109532
+
109533
+ $response = [pscustomobject]@{
109534
+ success = $true
109535
+ result = [pscustomobject]@{
109536
+ kind = 'rows'
109537
+ rowCount = @($rows).Count
109538
+ rows = @($rows)
109539
+ }
109540
+ }
109541
+ } else {
109542
+ $affectedRows = $command.ExecuteNonQuery()
109543
+ if ($affectedRows -lt 0) {
109544
+ $affectedRows = 0
109545
+ }
109546
+ $response = [pscustomobject]@{
109547
+ success = $true
109548
+ result = [pscustomobject]@{
109549
+ kind = 'exec'
109550
+ affectedRows = $affectedRows
109551
+ }
109552
+ }
109553
+ }
109554
+
109555
+ $response | ConvertTo-Json -Depth 100 -Compress
109556
+ }
109557
+ catch {
109558
+ $details = @()
109559
+ if ($_.Exception -and $_.Exception.GetType()) {
109560
+ $details += "type: $($_.Exception.GetType().FullName)"
109561
+ }
109562
+ if ($_.InvocationInfo -and $_.InvocationInfo.ScriptLineNumber) {
109563
+ $details += "line: $($_.InvocationInfo.ScriptLineNumber)"
109564
+ }
109565
+ if ($_.FullyQualifiedErrorId) {
109566
+ $details += "id: $($_.FullyQualifiedErrorId)"
109567
+ }
109568
+ $suffix = if ($details.Count -gt 0) { " ($($details -join ', '))" } else { '' }
109569
+ [Console]::Error.WriteLine("$($_.Exception.Message)$suffix")
109570
+ exit 1
109571
+ }
109572
+ finally {
109573
+ if ($null -ne $connection) {
109574
+ $connection.Dispose()
109575
+ }
109576
+ }
109577
+ `.trim();
109578
+ const payload = JSON.stringify(request);
109579
+ const command = getPowerShellCommand();
109580
+ const commandArgs = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-OutputFormat", "Text", "-EncodedCommand", encodePowerShellCommand(script)];
109581
+ return new Promise((resolve11, reject) => {
109582
+ const child = (0, import_child_process2.spawn)(command, commandArgs, {
109583
+ cwd: request.context?.workingDir || process.cwd(),
109584
+ shell: false,
109585
+ env: {
109586
+ ...process.env
109587
+ }
109588
+ });
109589
+ let stdout = "";
109590
+ let stderr = "";
109591
+ child.stdout.on("data", (data) => {
109592
+ stdout += data.toString();
109593
+ });
109594
+ child.stderr.on("data", (data) => {
109595
+ stderr += data.toString();
109596
+ });
109597
+ child.on("error", (error) => {
109598
+ reject(new Error(`Failed to start PowerShell for SQL Server Integrated Auth: ${error.message}`));
109599
+ });
109600
+ child.on("close", (code) => {
109601
+ if (code !== 0) {
109602
+ reject(new Error((stderr || stdout || `PowerShell exited with code ${code}`).trim()));
109603
+ return;
109604
+ }
109605
+ try {
109606
+ const parsed = JSON.parse(stdout.trim());
109607
+ resolve11(parsed);
109608
+ } catch (error) {
109609
+ reject(new Error(`Invalid PowerShell SQL adapter response: ${error instanceof Error ? error.message : String(error)}`));
109610
+ }
109611
+ });
109612
+ child.stdin.write(payload);
109613
+ child.stdin.end();
109614
+ });
109615
+ }
109616
+ function shouldPassThroughError(error) {
109617
+ if (!(error instanceof Error)) {
109438
109618
  return false;
109439
109619
  }
109440
- return defaultValue;
109620
+ return /^(Missing required|Missing SQL parameter|Invalid )/.test(error.message);
109441
109621
  }
109442
- function parseOptionalNumber(value, label) {
109443
- if (value === void 0 || value === null || value.trim() === "") {
109444
- return void 0;
109445
- }
109446
- const parsed = Number(value);
109447
- if (!Number.isFinite(parsed)) {
109448
- fail(`Invalid ${label} '${value}'`);
109622
+ function formatBuiltInDriverError(adapterLabel, error) {
109623
+ if (shouldPassThroughError(error)) {
109624
+ return error;
109449
109625
  }
109450
- return parsed;
109451
- }
109452
- function buildMissingConnectionError(adapterLabel) {
109453
- return `Missing required ${adapterLabel} connection values. Expected connectionString or host/server, database, user, and password.`;
109626
+ const candidate = error;
109627
+ const baseMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown database error";
109628
+ const details = [];
109629
+ const append2 = (label, value) => {
109630
+ if (value === void 0 || value === null || `${value}`.trim() === "") {
109631
+ return;
109632
+ }
109633
+ details.push(`${label}: ${value}`);
109634
+ };
109635
+ append2("code", candidate?.code);
109636
+ append2("number", candidate?.number);
109637
+ append2("state", candidate?.state);
109638
+ append2("severity", candidate?.severity ?? candidate?.class);
109639
+ append2("detail", candidate?.detail);
109640
+ append2("hint", candidate?.hint);
109641
+ append2("schema", candidate?.schema);
109642
+ append2("table", candidate?.table);
109643
+ append2("column", candidate?.column);
109644
+ append2("constraint", candidate?.constraint);
109645
+ append2("routine", candidate?.routine);
109646
+ append2("line", candidate?.lineNumber);
109647
+ append2("procedure", candidate?.procName);
109648
+ append2("server", candidate?.serverName);
109649
+ const precedingErrors = Array.isArray(candidate?.precedingErrors) ? (candidate?.precedingErrors).map((item) => item?.message).filter((message) => message !== void 0 && message !== null && `${message}`.trim() !== "") : [];
109650
+ if (precedingErrors.length > 0) {
109651
+ details.push(`precedingErrors: ${precedingErrors.join(" | ")}`);
109652
+ }
109653
+ const suffix = details.length > 0 ? ` (${details.join(", ")})` : "";
109654
+ return new Error(`${adapterLabel} driver error: ${baseMessage}${suffix}`);
109454
109655
  }
109455
109656
  function compilePostgresSql(sql, params) {
109456
109657
  const values = [];
@@ -109485,31 +109686,12 @@ function compileSqlServerSql(sql, params) {
109485
109686
  async function runBuiltInPostgresAdapter(request) {
109486
109687
  const values = request.connection.values;
109487
109688
  const connectionString = getConnectionString(values);
109488
- const host = getConnectionValue(values, "host", "server");
109489
- const database = getConnectionValue(values, "database");
109490
- const user = getConnectionValue(values, "user");
109491
- const password = getConnectionValue(values, "password");
109492
- const client = connectionString ? new Client(connectionString) : (() => {
109493
- if (!host || !database || !user || !password) {
109494
- fail(buildMissingConnectionError("Postgres"));
109495
- }
109496
- const port = parseOptionalNumber(values.port, "Postgres port") ?? 5432;
109497
- return new Client({
109498
- host,
109499
- port,
109500
- database,
109501
- user,
109502
- password,
109503
- ssl: parseBoolean(values.ssl, false) ? { rejectUnauthorized: parseBoolean(values.rejectUnauthorized, false) } : false,
109504
- application_name: values.applicationName || "norn-sql"
109505
- });
109506
- })();
109689
+ if (!connectionString) {
109690
+ fail(buildMissingConnectionError("Postgres", request.connection.profile));
109691
+ }
109692
+ const client = new Client(connectionString);
109507
109693
  try {
109508
109694
  await client.connect();
109509
- if (values.schema) {
109510
- const safeSchema = values.schema.replace(/"/g, '""');
109511
- await client.query(`set search_path to "${safeSchema}"`);
109512
- }
109513
109695
  const compiled = compilePostgresSql(request.operation.sql, request.params || {});
109514
109696
  const result = await client.query(compiled.text, compiled.values);
109515
109697
  if (request.mode === "query") {
@@ -109529,6 +109711,8 @@ async function runBuiltInPostgresAdapter(request) {
109529
109711
  affectedRows: result.rowCount ?? 0
109530
109712
  }
109531
109713
  };
109714
+ } catch (error) {
109715
+ throw formatBuiltInDriverError("Postgres", error);
109532
109716
  } finally {
109533
109717
  await client.end().catch(() => void 0);
109534
109718
  }
@@ -109536,53 +109720,10 @@ async function runBuiltInPostgresAdapter(request) {
109536
109720
  async function runBuiltInSqlServerAdapter(request) {
109537
109721
  const values = request.connection.values;
109538
109722
  const connectionString = getConnectionString(values);
109539
- const pool = connectionString ? new mssql.ConnectionPool(connectionString) : (() => {
109540
- const server = getConnectionValue(values, "server", "host");
109541
- const database = getConnectionValue(values, "database");
109542
- const user = getConnectionValue(values, "user");
109543
- const password = getConnectionValue(values, "password");
109544
- if (!server || !database || !user || !password) {
109545
- fail(buildMissingConnectionError("SQL Server"));
109546
- }
109547
- const port = parseOptionalNumber(values.port, "SQL Server port");
109548
- const connectionTimeout = parseOptionalNumber(values.connectionTimeout, "SQL Server connectionTimeout");
109549
- const requestTimeout = parseOptionalNumber(values.requestTimeout, "SQL Server requestTimeout");
109550
- const config = {
109551
- server,
109552
- database,
109553
- user,
109554
- password,
109555
- options: {
109556
- encrypt: parseBoolean(values.encrypt, false),
109557
- trustServerCertificate: parseBoolean(values.trustServerCertificate, false)
109558
- }
109559
- };
109560
- if (port !== void 0) {
109561
- config.port = port;
109562
- }
109563
- if (values.instanceName) {
109564
- config.options = {
109565
- ...config.options,
109566
- instanceName: values.instanceName
109567
- };
109568
- }
109569
- if (values.appName) {
109570
- config.options = {
109571
- ...config.options,
109572
- appName: values.appName
109573
- };
109574
- }
109575
- if (values.domain) {
109576
- config.domain = values.domain;
109577
- }
109578
- if (connectionTimeout !== void 0) {
109579
- config.connectionTimeout = connectionTimeout;
109580
- }
109581
- if (requestTimeout !== void 0) {
109582
- config.requestTimeout = requestTimeout;
109583
- }
109584
- return new mssql.ConnectionPool(config);
109585
- })();
109723
+ if (!connectionString) {
109724
+ fail(buildMissingConnectionError("SQL Server", request.connection.profile));
109725
+ }
109726
+ const pool = new mssql.ConnectionPool(connectionString);
109586
109727
  try {
109587
109728
  await pool.connect();
109588
109729
  const compiled = compileSqlServerSql(request.operation.sql, request.params || {});
@@ -109610,10 +109751,33 @@ async function runBuiltInSqlServerAdapter(request) {
109610
109751
  affectedRows
109611
109752
  }
109612
109753
  };
109754
+ } catch (error) {
109755
+ throw formatBuiltInDriverError("SQL Server", error);
109613
109756
  } finally {
109614
109757
  await pool.close().catch(() => void 0);
109615
109758
  }
109616
109759
  }
109760
+ async function runBuiltInSqlServerIntegratedAdapter(request) {
109761
+ const values = request.connection.values;
109762
+ const connectionString = getConnectionString(values);
109763
+ if (!connectionString) {
109764
+ fail(buildMissingConnectionError("SQL Server Integrated Auth", request.connection.profile));
109765
+ }
109766
+ const compiled = compileSqlServerSql(request.operation.sql, request.params || {});
109767
+ const compiledRequest = {
109768
+ ...request,
109769
+ operation: {
109770
+ ...request.operation,
109771
+ sql: compiled.text
109772
+ },
109773
+ params: Object.fromEntries(compiled.names.map((name) => [name, request.params?.[name] ?? null]))
109774
+ };
109775
+ try {
109776
+ return await runSqlServerIntegratedViaPowerShell(compiledRequest);
109777
+ } catch (error) {
109778
+ throw formatBuiltInDriverError("SQL Server Integrated Auth", error);
109779
+ }
109780
+ }
109617
109781
  function isBuiltInSqlAdapter(adapterId) {
109618
109782
  return BUILT_IN_SQL_ADAPTER_MAP.has(adapterId.toLowerCase());
109619
109783
  }
@@ -109623,6 +109787,8 @@ async function runBuiltInSqlAdapter(adapterId, request) {
109623
109787
  return runBuiltInPostgresAdapter(request);
109624
109788
  case "sqlserver":
109625
109789
  return runBuiltInSqlServerAdapter(request);
109790
+ case "sqlserver-integrated":
109791
+ return runBuiltInSqlServerIntegratedAdapter(request);
109626
109792
  default:
109627
109793
  fail(`Built-in SQL adapter '${adapterId}' is not supported`);
109628
109794
  }
@@ -109800,7 +109966,7 @@ function resolveSqlAdapterTarget(startPath, adapterId) {
109800
109966
  }
109801
109967
 
109802
109968
  // src/sqlAdapterRunner.ts
109803
- var import_child_process2 = require("child_process");
109969
+ var import_child_process3 = require("child_process");
109804
109970
  function isRowsResult(value) {
109805
109971
  if (!value || typeof value !== "object" || Array.isArray(value)) {
109806
109972
  return false;
@@ -109840,7 +110006,7 @@ async function runExternalSqlAdapter(command, request, workingDir) {
109840
110006
  throw new Error("SQL adapter command is empty");
109841
110007
  }
109842
110008
  return new Promise((resolve11, reject) => {
109843
- const child = (0, import_child_process2.spawn)(command[0], command.slice(1), {
110009
+ const child = (0, import_child_process3.spawn)(command[0], command.slice(1), {
109844
110010
  cwd: workingDir,
109845
110011
  env: process.env,
109846
110012
  shell: false
@@ -110161,23 +110327,35 @@ function splitNamedArgumentList(argsStr) {
110161
110327
  }
110162
110328
  return parts;
110163
110329
  }
110164
- function parseNamedSqlArguments(argsStr) {
110330
+ function parseSqlArguments(argsStr) {
110165
110331
  if (!argsStr.trim()) {
110166
110332
  return { args: [] };
110167
110333
  }
110168
110334
  const parts = splitNamedArgumentList(argsStr);
110169
110335
  const args = [];
110336
+ let sawNamedArgument = false;
110170
110337
  for (const part of parts) {
110171
- const match = part.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
110172
- if (!match) {
110338
+ const trimmed = part.trim();
110339
+ if (!trimmed) {
110340
+ continue;
110341
+ }
110342
+ const match = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
110343
+ if (match) {
110344
+ sawNamedArgument = true;
110345
+ args.push({
110346
+ name: match[1],
110347
+ valueExpression: match[2].trim()
110348
+ });
110349
+ continue;
110350
+ }
110351
+ if (sawNamedArgument) {
110173
110352
  return {
110174
110353
  args: [],
110175
- error: "SQL arguments must use named syntax: run sql Name(param: value)"
110354
+ error: "Positional SQL arguments cannot follow named arguments."
110176
110355
  };
110177
110356
  }
110178
110357
  args.push({
110179
- name: match[1],
110180
- valueExpression: match[2].trim()
110358
+ valueExpression: trimmed
110181
110359
  });
110182
110360
  }
110183
110361
  return { args };
@@ -110191,7 +110369,7 @@ function parseRunSqlCommand(line2) {
110191
110369
  if (!match) {
110192
110370
  return null;
110193
110371
  }
110194
- const parsedArgs = parseNamedSqlArguments(match[3] || "");
110372
+ const parsedArgs = parseSqlArguments(match[3] || "");
110195
110373
  if (parsedArgs.error) {
110196
110374
  return {
110197
110375
  variableName: match[1],
@@ -110206,6 +110384,52 @@ function parseRunSqlCommand(line2) {
110206
110384
  args: parsedArgs.args
110207
110385
  };
110208
110386
  }
110387
+ function bindSqlArguments(parameterNames, args, runtimeVariables) {
110388
+ const resolvedParams = {};
110389
+ const boundParams = /* @__PURE__ */ new Set();
110390
+ let positionalIndex = 0;
110391
+ let sawNamedArgument = false;
110392
+ for (const arg of args) {
110393
+ if (arg.name) {
110394
+ sawNamedArgument = true;
110395
+ const declaredParam2 = parameterNames.find((param) => param.toLowerCase() === arg.name.toLowerCase());
110396
+ if (!declaredParam2) {
110397
+ return { error: `Unknown SQL parameter '${arg.name}'.` };
110398
+ }
110399
+ const declaredLower = declaredParam2.toLowerCase();
110400
+ if (boundParams.has(declaredLower)) {
110401
+ return { error: `Duplicate SQL parameter '${declaredParam2}' in run sql call.` };
110402
+ }
110403
+ const valueResult2 = evaluateSqlArgumentExpression(arg.valueExpression, runtimeVariables);
110404
+ if (valueResult2.error) {
110405
+ return { error: valueResult2.error };
110406
+ }
110407
+ resolvedParams[declaredParam2] = valueResult2.value;
110408
+ boundParams.add(declaredLower);
110409
+ continue;
110410
+ }
110411
+ if (sawNamedArgument) {
110412
+ return { error: "Positional SQL arguments cannot follow named arguments." };
110413
+ }
110414
+ if (positionalIndex >= parameterNames.length) {
110415
+ return { error: `Too many SQL arguments: expected ${parameterNames.length}.` };
110416
+ }
110417
+ const declaredParam = parameterNames[positionalIndex];
110418
+ const valueResult = evaluateSqlArgumentExpression(arg.valueExpression, runtimeVariables);
110419
+ if (valueResult.error) {
110420
+ return { error: valueResult.error };
110421
+ }
110422
+ resolvedParams[declaredParam] = valueResult.value;
110423
+ boundParams.add(declaredParam.toLowerCase());
110424
+ positionalIndex++;
110425
+ }
110426
+ for (const param of parameterNames) {
110427
+ if (!boundParams.has(param.toLowerCase())) {
110428
+ return { error: `Missing required SQL parameter '${param}'.` };
110429
+ }
110430
+ }
110431
+ return { params: resolvedParams };
110432
+ }
110209
110433
  function isVarRunSequenceCommand(line2) {
110210
110434
  const trimmed = line2.trim();
110211
110435
  if (!/^var\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*run\s+[a-zA-Z_][a-zA-Z0-9_-]*(?:\s*\([^)]*\))?(?:\s+retry\s+\d+)?(?:\s+backoff\s+\d+(?:\.\d+)?\s*(?:s|ms|seconds?|milliseconds?)?)?\s*$/i.test(trimmed)) {
@@ -111719,78 +111943,25 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
111719
111943
  duration: Date.now() - startTime
111720
111944
  };
111721
111945
  }
111722
- const providedArgs = /* @__PURE__ */ new Map();
111723
- const declaredParams = new Set(operation.parameters.map((param) => param.toLowerCase()));
111724
- for (const arg of parsed.args) {
111725
- const lowerName = arg.name.toLowerCase();
111726
- if (providedArgs.has(lowerName)) {
111727
- errors.push(`Duplicate SQL parameter '${arg.name}' in run sql call.`);
111728
- return {
111729
- name: "",
111730
- success: false,
111731
- responses,
111732
- scriptResults,
111733
- assertionResults,
111734
- steps: orderedSteps,
111735
- errors,
111736
- duration: Date.now() - startTime
111737
- };
111738
- }
111739
- if (!declaredParams.has(lowerName)) {
111740
- errors.push(`Unknown SQL parameter '${arg.name}'.`);
111741
- return {
111742
- name: "",
111743
- success: false,
111744
- responses,
111745
- scriptResults,
111746
- assertionResults,
111747
- steps: orderedSteps,
111748
- errors,
111749
- duration: Date.now() - startTime
111750
- };
111751
- }
111752
- const valueResult = evaluateSqlArgumentExpression(arg.valueExpression, runtimeVariables);
111753
- if (valueResult.error) {
111754
- errors.push(`Step ${stepIdx + 1}: ${valueResult.error}`);
111755
- return {
111756
- name: "",
111757
- success: false,
111758
- responses,
111759
- scriptResults,
111760
- assertionResults,
111761
- steps: orderedSteps,
111762
- errors,
111763
- duration: Date.now() - startTime
111764
- };
111765
- }
111766
- providedArgs.set(lowerName, valueResult.value);
111767
- }
111768
- for (const param of operation.parameters) {
111769
- if (!providedArgs.has(param.toLowerCase())) {
111770
- errors.push(`Missing required SQL parameter '${param}'.`);
111771
- return {
111772
- name: "",
111773
- success: false,
111774
- responses,
111775
- scriptResults,
111776
- assertionResults,
111777
- steps: orderedSteps,
111778
- errors,
111779
- duration: Date.now() - startTime
111780
- };
111781
- }
111946
+ const boundArgs = bindSqlArguments(operation.parameters, parsed.args, runtimeVariables);
111947
+ if ("error" in boundArgs) {
111948
+ errors.push(`Step ${stepIdx + 1}: ${boundArgs.error}`);
111949
+ return {
111950
+ name: "",
111951
+ success: false,
111952
+ responses,
111953
+ scriptResults,
111954
+ assertionResults,
111955
+ steps: orderedSteps,
111956
+ errors,
111957
+ duration: Date.now() - startTime
111958
+ };
111782
111959
  }
111783
111960
  const adapterWorkingDir = workingDir || process.cwd();
111784
111961
  try {
111785
111962
  const connection = resolveSqlConnection(executionContext?.filePath || adapterWorkingDir, operation.connectionName);
111786
111963
  const adapterTarget = resolveSqlAdapterTarget(executionContext?.filePath || adapterWorkingDir, connection.adapter);
111787
111964
  const connectionValues = getSqlConnectionValues(connection.profile, runtimeVariables);
111788
- const paramPayload = Object.fromEntries(
111789
- Array.from(providedArgs.entries()).map(([lowerName, value]) => {
111790
- const declaredName = operation.parameters.find((param) => param.toLowerCase() === lowerName) || lowerName;
111791
- return [declaredName, value];
111792
- })
111793
- );
111794
111965
  const adapterResponse = await runSqlAdapter(
111795
111966
  adapterTarget,
111796
111967
  {
@@ -111804,7 +111975,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
111804
111975
  connectionName: operation.connectionName,
111805
111976
  sourcePath: operation.sourcePath
111806
111977
  },
111807
- params: paramPayload,
111978
+ params: boundArgs.params,
111808
111979
  connection: {
111809
111980
  name: connection.alias,
111810
111981
  profile: connection.profile,
@@ -114326,7 +114497,9 @@ var ENV_FILENAME = ".nornenv";
114326
114497
  var importRegex = /^import\s+["']?(.+?)["']?\s*$/;
114327
114498
  var envRegex = /^\[env:([a-zA-Z_][a-zA-Z0-9_-]*)\]$/;
114328
114499
  var varRegex = /^var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
114500
+ var connectionStringRegex = /^connectionString\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
114329
114501
  var secretRegex = /^secret\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
114502
+ var secretConnectionStringRegex = /^secret\s+connectionString\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
114330
114503
  function parseEnvFile(content, sourceFilePath) {
114331
114504
  const lines = content.split("\n");
114332
114505
  const config = {
@@ -114367,6 +114540,27 @@ function parseEnvFile(content, sourceFilePath) {
114367
114540
  config.environments.push(currentEnv);
114368
114541
  continue;
114369
114542
  }
114543
+ const secretConnectionStringMatch = trimmed.match(secretConnectionStringRegex);
114544
+ if (secretConnectionStringMatch) {
114545
+ const profileName = secretConnectionStringMatch[1];
114546
+ const varName = `${profileName}_connectionString`;
114547
+ const varValue = secretConnectionStringMatch[2].trim();
114548
+ config.secretDeclarations.push({
114549
+ name: varName,
114550
+ value: varValue,
114551
+ envName: currentEnv?.name,
114552
+ lineNumber: i,
114553
+ filePath: sourceFilePath
114554
+ });
114555
+ config.secretNames.add(varName);
114556
+ config.secretValues.set(varName, varValue);
114557
+ if (currentEnv) {
114558
+ currentEnv.variables[varName] = varValue;
114559
+ } else {
114560
+ config.common[varName] = varValue;
114561
+ }
114562
+ continue;
114563
+ }
114370
114564
  const secretMatch = trimmed.match(secretRegex);
114371
114565
  if (secretMatch) {
114372
114566
  const varName = secretMatch[1];
@@ -114387,6 +114581,18 @@ function parseEnvFile(content, sourceFilePath) {
114387
114581
  }
114388
114582
  continue;
114389
114583
  }
114584
+ const connectionStringMatch = trimmed.match(connectionStringRegex);
114585
+ if (connectionStringMatch) {
114586
+ const profileName = connectionStringMatch[1];
114587
+ const varName = `${profileName}_connectionString`;
114588
+ const varValue = connectionStringMatch[2].trim();
114589
+ if (currentEnv) {
114590
+ currentEnv.variables[varName] = varValue;
114591
+ } else {
114592
+ config.common[varName] = varValue;
114593
+ }
114594
+ continue;
114595
+ }
114390
114596
  const varMatch = trimmed.match(varRegex);
114391
114597
  if (varMatch) {
114392
114598
  const varName = varMatch[1];
@@ -114661,6 +114867,7 @@ var import_process = require("process");
114661
114867
  var fs14 = __toESM(require("fs"));
114662
114868
  var path10 = __toESM(require("path"));
114663
114869
  var envRegex2 = /^\s*\[env:([a-zA-Z_][a-zA-Z0-9_-]*)\]\s*$/;
114870
+ var secretConnectionStringRegex2 = /^(\s*secret\s+connectionString\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
114664
114871
  var secretRegex2 = /^(\s*secret\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
114665
114872
  function splitContentLines(content) {
114666
114873
  return content.split(/\r?\n/);
@@ -114686,11 +114893,13 @@ function extractSecretLines(content, filePath) {
114686
114893
  currentEnv = envMatch[1];
114687
114894
  continue;
114688
114895
  }
114896
+ const secretConnectionStringMatch = line2.match(secretConnectionStringRegex2);
114689
114897
  const secretMatch = line2.match(secretRegex2);
114690
- if (!secretMatch) {
114898
+ if (!secretConnectionStringMatch && !secretMatch) {
114691
114899
  continue;
114692
114900
  }
114693
- const value = secretMatch[4].trim();
114901
+ const secretName = secretConnectionStringMatch ? `${secretConnectionStringMatch[2]}_connectionString` : secretMatch[2];
114902
+ const value = (secretConnectionStringMatch?.[4] ?? secretMatch[4]).trim();
114694
114903
  const encrypted = isEncryptedSecretValue(value);
114695
114904
  let kid;
114696
114905
  if (encrypted) {
@@ -114703,7 +114912,7 @@ function extractSecretLines(content, filePath) {
114703
114912
  filePath,
114704
114913
  lineNumber: i,
114705
114914
  envName: currentEnv,
114706
- name: secretMatch[2],
114915
+ name: secretName,
114707
114916
  value,
114708
114917
  encrypted,
114709
114918
  kid
@@ -114717,11 +114926,16 @@ function updateSecretLineValue(content, lineNumber, newValue) {
114717
114926
  throw new Error(`Line ${lineNumber + 1} is out of range.`);
114718
114927
  }
114719
114928
  const line2 = lines[lineNumber];
114929
+ const secretConnectionStringMatch = line2.match(secretConnectionStringRegex2);
114720
114930
  const secretMatch = line2.match(secretRegex2);
114721
- if (!secretMatch) {
114931
+ if (!secretConnectionStringMatch && !secretMatch) {
114722
114932
  throw new Error(`Line ${lineNumber + 1} is not a secret declaration.`);
114723
114933
  }
114724
- lines[lineNumber] = `${secretMatch[1]}${secretMatch[2]}${secretMatch[3]}${newValue}`;
114934
+ if (secretConnectionStringMatch) {
114935
+ lines[lineNumber] = `${secretConnectionStringMatch[1]}${secretConnectionStringMatch[2]}${secretConnectionStringMatch[3]}${newValue}`;
114936
+ } else {
114937
+ lines[lineNumber] = `${secretMatch[1]}${secretMatch[2]}${secretMatch[3]}${newValue}`;
114938
+ }
114725
114939
  return lines.join(detectEol(content));
114726
114940
  }
114727
114941
  function findSecretLine(content, variableName, envName) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn - REST Client",
4
4
  "description": "A powerful REST client for making HTTP requests with sequences, variables, scripts, and cookie support",
5
- "version": "1.8.0",
5
+ "version": "1.10.0",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -362,6 +362,12 @@
362
362
  "settings": {
363
363
  "foreground": "#85EA2C"
364
364
  }
365
+ },
366
+ {
367
+ "scope": "support.type.connection.nornenv",
368
+ "settings": {
369
+ "foreground": "#d7ba7d"
370
+ }
365
371
  }
366
372
  ]
367
373
  }