pi-apexlang 0.2.0 → 0.2.2
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/README.md
CHANGED
|
@@ -90,6 +90,58 @@ code** requires an explicit target mode:
|
|
|
90
90
|
Without GUI support, the tool always stops after checking and reports import
|
|
91
91
|
as a follow-up.
|
|
92
92
|
|
|
93
|
+
When refreshing a full APEXlang export, run one SQLcl export with explicit
|
|
94
|
+
replacement semantics:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
apex export -applicationid <id> -exptype APEXLANG -split -dir <absolute-parent-directory> -force
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`-dir` names the parent directory; SQLcl creates the application-alias folder
|
|
101
|
+
below it. The `-force` flag removes and recreates that export folder, so a
|
|
102
|
+
repeat export produces one current snapshot instead of collision copies such
|
|
103
|
+
as `application_1.apx` and `p00005_1.apx`. Because this replaces the complete
|
|
104
|
+
local export, preserve or import any local-only work before refreshing it.
|
|
105
|
+
|
|
106
|
+
## SQLcl and ORDS compatibility advisory
|
|
107
|
+
|
|
108
|
+
Oracle documents minimum APEXlang prerequisites, but does not publish an exact
|
|
109
|
+
ORDS-to-SQLcl patch certification matrix. The extension therefore keeps this
|
|
110
|
+
mapping advisory and records whether a row is Oracle-confirmed or an extension
|
|
111
|
+
diagnostic recommendation:
|
|
112
|
+
|
|
113
|
+
| Server environment | SQLcl guidance | Basis |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| ORDS before 26.1.1 or APEX before 26.1 | APEXlang is unsupported; upgrade the server components first. | Oracle-confirmed |
|
|
116
|
+
| APEX 26.1.x with ORDS 26.1.1 through 26.1.x | Oracle minimum: SQLcl 26.1. Diagnostic baseline: `26.1.2.132.1334`. | Oracle minimum plus extension advisory |
|
|
117
|
+
| APEX 26.1.x with ORDS 26.2.x | Oracle does not mandate SQLcl 26.2. On mass findings, compare with `26.1.2.132.1334`. | Extension advisory |
|
|
118
|
+
| Other or newer APEX, MMD, or ORDS lines | Verify current Oracle release documentation and use compiler metadata supporting the app `mmdVersion`. | Verify current docs |
|
|
119
|
+
|
|
120
|
+
The complete machine-readable table, source URLs, thresholds, and the direct
|
|
121
|
+
diagnostic-build download are in
|
|
122
|
+
[`extensions/apexlang/ords-sqlcl-compatibility.json`](extensions/apexlang/ords-sqlcl-compatibility.json).
|
|
123
|
+
The exact build is a known diagnostic baseline, not a universal pin, and SQLcl
|
|
124
|
+
26.2 is not categorically blocked.
|
|
125
|
+
|
|
126
|
+
When a validation result contains at least 50 structured findings, the
|
|
127
|
+
extension appends the table and recommends checking:
|
|
128
|
+
|
|
129
|
+
- local `sql -version`;
|
|
130
|
+
- the app's `.apex/apexlang.json` `mmdVersion`;
|
|
131
|
+
- the server APEX release and ORDS version with its administrator.
|
|
132
|
+
|
|
133
|
+
For local and compiler-truth text reports, the mass-error signal additionally
|
|
134
|
+
requires at least five distinct `.apx` files, which avoids treating one noisy
|
|
135
|
+
file as an environment-wide mismatch. An explicitly unsupported MMD version
|
|
136
|
+
also produces the compatibility advice. The advice never changes validation
|
|
137
|
+
status, relaxes an import gate, or claims that the application is valid.
|
|
138
|
+
|
|
139
|
+
[ORDS 26.1.1 introduced APEXlang support](https://www.oracle.com/tools/ords/ords-relnotes-26.1.1.html),
|
|
140
|
+
[APEX 26.1 requires ORDS 26.1.1 or later](https://docs.oracle.com/en/database/oracle/apex/26.1/htmrn/changed-behavior.html),
|
|
141
|
+
and Oracle's [SQLcl prerequisites](https://docs.oracle.com/en/database/oracle/sql-developer-command-line/26.1/sqcug/prerequisites-apexlang.html)
|
|
142
|
+
set the SQLcl minimum at 26.1. SQLcl
|
|
143
|
+
[26.1.2 added full APEXlang support](https://www.oracle.com/tools/sqlcl/sqlcl-relnotes-26.1.2.html).
|
|
144
|
+
|
|
93
145
|
All app paths must remain inside the active Pi workspace. The adapter rejects
|
|
94
146
|
traversal, app-tree symlinks, and multiply linked files on mutating vocabulary
|
|
95
147
|
fixes; verifies live workspace input against `deployments/default.json`; and
|
|
@@ -103,8 +155,9 @@ Offline routing, templates, generation, and most local checks need Node.js
|
|
|
103
155
|
|
|
104
156
|
Oracle documents these requirements for live APEXlang work:
|
|
105
157
|
|
|
106
|
-
- Oracle APEX with APEXlang support
|
|
107
|
-
-
|
|
158
|
+
- Oracle APEX 26.1 with APEXlang support.
|
|
159
|
+
- ORDS 26.1.1 or newer.
|
|
160
|
+
- SQLcl 26.1 or newer, selected with the compatibility advisory above.
|
|
108
161
|
- Java 17 or Java 21 for SQLcl.
|
|
109
162
|
- A saved SQLcl connection name and its corresponding APEX workspace name.
|
|
110
163
|
- A local APEX app or authoritative schema, model, API, or table metadata.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import compatibility from "./ords-sqlcl-compatibility.json" with { type: "json" };
|
|
4
|
+
import type { ApexlangRunResult } from "../lib/apexlang-cli.mjs";
|
|
5
|
+
|
|
6
|
+
type JsonRecord = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export type ValidationCompatibilitySignal = {
|
|
9
|
+
findingCount?: number;
|
|
10
|
+
distinctFileCount?: number;
|
|
11
|
+
source: "structured-result" | "local-reports" | "compiler-truth-output" | "unsupported-mmd";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const ORDS_SQLCL_COMPATIBILITY = compatibility;
|
|
15
|
+
export const ORDS_SQLCL_COMPATIBILITY_TABLE = compatibility.rows;
|
|
16
|
+
export const MASS_VALIDATION_FINDING_THRESHOLD = compatibility.massValidation.minimumFindings;
|
|
17
|
+
export const MASS_VALIDATION_DISTINCT_FILE_THRESHOLD =
|
|
18
|
+
compatibility.massValidation.minimumDistinctFilesForAppWideReports;
|
|
19
|
+
export const ORDS_SQLCL_COMPATIBILITY_GUIDELINE =
|
|
20
|
+
`When a validation action reports ${MASS_VALIDATION_FINDING_THRESHOLD}+ findings, recommend checking the server APEX/ORDS release, the app mmdVersion, and local \`sql -version\` against the extension's advisory compatibility table. For APEX 26.1, SQLcl 26.1 is Oracle's minimum and 26.1.2.132.1334 is the diagnostic baseline; do not hard-block other SQLcl versions or weaken validation/import gates.`;
|
|
21
|
+
|
|
22
|
+
const VALIDATION_ACTIONS = new Set([
|
|
23
|
+
"local_validate",
|
|
24
|
+
"compiler_truth_audit",
|
|
25
|
+
"runtime_validate"
|
|
26
|
+
]);
|
|
27
|
+
const LOCAL_VALIDATION_REPORT_NAMES = [
|
|
28
|
+
"apexlang-dsl-report.json",
|
|
29
|
+
"apexlang-validations-report.json",
|
|
30
|
+
"apexlang-vocab-report.json"
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
function asRecord(value: unknown): JsonRecord | undefined {
|
|
34
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
35
|
+
? value as JsonRecord
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseJsonRecord(value: string): JsonRecord | undefined {
|
|
40
|
+
try {
|
|
41
|
+
return asRecord(JSON.parse(value));
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function safeCount(value: unknown): number | undefined {
|
|
48
|
+
return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function maximumStructuredFindingCount(payload: JsonRecord): number | undefined {
|
|
52
|
+
const compatibilityFallback = asRecord(payload.compatibility_fallback);
|
|
53
|
+
const candidates = [
|
|
54
|
+
safeCount(payload.unresolved_count),
|
|
55
|
+
safeCount(payload.problem_count),
|
|
56
|
+
Array.isArray(payload.issues) ? payload.issues.length : undefined,
|
|
57
|
+
Array.isArray(payload.problems) ? payload.problems.length : undefined,
|
|
58
|
+
safeCount(compatibilityFallback?.original_unresolved_count)
|
|
59
|
+
].filter((value): value is number => value !== undefined);
|
|
60
|
+
return candidates.length > 0 ? Math.max(...candidates) : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function diagnosticIdentity(issue: unknown, reportName: string, index: number): {
|
|
64
|
+
key: string;
|
|
65
|
+
file?: string;
|
|
66
|
+
} {
|
|
67
|
+
if (typeof issue === "string") {
|
|
68
|
+
const raw = issue.trim();
|
|
69
|
+
const match = raw.match(/^\s*-?\s*(.+?\.apx):(\d+)(?::\d+)?:\s+[A-Z][A-Z0-9_]+\b/);
|
|
70
|
+
return { key: raw || `${reportName}:${index}`, ...(match?.[1] ? { file: match[1] } : {}) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const record = asRecord(issue);
|
|
74
|
+
if (!record) return { key: `${reportName}:${index}:${String(issue)}` };
|
|
75
|
+
const file = typeof record.file === "string" ? record.file.trim() : "";
|
|
76
|
+
const raw = typeof record.raw === "string" ? record.raw.trim() : "";
|
|
77
|
+
const key = raw || [
|
|
78
|
+
file,
|
|
79
|
+
String(record.line ?? ""),
|
|
80
|
+
String(record.column ?? ""),
|
|
81
|
+
String(record.rule ?? record.code ?? ""),
|
|
82
|
+
String(record.message ?? "")
|
|
83
|
+
].join(":");
|
|
84
|
+
return {
|
|
85
|
+
key: key || `${reportName}:${index}:${JSON.stringify(record)}`,
|
|
86
|
+
...(file ? { file } : {})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function readJsonReport(path: string): Promise<JsonRecord | undefined> {
|
|
91
|
+
try {
|
|
92
|
+
return parseJsonRecord(await readFile(path, "utf8"));
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function clearLocalValidationCompatibilityReports(outputRoot: string): Promise<void> {
|
|
99
|
+
await Promise.all(
|
|
100
|
+
LOCAL_VALIDATION_REPORT_NAMES.map((name) => rm(join(outputRoot, "logs", name), { force: true }))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function detectLocalReportSignal(outputRoot: string): Promise<ValidationCompatibilitySignal | undefined> {
|
|
105
|
+
const reports = await Promise.all(
|
|
106
|
+
LOCAL_VALIDATION_REPORT_NAMES.map(async (name) => ({
|
|
107
|
+
name,
|
|
108
|
+
payload: await readJsonReport(join(outputRoot, "logs", name))
|
|
109
|
+
}))
|
|
110
|
+
);
|
|
111
|
+
const vocabularyReport = reports.find(({ name }) => name === "apexlang-vocab-report.json")?.payload;
|
|
112
|
+
if (vocabularyReport?.blocking_reason === "UNSUPPORTED_MMD_VERSION") {
|
|
113
|
+
return { source: "unsupported-mmd" };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const findingKeys = new Set<string>();
|
|
117
|
+
const files = new Set<string>();
|
|
118
|
+
for (const { name, payload } of reports) {
|
|
119
|
+
for (const collection of [payload?.issues, payload?.unresolved]) {
|
|
120
|
+
if (!Array.isArray(collection)) continue;
|
|
121
|
+
collection.forEach((issue, index) => {
|
|
122
|
+
const identity = diagnosticIdentity(issue, name, index);
|
|
123
|
+
findingKeys.add(identity.key);
|
|
124
|
+
if (identity.file) files.add(identity.file);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
findingKeys.size >= MASS_VALIDATION_FINDING_THRESHOLD &&
|
|
130
|
+
files.size >= MASS_VALIDATION_DISTINCT_FILE_THRESHOLD
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
source: "local-reports",
|
|
134
|
+
findingCount: findingKeys.size,
|
|
135
|
+
distinctFileCount: files.size
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function detectCompilerTruthOutputSignal(output: string): ValidationCompatibilitySignal | undefined {
|
|
142
|
+
const findings = new Set<string>();
|
|
143
|
+
const files = new Set<string>();
|
|
144
|
+
for (const line of output.split(/\r?\n/)) {
|
|
145
|
+
const match = line.match(/^\s*-\s+(.+?\.apx):(\d+)(?::\d+)?:\s+([A-Z][A-Z0-9_]+)\b\s*(.*)$/);
|
|
146
|
+
if (!match?.[1]) continue;
|
|
147
|
+
findings.add(`${match[1]}:${match[2]}:${match[3]}:${match[4] ?? ""}`);
|
|
148
|
+
files.add(match[1]);
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
findings.size >= MASS_VALIDATION_FINDING_THRESHOLD &&
|
|
152
|
+
files.size >= MASS_VALIDATION_DISTINCT_FILE_THRESHOLD
|
|
153
|
+
) {
|
|
154
|
+
return {
|
|
155
|
+
source: "compiler-truth-output",
|
|
156
|
+
findingCount: findings.size,
|
|
157
|
+
distinctFileCount: files.size
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function detectValidationCompatibilitySignal(
|
|
164
|
+
result: ApexlangRunResult
|
|
165
|
+
): Promise<ValidationCompatibilitySignal | undefined> {
|
|
166
|
+
if (!VALIDATION_ACTIONS.has(result.action)) return undefined;
|
|
167
|
+
|
|
168
|
+
const payload = parseJsonRecord(result.stdout);
|
|
169
|
+
if (payload) {
|
|
170
|
+
const findingCount = maximumStructuredFindingCount(payload);
|
|
171
|
+
if (findingCount !== undefined && findingCount >= MASS_VALIDATION_FINDING_THRESHOLD) {
|
|
172
|
+
return { source: "structured-result", findingCount };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (result.action === "local_validate") {
|
|
176
|
+
return detectLocalReportSignal(result.outputRoot);
|
|
177
|
+
}
|
|
178
|
+
if (result.action === "compiler_truth_audit") {
|
|
179
|
+
return detectCompilerTruthOutputSignal([result.stdout, result.stderr].filter(Boolean).join("\n"));
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function tableCell(value: string): string {
|
|
185
|
+
return value.replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function renderOrdsSqlclCompatibilityTable(): string {
|
|
189
|
+
const rows = ORDS_SQLCL_COMPATIBILITY_TABLE.map((row) => {
|
|
190
|
+
const guidance = row.diagnosticSqlclBuild && row.diagnosticSqlclDownloadUrl
|
|
191
|
+
? `${row.sqlclGuidance} [Download ${row.diagnosticSqlclBuild}](${row.diagnosticSqlclDownloadUrl})`
|
|
192
|
+
: row.sqlclGuidance;
|
|
193
|
+
return `| ${tableCell(row.environment)} | ${tableCell(guidance)} | ${tableCell(row.basis)} |`;
|
|
194
|
+
});
|
|
195
|
+
return [
|
|
196
|
+
"| Server environment | SQLcl guidance | Basis |",
|
|
197
|
+
"| --- | --- | --- |",
|
|
198
|
+
...rows
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function formatValidationCompatibilityAdvisory(
|
|
203
|
+
signal: ValidationCompatibilitySignal
|
|
204
|
+
): string {
|
|
205
|
+
const findingSummary = signal.findingCount === undefined
|
|
206
|
+
? "The application declares an unsupported APEXlang compiler metadata version."
|
|
207
|
+
: `Large-scale validation output detected (${signal.findingCount} findings${
|
|
208
|
+
signal.distinctFileCount === undefined ? "" : ` across ${signal.distinctFileCount} files`
|
|
209
|
+
}).`;
|
|
210
|
+
return [
|
|
211
|
+
"### SQLcl/ORDS compatibility advisory",
|
|
212
|
+
"",
|
|
213
|
+
`${findingSummary} Before changing many application files, check local \`sql -version\`, the app's \`.apex/apexlang.json\` \`mmdVersion\`, and the server APEX and ORDS versions with its administrator. A version/metadata mismatch can create broad diagnostic noise.`,
|
|
214
|
+
"",
|
|
215
|
+
renderOrdsSqlclCompatibilityTable(),
|
|
216
|
+
"",
|
|
217
|
+
compatibility.caveat,
|
|
218
|
+
"This advice does not turn a failed validation into a pass and does not authorize import."
|
|
219
|
+
].join("\n");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function buildValidationCompatibilityAdvisory(
|
|
223
|
+
result: ApexlangRunResult
|
|
224
|
+
): Promise<string> {
|
|
225
|
+
const signal = await detectValidationCompatibilitySignal(result);
|
|
226
|
+
return signal ? formatValidationCompatibilityAdvisory(signal) : "";
|
|
227
|
+
}
|
|
@@ -13,6 +13,18 @@ import {
|
|
|
13
13
|
type ApexlangInput,
|
|
14
14
|
type ApexlangRunResult
|
|
15
15
|
} from "../lib/apexlang-cli.mjs";
|
|
16
|
+
import {
|
|
17
|
+
MASS_VALIDATION_DISTINCT_FILE_THRESHOLD,
|
|
18
|
+
MASS_VALIDATION_FINDING_THRESHOLD,
|
|
19
|
+
ORDS_SQLCL_COMPATIBILITY,
|
|
20
|
+
ORDS_SQLCL_COMPATIBILITY_GUIDELINE,
|
|
21
|
+
ORDS_SQLCL_COMPATIBILITY_TABLE,
|
|
22
|
+
buildValidationCompatibilityAdvisory,
|
|
23
|
+
clearLocalValidationCompatibilityReports,
|
|
24
|
+
detectValidationCompatibilitySignal,
|
|
25
|
+
formatValidationCompatibilityAdvisory,
|
|
26
|
+
renderOrdsSqlclCompatibilityTable
|
|
27
|
+
} from "./compatibility.ts";
|
|
16
28
|
|
|
17
29
|
const MAX_TOOL_OUTPUT = 80_000;
|
|
18
30
|
const COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -20,6 +32,8 @@ const CHECK_ONLY_CHOICE = "Check APEXlang code (recommended) — stop after the
|
|
|
20
32
|
const IMPORT_CHOICE = "Check and import APEXlang code — revalidate and import in one SQLcl session";
|
|
21
33
|
const UPDATE_EXISTING_CHOICE = "Update an existing app — require one proven remote target";
|
|
22
34
|
const CREATE_NEW_CHOICE = "Create a new app — require proof that the alias is absent";
|
|
35
|
+
const EXPORT_OVERWRITE_GUIDELINE =
|
|
36
|
+
"For a full APEXlang export refresh, run exactly one SQLcl `apex export` command with `-force` and `-dir` set to the absolute parent directory. Never export without `-force` into an existing destination: SQLcl can collision-suffix every artifact as `*_1.apx` instead of replacing the snapshot.";
|
|
23
37
|
|
|
24
38
|
function trimOutput(value: string, outputRoot?: string): string {
|
|
25
39
|
if (value.length <= MAX_TOOL_OUTPUT) return value;
|
|
@@ -75,14 +89,20 @@ function createNewTargetProved(result: ApexlangRunResult): boolean {
|
|
|
75
89
|
payload?.failure_class === "create_new_confirmation_required";
|
|
76
90
|
}
|
|
77
91
|
|
|
78
|
-
function processOutput(result: ApexlangRunResult): string {
|
|
92
|
+
function processOutput(result: ApexlangRunResult, compatibilityAdvisory = ""): string {
|
|
79
93
|
const streams = result.ok ? [result.stdout, result.stderr] : [result.stderr, result.stdout];
|
|
80
|
-
|
|
94
|
+
const output = trimOutput(streams.filter(Boolean).join("\n").trim(), result.outputRoot);
|
|
95
|
+
return [output, compatibilityAdvisory].filter(Boolean).join("\n\n");
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
function failureOutput(
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
function failureOutput(
|
|
99
|
+
result: ApexlangRunResult,
|
|
100
|
+
fallback: string,
|
|
101
|
+
compatibilityAdvisory = ""
|
|
102
|
+
): string {
|
|
103
|
+
const streams = result.ok ? [result.stdout, result.stderr] : [result.stderr, result.stdout];
|
|
104
|
+
const output = trimOutput(streams.filter(Boolean).join("\n").trim(), result.outputRoot) || fallback;
|
|
105
|
+
return `${[output, compatibilityAdvisory].filter(Boolean).join("\n\n")}\n\nAPEXlang reports: ${result.outputRoot}`;
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
type ApexlangDependencies = {
|
|
@@ -110,6 +130,8 @@ export function createApexlangTool(overrides: Partial<ApexlangDependencies> = {}
|
|
|
110
130
|
promptGuidelines: [
|
|
111
131
|
"Load the apexlang skill before using the apexlang tool and follow its routing and Missing Inputs rules.",
|
|
112
132
|
"Use apexlang workspace_probe before app-scoped APEXlang work.",
|
|
133
|
+
EXPORT_OVERWRITE_GUIDELINE,
|
|
134
|
+
ORDS_SQLCL_COMPATIBILITY_GUIDELINE,
|
|
113
135
|
"Use apexlang runtime_validate only after the user provides both db_connection_name and the matching APEX workspace_name; import requires the tool's separate post-check GUI choice."
|
|
114
136
|
],
|
|
115
137
|
executionMode: "sequential",
|
|
@@ -191,11 +213,25 @@ export function createApexlangTool(overrides: Partial<ApexlangDependencies> = {}
|
|
|
191
213
|
...(signal ? { signal } : {}),
|
|
192
214
|
timeoutMs: COMMAND_TIMEOUT_MS
|
|
193
215
|
};
|
|
216
|
+
if (input.action === "local_validate") {
|
|
217
|
+
await clearLocalValidationCompatibilityReports(outputRoot);
|
|
218
|
+
}
|
|
194
219
|
const result = await dependencies.run(input, runOptions);
|
|
195
|
-
const
|
|
220
|
+
const compatibilityAdvisory = await buildValidationCompatibilityAdvisory(result);
|
|
221
|
+
if (compatibilityAdvisory) {
|
|
222
|
+
onUpdate?.({
|
|
223
|
+
content: [{ type: "text", text: compatibilityAdvisory }],
|
|
224
|
+
details: { action: input.action, compatibilityAdvisory: true }
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const output = processOutput(result, compatibilityAdvisory);
|
|
196
228
|
if (!result.ok) {
|
|
197
229
|
throw new Error(
|
|
198
|
-
failureOutput(
|
|
230
|
+
failureOutput(
|
|
231
|
+
result,
|
|
232
|
+
`APEXlang ${input.action} failed with exit code ${result.code}.`,
|
|
233
|
+
compatibilityAdvisory
|
|
234
|
+
)
|
|
199
235
|
);
|
|
200
236
|
}
|
|
201
237
|
|
|
@@ -204,7 +240,8 @@ export function createApexlangTool(overrides: Partial<ApexlangDependencies> = {}
|
|
|
204
240
|
throw new Error(
|
|
205
241
|
failureOutput(
|
|
206
242
|
result,
|
|
207
|
-
"APEXlang runtime validation did not produce authoritative live pass evidence."
|
|
243
|
+
"APEXlang runtime validation did not produce authoritative live pass evidence.",
|
|
244
|
+
compatibilityAdvisory
|
|
208
245
|
)
|
|
209
246
|
);
|
|
210
247
|
}
|
|
@@ -296,10 +333,12 @@ export function createApexlangTool(overrides: Partial<ApexlangDependencies> = {}
|
|
|
296
333
|
expectedAppDigest
|
|
297
334
|
});
|
|
298
335
|
if (!createNewTargetProved(proofResult)) {
|
|
336
|
+
const proofCompatibilityAdvisory = await buildValidationCompatibilityAdvisory(proofResult);
|
|
299
337
|
throw new Error(
|
|
300
338
|
failureOutput(
|
|
301
339
|
proofResult,
|
|
302
|
-
"Oracle did not prove that the create-new target is absent from the selected workspace."
|
|
340
|
+
"Oracle did not prove that the create-new target is absent from the selected workspace.",
|
|
341
|
+
proofCompatibilityAdvisory
|
|
303
342
|
)
|
|
304
343
|
);
|
|
305
344
|
}
|
|
@@ -340,12 +379,14 @@ export function createApexlangTool(overrides: Partial<ApexlangDependencies> = {}
|
|
|
340
379
|
createNewConfirmed,
|
|
341
380
|
expectedAppDigest
|
|
342
381
|
});
|
|
343
|
-
const
|
|
382
|
+
const importCompatibilityAdvisory = await buildValidationCompatibilityAdvisory(importResult);
|
|
383
|
+
const importOutput = processOutput(importResult, importCompatibilityAdvisory);
|
|
344
384
|
if (!liveImportPassed(importResult)) {
|
|
345
385
|
throw new Error(
|
|
346
386
|
failureOutput(
|
|
347
387
|
importResult,
|
|
348
|
-
"APEXlang validate-and-import did not produce authoritative import pass evidence."
|
|
388
|
+
"APEXlang validate-and-import did not produce authoritative import pass evidence.",
|
|
389
|
+
importCompatibilityAdvisory
|
|
349
390
|
)
|
|
350
391
|
);
|
|
351
392
|
}
|
|
@@ -425,11 +466,22 @@ export {
|
|
|
425
466
|
CHECK_ONLY_CHOICE,
|
|
426
467
|
IMPORT_CHOICE,
|
|
427
468
|
CREATE_NEW_CHOICE,
|
|
469
|
+
EXPORT_OVERWRITE_GUIDELINE,
|
|
470
|
+
MASS_VALIDATION_DISTINCT_FILE_THRESHOLD,
|
|
471
|
+
MASS_VALIDATION_FINDING_THRESHOLD,
|
|
472
|
+
ORDS_SQLCL_COMPATIBILITY,
|
|
473
|
+
ORDS_SQLCL_COMPATIBILITY_GUIDELINE,
|
|
474
|
+
ORDS_SQLCL_COMPATIBILITY_TABLE,
|
|
428
475
|
UPDATE_EXISTING_CHOICE,
|
|
429
476
|
actionWritesProject,
|
|
477
|
+
buildValidationCompatibilityAdvisory,
|
|
478
|
+
clearLocalValidationCompatibilityReports,
|
|
430
479
|
confirmationMessage,
|
|
431
480
|
createNewTargetProved,
|
|
481
|
+
detectValidationCompatibilitySignal,
|
|
482
|
+
formatValidationCompatibilityAdvisory,
|
|
432
483
|
liveImportPassed,
|
|
433
|
-
liveValidationPassed
|
|
484
|
+
liveValidationPassed,
|
|
485
|
+
renderOrdsSqlclCompatibilityTable
|
|
434
486
|
};
|
|
435
487
|
export type { ApexlangAction };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"policy": "advisory-only",
|
|
4
|
+
"lastReviewed": "2026-07-19",
|
|
5
|
+
"massValidation": {
|
|
6
|
+
"minimumFindings": 50,
|
|
7
|
+
"minimumDistinctFilesForAppWideReports": 5
|
|
8
|
+
},
|
|
9
|
+
"caveat": "Oracle publishes minimum APEXlang prerequisites, not an ORDS-to-SQLcl patch certification matrix. Exact SQLcl builds in this table are diagnostic baselines and never validation gates.",
|
|
10
|
+
"rows": [
|
|
11
|
+
{
|
|
12
|
+
"environment": "ORDS before 26.1.1 or APEX before 26.1",
|
|
13
|
+
"matchMode": "any",
|
|
14
|
+
"ordsRange": "<26.1.1",
|
|
15
|
+
"apexRange": "<26.1",
|
|
16
|
+
"sqlclGuidance": "APEXlang is unsupported; upgrade the server components before changing SQLcl.",
|
|
17
|
+
"diagnosticSqlclBuild": "",
|
|
18
|
+
"diagnosticSqlclDownloadUrl": "",
|
|
19
|
+
"basis": "oracle-confirmed",
|
|
20
|
+
"sourceUrls": [
|
|
21
|
+
"https://www.oracle.com/tools/ords/ords-relnotes-26.1.1.html",
|
|
22
|
+
"https://docs.oracle.com/en/database/oracle/apex/26.1/htmrn/changed-behavior.html"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"environment": "APEX 26.1.x with ORDS 26.1.1 through 26.1.x",
|
|
27
|
+
"matchMode": "all",
|
|
28
|
+
"ordsRange": ">=26.1.1 <26.2.0",
|
|
29
|
+
"apexRange": "26.1.x",
|
|
30
|
+
"sqlclGuidance": "Oracle minimum: SQLcl 26.1. Diagnostic baseline: 26.1.2.132.1334.",
|
|
31
|
+
"diagnosticSqlclBuild": "26.1.2.132.1334",
|
|
32
|
+
"diagnosticSqlclDownloadUrl": "https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-26.1.2.132.1334.zip",
|
|
33
|
+
"basis": "oracle-minimum-plus-extension-advisory",
|
|
34
|
+
"sourceUrls": [
|
|
35
|
+
"https://docs.oracle.com/en/database/oracle/sql-developer-command-line/26.1/sqcug/prerequisites-apexlang.html",
|
|
36
|
+
"https://www.oracle.com/tools/sqlcl/sqlcl-relnotes-26.1.2.html"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"environment": "APEX 26.1.x with ORDS 26.2.x",
|
|
41
|
+
"matchMode": "all",
|
|
42
|
+
"ordsRange": ">=26.2.0 <26.3.0",
|
|
43
|
+
"apexRange": "26.1.x",
|
|
44
|
+
"sqlclGuidance": "Oracle does not mandate SQLcl 26.2. On mass findings, compare with diagnostic baseline 26.1.2.132.1334.",
|
|
45
|
+
"diagnosticSqlclBuild": "26.1.2.132.1334",
|
|
46
|
+
"diagnosticSqlclDownloadUrl": "https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-26.1.2.132.1334.zip",
|
|
47
|
+
"basis": "extension-advisory",
|
|
48
|
+
"sourceUrls": [
|
|
49
|
+
"https://docs.oracle.com/en/database/oracle/sql-developer-command-line/26.1/sqcug/prerequisites-apexlang.html",
|
|
50
|
+
"https://www.oracle.com/tools/sqlcl/sqlcl-downloads.html"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"environment": "Other or newer APEX, MMD, or ORDS release lines",
|
|
55
|
+
"matchMode": "fallback",
|
|
56
|
+
"ordsRange": "other",
|
|
57
|
+
"apexRange": "other",
|
|
58
|
+
"sqlclGuidance": "Verify current Oracle release documentation and use SQLcl compiler metadata that supports the app mmdVersion.",
|
|
59
|
+
"diagnosticSqlclBuild": "",
|
|
60
|
+
"diagnosticSqlclDownloadUrl": "",
|
|
61
|
+
"basis": "verify-current-docs",
|
|
62
|
+
"sourceUrls": [
|
|
63
|
+
"https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/using-sqlcl-apexlang.html"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|