Skip to content

Commit 5a28f9f

Browse files
authored
feat: enable offline mode (#576)
* feat: enable offline mode * fix: include all schema * fix: review comments
1 parent cdbbd0b commit 5a28f9f

File tree

32 files changed

+11173
-108
lines changed

32 files changed

+11173
-108
lines changed

packages/context/api.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export {
22
getContext,
3+
isContext,
34
getCDNBaseUrl,
45
initializeManifestData,
56
initializeUI5YamlData,

packages/context/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@
2020
"@sap-ux/edmx-parser": "0.5.13",
2121
"@sap-ux/project-access": "1.1.1",
2222
"@ui5-language-assistant/logic-utils": "4.0.5",
23+
"@ui5-language-assistant/settings": "4.0.5",
2324
"fs-extra": "10.1.0",
2425
"globby": "11.1.0",
25-
"https-proxy-agent": "5.0.1",
2626
"js-yaml": "4.1.0",
2727
"lodash": "4.17.21",
28-
"node-fetch": "3.2.10",
29-
"proxy-from-env": "1.1.0",
3028
"semver": "7.3.7",
3129
"vscode-languageserver": "8.0.2",
3230
"vscode-uri": "2.1.2"
@@ -41,7 +39,8 @@
4139
"@ui5-language-assistant/test-framework": "4.0.5",
4240
"@ui5-language-assistant/test-utils": "4.0.5",
4341
"rimraf": "3.0.2",
44-
"tmp-promise": "3.0.2"
42+
"tmp-promise": "3.0.2",
43+
"proxyquire": "2.1.3"
4544
},
4645
"scripts": {
4746
"ci": "npm-run-all clean compile lint coverage",

packages/context/src/api.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,33 @@ export {
3434
export async function getContext(
3535
documentPath: string,
3636
modelCachePath?: string
37-
): Promise<Context> {
38-
const manifestDetails = await getManifestDetails(documentPath);
39-
const yamlDetails = await getYamlDetails(documentPath);
40-
const ui5Model = await getSemanticModel(
41-
modelCachePath,
42-
yamlDetails.framework,
43-
manifestDetails.minUI5Version
44-
);
45-
const services = await getServices(documentPath);
46-
const customViewId = await getCustomViewId(documentPath);
47-
return { manifestDetails, yamlDetails, ui5Model, services, customViewId };
37+
): Promise<Context | Error> {
38+
try {
39+
const manifestDetails = await getManifestDetails(documentPath);
40+
const yamlDetails = await getYamlDetails(documentPath);
41+
const ui5Model = await getSemanticModel(
42+
modelCachePath,
43+
yamlDetails.framework,
44+
manifestDetails.minUI5Version
45+
);
46+
const services = await getServices(documentPath);
47+
const customViewId = await getCustomViewId(documentPath);
48+
return { manifestDetails, yamlDetails, ui5Model, services, customViewId };
49+
} catch (error) {
50+
return error as Error;
51+
}
4852
}
4953

54+
/**
55+
* Checks if data is context or an error
56+
*/
57+
export const isContext = (
58+
data: Context | (Error & { code?: string })
59+
): data is Context => {
60+
if ((data as Context).ui5Model) {
61+
return true;
62+
}
63+
return false;
64+
};
65+
5066
export const DEFAULT_I18N_NAMESPACE = "translation";

packages/context/src/types.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
} from "@ui5-language-assistant/semantic-model-types";
55
import { ConvertedMetadata } from "@sap-ux/vocabularies-types";
66
import type { Manifest } from "@sap-ux/project-access";
7+
import { FetchResponse } from "@ui5-language-assistant/logic-utils";
78

89
export const DEFAULT_UI5_FRAMEWORK = "SAPUI5";
910
export const DEFAULT_UI5_VERSION = "1.71.49";
@@ -111,9 +112,4 @@ export type CAPProjectKind = "Java" | "NodeJS";
111112
export type ProjectKind = CAPProjectKind | "UI5";
112113
export type Project = UI5Project | CAPProject;
113114
export type ProjectType = typeof UI5_PROJECT_TYPE | typeof CAP_PROJECT_TYPE;
114-
export type FetchResponse = {
115-
ok: boolean;
116-
status: number;
117-
json: () => Promise<unknown>;
118-
};
119115
export type Fetcher = (url: string) => Promise<FetchResponse>;

packages/context/src/ui5-model.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
TypeNameFix,
1414
} from "@ui5-language-assistant/semantic-model";
1515
import { Fetcher } from "./types";
16-
import fetch from "./fetch";
16+
import { fetch } from "@ui5-language-assistant/logic-utils";
1717
import {
1818
getLibraryAPIJsonUrl,
1919
getLogger,
@@ -124,7 +124,11 @@ async function createSemanticModelWithFetcher(
124124
// If the file doesn't exist in the cache (or we couldn't read it), fetch it from the network
125125
if (apiJson === undefined) {
126126
getLogger().info("No cache found for UI5 lib", { libName });
127-
const url = getLibraryAPIJsonUrl(framework, version as string, libName);
127+
const url = await getLibraryAPIJsonUrl(
128+
framework,
129+
version as string,
130+
libName
131+
);
128132
const response = await fetcher(url);
129133
if (response.ok) {
130134
apiJson = await response.json();
@@ -256,7 +260,7 @@ async function getVersionInfo(
256260
}
257261
let versionInfo = await readFromCache(cacheFilePath);
258262
if (versionInfo === undefined) {
259-
const url = getVersionInfoUrl(framework, version);
263+
const url = await getVersionInfoUrl(framework, version);
260264
const response = await fetcher(url);
261265
if (response.ok) {
262266
versionInfo = await response.json();

packages/context/src/utils/ui5.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
import { UI5Framework } from "@ui5-language-assistant/semantic-model-types";
22
import { UI5_FRAMEWORK_CDN_BASE_URL } from "../types";
3+
import { getLogger } from "../utils";
4+
import { tryFetch, getLocalUrl } from "@ui5-language-assistant/logic-utils";
35

4-
export function getCDNBaseUrl(
6+
/**
7+
* Get CDN URL for UI5 framework. If a URL for `SAPUI5 Web Server` is maintained in settings, it appends UI5 version if available and tries to check if this URL is responding and return it,
8+
* if it fails, it appends UI5 version if available to a public URL and return it
9+
*
10+
* @param framework UI5 framework e.g OpenUI5" | "SAPUI5"
11+
* @param version min ui5 version specified in manifest.json file
12+
*/
13+
export async function getCDNBaseUrl(
514
framework: UI5Framework,
615
version: string | undefined
7-
): string {
16+
): Promise<string> {
17+
const localUrl = getLocalUrl(version);
18+
if (localUrl) {
19+
const response = await tryFetch(localUrl);
20+
if (response) {
21+
return localUrl;
22+
}
23+
24+
getLogger().info("Failed to load. Will try over internet.", {
25+
localUrl,
26+
});
27+
}
28+
829
let url = UI5_FRAMEWORK_CDN_BASE_URL[framework];
930
if (version) {
1031
url += `${version}/`;
@@ -16,20 +37,20 @@ export function getVersionJsonUrl(framework: UI5Framework): string {
1637
return `${UI5_FRAMEWORK_CDN_BASE_URL[framework]}version.json`;
1738
}
1839

19-
export function getVersionInfoUrl(
40+
export async function getVersionInfoUrl(
2041
framework: UI5Framework,
2142
version: string
22-
): string {
23-
const cdnBaseUrl = getCDNBaseUrl(framework, version);
43+
): Promise<string> {
44+
const cdnBaseUrl = await getCDNBaseUrl(framework, version);
2445
return `${cdnBaseUrl}resources/sap-ui-version.json`;
2546
}
2647

27-
export function getLibraryAPIJsonUrl(
48+
export async function getLibraryAPIJsonUrl(
2849
framework: UI5Framework,
2950
version: string,
3051
libName: string
31-
): string {
32-
const cdnBaseUrl = getCDNBaseUrl(framework, version);
52+
): Promise<string> {
53+
const cdnBaseUrl = await getCDNBaseUrl(framework, version);
3354
const baseUrl = `${cdnBaseUrl}test-resources/`;
3455
const suffix = "/designtime/api.json";
3556
return baseUrl + libName.replace(/\./g, "/") + suffix;

packages/context/test/api-spec.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import * as ui5Yaml from "../src/ui5-yaml";
55
import * as ui5Model from "../src/ui5-model";
66
import * as services from "../src/services";
77
import { UI5SemanticModel } from "@ui5-language-assistant/semantic-model-types";
8-
import { getContext } from "../src/api";
8+
import { getContext, isContext } from "../src/api";
9+
import type { Context } from "../src/types";
910

1011
describe("context", () => {
1112
afterEach(() => {
@@ -47,5 +48,40 @@ describe("context", () => {
4748
"ui5Model"
4849
);
4950
});
51+
it("throw connection error", async () => {
52+
const getManifestDetailsStub = stub(
53+
manifest,
54+
"getManifestDetails"
55+
).resolves({
56+
mainServicePath: "/",
57+
customViews: {},
58+
flexEnabled: false,
59+
minUI5Version: undefined,
60+
});
61+
const getYamlDetailsStub = stub(ui5Yaml, "getYamlDetails").resolves({
62+
framework: "OpenUI5",
63+
version: undefined,
64+
});
65+
const getSemanticModelStub = stub(ui5Model, "getSemanticModel").throws({
66+
code: "ENOTFOUND",
67+
});
68+
const result = await getContext("path/to/xml/file");
69+
expect(getManifestDetailsStub).to.have.been.called;
70+
expect(getYamlDetailsStub).to.have.been.called;
71+
expect(getSemanticModelStub).to.have.been.called;
72+
expect(result).to.have.keys("code");
73+
});
74+
});
75+
context("isContext", () => {
76+
it("check true", () => {
77+
const result = isContext({ ui5Model: {} } as Context);
78+
expect(result).to.be.true;
79+
});
80+
it("check false", () => {
81+
const result = isContext({ code: "ENOTFOUND" } as Error & {
82+
code?: string;
83+
});
84+
expect(result).to.be.false;
85+
});
5086
});
5187
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { restore, fake } from "sinon";
2+
import { join } from "path";
3+
import proxyquire from "proxyquire";
4+
import { expect } from "chai";
5+
import { getCDNBaseUrl } from "../../src/utils/ui5";
6+
7+
describe("ui5", () => {
8+
afterEach(() => {
9+
restore();
10+
});
11+
context("getCDNBaseUrl", () => {
12+
it("get CDN without local url [with version]", async () => {
13+
const result = await getCDNBaseUrl("SAPUI5", "1.111.0");
14+
expect(result).to.be.equal("https://ui5.sap.com/1.111.0/");
15+
});
16+
it("get CDN without local url [without version]", async () => {
17+
const result = await getCDNBaseUrl("SAPUI5", undefined);
18+
expect(result).to.be.equal("https://ui5.sap.com/");
19+
});
20+
it("get CDN with local url", async () => {
21+
const filePath = join(__dirname, "..", "..", "src", "utils", "ui5");
22+
const fakeGetLocalUrl = fake.returns("http://localhost:3000/1.111.0/");
23+
const fakeTryFetch = fake.resolves({ ok: true });
24+
const ui5Module = proxyquire
25+
.noPreserveCache()
26+
.noCallThru()
27+
.load(filePath, {
28+
"@ui5-language-assistant/logic-utils": {
29+
getLocalUrl: fakeGetLocalUrl,
30+
tryFetch: fakeTryFetch,
31+
},
32+
});
33+
const result = await ui5Module.getCDNBaseUrl("SAPUI5", "1.111.0");
34+
expect(fakeGetLocalUrl).to.have.been.called;
35+
expect(fakeTryFetch).to.have.been.called;
36+
expect(result).to.be.equal("http://localhost:3000/1.111.0/");
37+
});
38+
it("get CDN with local url [fetch not responding => fall back to public]", async () => {
39+
const filePath = join(__dirname, "..", "..", "src", "utils", "ui5");
40+
const fakeGetLocalUrl = fake.returns("http://localhost:3000/1.111.0/");
41+
const fakeTryFetch = fake.resolves(undefined);
42+
const ui5Module = proxyquire
43+
.noPreserveCache()
44+
.noCallThru()
45+
.load(filePath, {
46+
"@ui5-language-assistant/logic-utils": {
47+
getLocalUrl: fakeGetLocalUrl,
48+
tryFetch: fakeTryFetch,
49+
},
50+
});
51+
const result = await ui5Module.getCDNBaseUrl("SAPUI5", "1.111.0");
52+
expect(fakeGetLocalUrl).to.have.been.called;
53+
expect(fakeTryFetch).to.have.been.called;
54+
expect(result).to.be.equal("https://ui5.sap.com/1.111.0/");
55+
});
56+
});
57+
});

packages/fe/test/services/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { AnnotationIssue, getCompletionItems } from "../../src/api";
22
import { CompletionItem } from "vscode-languageserver-types";
33
import { TestFramework } from "@ui5-language-assistant/test-framework";
4-
import { getContext, Context } from "@ui5-language-assistant/context";
4+
import { getContext } from "@ui5-language-assistant/context";
5+
import type { Context } from "@ui5-language-assistant/context";
56
import { validateXMLView } from "@ui5-language-assistant/xml-views-validation";
67

78
import { CURSOR_ANCHOR } from "@ui5-language-assistant/test-framework";
@@ -52,7 +53,7 @@ export const getViewCompletionProvider = (
5253
content,
5354
offset
5455
);
55-
const context = await getContext(documentPath);
56+
const context = (await getContext(documentPath)) as Context;
5657

5758
result = getCompletionItems({
5859
ast,
@@ -102,7 +103,7 @@ export const getViewValidator = (
102103
insertAfter: "<content>",
103104
});
104105
const { ast } = await framework.readFile(viewFilePathSegments);
105-
const context = await getContext(documentPath);
106+
const context = (await getContext(documentPath)) as Context;
106107
result = validateXMLView({
107108
validators: {
108109
attribute: [validator],

0 commit comments

Comments
 (0)