vscode-apollo 2.2.0 → 2.3.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 (38) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/.github/workflows/build-prs.yml +55 -0
  3. package/.github/workflows/release.yml +1 -1
  4. package/.gitleaks.toml +10 -3
  5. package/.vscodeignore +0 -1
  6. package/CHANGELOG.md +26 -0
  7. package/README.md +68 -52
  8. package/package.json +31 -3
  9. package/renovate.json +5 -5
  10. package/sampleWorkspace/httpSchema/apollo.config.ts +2 -0
  11. package/sampleWorkspace/httpSchema/self-signed.crt +22 -0
  12. package/sampleWorkspace/httpSchema/self-signed.key +28 -0
  13. package/sampleWorkspace/localSchemaArray/apollo.config.json +9 -0
  14. package/sampleWorkspace/rover/apollo.config.yaml +2 -0
  15. package/sampleWorkspace/rover/supergraph.yaml +0 -0
  16. package/schemas/apollo.config.schema.json +184 -0
  17. package/src/__e2e__/mockServer.js +37 -11
  18. package/src/__e2e__/mocks.js +11 -7
  19. package/src/__e2e__/runTests.js +8 -6
  20. package/src/build.js +53 -1
  21. package/src/language-server/__e2e__/studioGraph.e2e.ts +4 -3
  22. package/src/language-server/config/__tests__/loadConfig.ts +35 -2
  23. package/src/language-server/config/cache-busting-resolver.js +65 -0
  24. package/src/language-server/config/cache-busting-resolver.types.ts +45 -0
  25. package/src/language-server/config/config.ts +136 -60
  26. package/src/language-server/config/loadConfig.ts +27 -6
  27. package/src/language-server/config/loadTsConfig.ts +74 -38
  28. package/src/language-server/project/base.ts +8 -8
  29. package/src/language-server/project/rover/DocumentSynchronization.ts +44 -22
  30. package/src/language-server/project/rover/project.ts +6 -0
  31. package/src/language-server/providers/schema/endpoint.ts +15 -8
  32. package/src/language-server/server.ts +8 -7
  33. package/src/language-server/workspace.ts +2 -5
  34. package/src/languageServerClient.ts +3 -1
  35. package/syntaxes/graphql.json +2 -2
  36. package/sampleWorkspace/localSchemaArray/apollo.config.js +0 -12
  37. package/sampleWorkspace/rover/apollo.config.js +0 -3
  38. /package/sampleWorkspace/localSchema/{apollo.config.js → apollo.config.ts} +0 -0
@@ -0,0 +1,184 @@
1
+ {
2
+ "allOf": [
3
+ {
4
+ "$ref": "#/definitions/baseConfig"
5
+ },
6
+ {
7
+ "type": "object",
8
+ "properties": {
9
+ "client": {
10
+ "$ref": "#/definitions/clientConfig"
11
+ }
12
+ },
13
+ "required": [
14
+ "client"
15
+ ]
16
+ }
17
+ ],
18
+ "definitions": {
19
+ "clientConfig": {
20
+ "type": "object",
21
+ "properties": {
22
+ "service": {
23
+ "anyOf": [
24
+ {
25
+ "type": "string",
26
+ "description": "The name of the Apollo Studio graph to use. Alternatively pass in an object to configure a local schema."
27
+ },
28
+ {
29
+ "type": "object",
30
+ "properties": {
31
+ "name": {
32
+ "type": "string",
33
+ "description": "The name your project will be referred to by the Apollo GraphQL extension."
34
+ },
35
+ "url": {
36
+ "type": "string",
37
+ "description": "URL of a GraphQL to use for the GraphQL Schema for this project. Needs introspection enabled."
38
+ },
39
+ "headers": {
40
+ "type": "object",
41
+ "additionalProperties": {
42
+ "type": "string"
43
+ },
44
+ "default": {},
45
+ "description": "Additional headers to send to the server."
46
+ },
47
+ "skipSSLValidation": {
48
+ "type": "boolean",
49
+ "default": false,
50
+ "description": "Skip SSL validation. May be required for self-signed certificates."
51
+ }
52
+ },
53
+ "required": [
54
+ "url"
55
+ ],
56
+ "additionalProperties": false,
57
+ "description": "Configuration for using a local schema from a URL."
58
+ },
59
+ {
60
+ "type": "object",
61
+ "properties": {
62
+ "name": {
63
+ "type": "string",
64
+ "description": "The name your project will be referred to by the Apollo GraphQL extension."
65
+ },
66
+ "localSchemaFile": {
67
+ "anyOf": [
68
+ {
69
+ "type": "string",
70
+ "description": "Path to a local schema file to use as GraphQL Schema for this project. Can be a string or an array of strings to merge multiple partial schemas into one."
71
+ },
72
+ {
73
+ "type": "array",
74
+ "items": {
75
+ "type": "string"
76
+ },
77
+ "description": "Path to a local schema file to use as GraphQL Schema for this project. Can be a string or an array of strings to merge multiple partial schemas into one."
78
+ }
79
+ ],
80
+ "description": "Path to a local schema file to use as GraphQL Schema for this project. Can be a string or an array of strings to merge multiple partial schemas into one."
81
+ }
82
+ },
83
+ "required": [
84
+ "localSchemaFile"
85
+ ],
86
+ "additionalProperties": false,
87
+ "description": "Configuration for using a local schema from a file."
88
+ }
89
+ ],
90
+ "description": "A string to refer to a graph in Apollo Studio, or an object for a local schema."
91
+ },
92
+ "validationRules": {
93
+ "anyOf": [
94
+ {
95
+ "type": "array",
96
+ "description": "Additional validation rules to check for. To use this feature, please use a configuration file format that allows passing JavaScript objects."
97
+ }
98
+ ],
99
+ "description": "Additional validation rules to check for. To use this feature, please use a configuration file format that allows passing JavaScript objects."
100
+ },
101
+ "includes": {
102
+ "type": "array",
103
+ "items": {
104
+ "type": "string"
105
+ },
106
+ "description": "An array of glob patterns this project should be active on. The Apollo GraphQL extension will only support IntelliSense-like features in files listed here."
107
+ },
108
+ "excludes": {
109
+ "type": "array",
110
+ "items": {
111
+ "type": "string"
112
+ },
113
+ "default": [
114
+ "**/node_modules",
115
+ "**/__tests__"
116
+ ],
117
+ "description": "Files to exclude from this project. The Apollo GraphQL extension will not provide IntelliSense-like features in these files."
118
+ },
119
+ "tagName": {
120
+ "type": "string",
121
+ "default": "gql",
122
+ "description": "The name of the template literal tag or function used in JavaScript files to declare GraphQL Documents."
123
+ },
124
+ "clientOnlyDirectives": {
125
+ "description": "This option is no longer supported, please remove it from your configuration file."
126
+ },
127
+ "clientSchemaDirectives": {
128
+ "description": "This option is no longer supported, please remove it from your configuration file."
129
+ },
130
+ "statsWindow": {
131
+ "description": "This option is no longer supported, please remove it from your configuration file."
132
+ },
133
+ "name": {
134
+ "description": "This option is no longer supported, please remove it from your configuration file."
135
+ },
136
+ "referenceId": {
137
+ "description": "This option is no longer supported, please remove it from your configuration file."
138
+ },
139
+ "version": {
140
+ "description": "This option is no longer supported, please remove it from your configuration file."
141
+ }
142
+ },
143
+ "required": [
144
+ "service"
145
+ ],
146
+ "additionalProperties": false,
147
+ "description": "Configuration for a Client project."
148
+ },
149
+ "engineConfig": {
150
+ "type": "object",
151
+ "properties": {
152
+ "endpoint": {
153
+ "type": "string",
154
+ "default": "https://graphql.api.apollographql.com/api/graphql",
155
+ "description": "The URL of the Apollo Studio API."
156
+ },
157
+ "apiKey": {
158
+ "type": "string",
159
+ "description": "The API key to use for Apollo Studio. If possible, use a `.env` file or `.env.local` file instead to store secrets like this."
160
+ }
161
+ },
162
+ "additionalProperties": false,
163
+ "description": "Network configuration for Apollo Studio API."
164
+ },
165
+ "baseConfig": {
166
+ "type": "object",
167
+ "properties": {
168
+ "engine": {
169
+ "$ref": "#/definitions/engineConfig",
170
+ "default": {},
171
+ "description": "Network configuration for Apollo Studio API."
172
+ },
173
+ "client": {
174
+ "description": "Configuration for a Client project."
175
+ },
176
+ "service": {
177
+ "description": "This option is no longer supported, please remove it from your configuration file."
178
+ }
179
+ },
180
+ "additionalProperties": false
181
+ }
182
+ },
183
+ "$schema": "http://json-schema.org/draft-07/schema#"
184
+ }
@@ -1,19 +1,43 @@
1
1
  // @ts-check
2
- const http = require("http");
3
2
  const {
4
3
  parseRequestParams,
5
4
  createHandler,
6
5
  } = require("graphql-http/lib/use/http");
7
6
  const { buildSchema } = require("graphql");
8
7
  const { Trie } = require("@wry/trie");
8
+ const { readFileSync } = require("fs");
9
+ const { join } = require("path");
9
10
 
10
- function runMockServer(
11
+ async function runMockServer(
11
12
  /** @type {number} */ port,
12
- onStart = (/** @type {number} */ port) => {},
13
+ useSelfSignedCert = false,
14
+ onStart = (/** @type {string} */ baseUri) => {},
13
15
  ) {
14
16
  const mocks = new Trie(false);
15
17
 
16
- const server = http.createServer(async (req, res) => {
18
+ /**
19
+ *
20
+ * @param {import('node:http').RequestListener} listener
21
+ * @returns
22
+ */
23
+ function createServer(listener) {
24
+ if (useSelfSignedCert) {
25
+ return require("node:https").createServer(
26
+ {
27
+ key: readFileSync(
28
+ join(__dirname, "../../sampleWorkspace/httpSchema/self-signed.key"),
29
+ ),
30
+ cert: readFileSync(
31
+ join(__dirname, "../../sampleWorkspace/httpSchema/self-signed.crt"),
32
+ ),
33
+ },
34
+ listener,
35
+ );
36
+ }
37
+ return require("node:http").createServer(listener);
38
+ }
39
+
40
+ const server = createServer(async (req, res) => {
17
41
  if (req.url === "/apollo") {
18
42
  if (req.method === "POST") {
19
43
  await handleApolloPost(req, res);
@@ -33,8 +57,9 @@ function runMockServer(
33
57
 
34
58
  console.log("Starting server...");
35
59
  server.listen(port);
36
- onStart(port);
37
- console.log(`Server ready at: http://localhost:${port}`);
60
+ const baseUri = `${useSelfSignedCert ? "https" : "http"}://localhost:${port}`;
61
+ await onStart(baseUri);
62
+ console.log(`Server ready at: ${baseUri}`);
38
63
  return {
39
64
  [Symbol.dispose]() {
40
65
  console.log("Closing server...");
@@ -45,8 +70,8 @@ function runMockServer(
45
70
 
46
71
  /**
47
72
  * Mock GraphQL Endpoint Handler
48
- * @param {http.IncomingMessage} req
49
- * @param {http.ServerResponse} res
73
+ * @param {import('node:http').IncomingMessage} req
74
+ * @param {import('node:http').ServerResponse} res
50
75
  */
51
76
  async function handleApolloPost(req, res) {
52
77
  const { operationName, variables } =
@@ -79,8 +104,8 @@ function runMockServer(
79
104
 
80
105
  /**
81
106
  * Handler to accept new GraphQL Mocks
82
- * @param {http.IncomingMessage} req
83
- * @param {http.ServerResponse} res
107
+ * @param {import('node:http').IncomingMessage} req
108
+ * @param {import('node:http').ServerResponse} res
84
109
  */
85
110
  async function handleApolloPut(req, res) {
86
111
  const body = await new Promise((resolve) => {
@@ -111,7 +136,8 @@ const schemaHandler = createHandler({
111
136
  });
112
137
 
113
138
  if (require.main === module) {
114
- runMockServer(7096, require("./mocks.js").loadDefaultMocks);
139
+ runMockServer(7096, false, require("./mocks.js").loadDefaultMocks);
140
+ runMockServer(7097, true, require("./mocks.js").loadDefaultMocks);
115
141
  }
116
142
 
117
143
  module.exports.runMockServer = runMockServer;
@@ -1,26 +1,30 @@
1
1
  // @ts-check
2
-
3
- async function loadDefaultMocks(/** @type {number} */ port) {
4
- await sendMock(port, FrontendUrlRoot);
5
- await sendMock(port, SchemaTagsAndFieldStats);
6
- await sendMock(port, GetSchemaByTag);
2
+ async function loadDefaultMocks(/** @type {string} */ baseUri) {
3
+ await sendMock(baseUri, FrontendUrlRoot);
4
+ await sendMock(baseUri, SchemaTagsAndFieldStats);
5
+ await sendMock(baseUri, GetSchemaByTag);
7
6
  }
8
7
 
9
8
  function sendMock(
10
- /** @type {number} */ port,
9
+ /** @type {string} */ baseUri,
11
10
  /** @type { { operationName: string, variables: Record<string, string>, response: unknown }} */ {
12
11
  operationName,
13
12
  variables,
14
13
  response,
15
14
  },
16
15
  ) {
17
- return fetch(`http://localhost:${port}/apollo`, {
16
+ return require("undici").fetch(`${baseUri}/apollo`, {
18
17
  method: "PUT",
19
18
  body: JSON.stringify({
20
19
  operationName,
21
20
  variables,
22
21
  response,
23
22
  }),
23
+ dispatcher: new (require("undici").Agent)({
24
+ connect: {
25
+ rejectUnauthorized: false,
26
+ },
27
+ }),
24
28
  });
25
29
  }
26
30
 
@@ -5,7 +5,7 @@ const { runMockServer } = require("./mockServer.js");
5
5
  const { loadDefaultMocks } = require("./mocks.js");
6
6
 
7
7
  async function main() {
8
- let disposable;
8
+ const disposables = /**{@type Disposable[]}*/ [];
9
9
  try {
10
10
  // The folder containing the Extension Manifest package.json
11
11
  // Passed to `--extensionDevelopmentPath`
@@ -18,8 +18,12 @@ async function main() {
18
18
  const TEST_PORT = 7096;
19
19
  process.env.APOLLO_ENGINE_ENDPOINT = "http://localhost:7096/apollo";
20
20
  process.env.MOCK_SERVER_PORT = String(TEST_PORT);
21
- disposable = runMockServer(TEST_PORT);
22
- await loadDefaultMocks(TEST_PORT);
21
+ disposables.push(
22
+ ...(await Promise.all([
23
+ runMockServer(TEST_PORT, false, loadDefaultMocks),
24
+ runMockServer(TEST_PORT + 1, true, loadDefaultMocks),
25
+ ])),
26
+ );
23
27
  // Download VS Code, unzip it and run the integration test
24
28
  const exitCode = await runTests({
25
29
  extensionDevelopmentPath,
@@ -35,9 +39,7 @@ async function main() {
35
39
  console.error("Failed to run tests");
36
40
  process.exit(1);
37
41
  } finally {
38
- if (disposable) {
39
- disposable[Symbol.dispose]();
40
- }
42
+ disposables.forEach((d) => d[Symbol.dispose]());
41
43
  }
42
44
  }
43
45
 
package/src/build.js CHANGED
@@ -1,11 +1,19 @@
1
1
  const esbuild = require("esbuild");
2
+ const { zodToJsonSchema } = require("zod-to-json-schema");
3
+ const { writeFileSync } = require("fs");
4
+ const importFresh = require("import-fresh");
2
5
 
3
6
  const production = process.argv.includes("--production");
4
7
  const watch = process.argv.includes("--watch");
5
8
 
6
9
  async function main() {
7
10
  const ctx = await esbuild.context({
8
- entryPoints: ["src/extension.ts", "src/language-server/server.ts"],
11
+ entryPoints: [
12
+ "src/extension.ts",
13
+ "src/language-server/server.ts",
14
+ "src/language-server/config/config.ts",
15
+ "src/language-server/config/cache-busting-resolver.js",
16
+ ],
9
17
  bundle: true,
10
18
  format: "cjs",
11
19
  minify: production,
@@ -19,6 +27,8 @@ async function main() {
19
27
  plugins: [
20
28
  /* add to the end of plugins array */
21
29
  esbuildProblemMatcherPlugin,
30
+ buildJsonSchemaPlugin,
31
+ resolvePlugin,
22
32
  ],
23
33
  });
24
34
  if (watch) {
@@ -51,6 +61,48 @@ const esbuildProblemMatcherPlugin = {
51
61
  },
52
62
  };
53
63
 
64
+ const buildJsonSchemaPlugin = {
65
+ name: "build-json-schema",
66
+ setup(build) {
67
+ build.onEnd(() => {
68
+ const {
69
+ configSchema,
70
+ clientConfig,
71
+ // roverConfig,
72
+ engineConfig,
73
+ baseConfig,
74
+ // @ts-ignore
75
+ } = importFresh("../lib/language-server/config/config.js");
76
+
77
+ const jsonSchema = zodToJsonSchema(configSchema, {
78
+ errorMessages: true,
79
+ definitions: {
80
+ clientConfig,
81
+ //roverConfig,
82
+ engineConfig,
83
+ baseConfig,
84
+ },
85
+ });
86
+ writeFileSync(
87
+ "./schemas/apollo.config.schema.json",
88
+ JSON.stringify(jsonSchema, null, 2),
89
+ );
90
+ });
91
+ },
92
+ };
93
+
94
+ const resolvePlugin = {
95
+ name: "resolve",
96
+ setup(build) {
97
+ build.onResolve(
98
+ { filter: /^jsonc-parser$/ },
99
+ async ({ path, ...options }) => {
100
+ return build.resolve("jsonc-parser/lib/esm/main.js", options);
101
+ },
102
+ );
103
+ },
104
+ };
105
+
54
106
  main().catch((e) => {
55
107
  console.error(e);
56
108
  process.exit(1);
@@ -37,9 +37,10 @@ Get detailed profile information about the current user (including the current u
37
37
  });
38
38
 
39
39
  test("wrong token", async () => {
40
+ const baseUri = `http://localhost:${mockPort}`;
40
41
  try {
41
- await mocks.sendMock(mockPort, mocks.GetSchemaByTag_WRONG_TOKEN);
42
- await mocks.sendMock(mockPort, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);
42
+ await mocks.sendMock(baseUri, mocks.GetSchemaByTag_WRONG_TOKEN);
43
+ await mocks.sendMock(baseUri, mocks.SchemaTagsAndFieldStats_WRONG_TOKEN);
43
44
 
44
45
  const ext = getExtension();
45
46
  ext.outputChannel.clear();
@@ -59,7 +60,7 @@ Invalid credentials provided
59
60
  at new ApolloError`.trim(),
60
61
  );
61
62
  } finally {
62
- await mocks.loadDefaultMocks(mockPort);
63
+ await mocks.loadDefaultMocks(baseUri);
63
64
  await reloadService();
64
65
  }
65
66
  });
@@ -174,7 +174,10 @@ Object {
174
174
  expect(config?.client?.service).toEqual("hello");
175
175
  });
176
176
 
177
- it("loads config from a cjs file", async () => {
177
+ // we skip these tests because ts-jest transpiles every `import` down to a `require` call,
178
+ // which messes up all the importing anyways.
179
+ // we have to rely on our E2E tests to ensure that config files resolve correctly
180
+ it.skip("loads config from a cjs file", async () => {
178
181
  writeFilesToDir(dir, {
179
182
  "apollo.config.cjs": `module.exports = {"client": {"service": "hello"} }`,
180
183
  });
@@ -182,13 +185,43 @@ Object {
182
185
  expect(config?.client?.service).toEqual("hello");
183
186
  });
184
187
 
185
- it("loads config from a mjs file", async () => {
188
+ it.skip("loads config from a mjs file", async () => {
186
189
  writeFilesToDir(dir, {
187
190
  "apollo.config.mjs": `export default {"client": {"service": "hello"} }`,
188
191
  });
189
192
  const config = await loadConfig({ configPath: dirPath });
190
193
  expect(config?.client?.service).toEqual("hello");
191
194
  });
195
+
196
+ it("loads config from a yml file", async () => {
197
+ writeFilesToDir(dir, {
198
+ "apollo.config.yml": `
199
+ client:
200
+ service: hello
201
+ `,
202
+ });
203
+ const config = await loadConfig({ configPath: dirPath });
204
+ expect(config?.client?.service).toEqual("hello");
205
+ });
206
+
207
+ it("loads config from a yaml file", async () => {
208
+ writeFilesToDir(dir, {
209
+ "apollo.config.yaml": `
210
+ client:
211
+ service: hello
212
+ `,
213
+ });
214
+ const config = await loadConfig({ configPath: dirPath });
215
+ expect(config?.client?.service).toEqual("hello");
216
+ });
217
+
218
+ it("loads config from a json file", async () => {
219
+ writeFilesToDir(dir, {
220
+ "apollo.config.json": `{"client": /* testing jsonc */ {"service": "hello"} }`,
221
+ });
222
+ const config = await loadConfig({ configPath: dirPath });
223
+ expect(config?.client?.service).toEqual("hello");
224
+ });
192
225
  });
193
226
 
194
227
  describe("errors", () => {
@@ -0,0 +1,65 @@
1
+ // @ts-check
2
+ const { pathToFileURL } = require("node:url");
3
+
4
+ /** @import { ResolveContext, ResolutionResult, LoadResult, ImportContext } from "./cache-busting-resolver.types" */
5
+
6
+ /**
7
+ * @param {string} specifier
8
+ * @returns {string}
9
+ */
10
+ function bustFileName(specifier) {
11
+ const url = pathToFileURL(specifier);
12
+ url.pathname = url.pathname + "." + Date.now() + ".js";
13
+ return url.toString();
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param {string} specifier
19
+ * @param {ResolveContext} context
20
+ * @param {(specifier: string,context: ResolveContext) => Promise<ResolutionResult>} nextResolve
21
+ * @returns {Promise<ResolutionResult>}
22
+ */
23
+ async function resolve(specifier, context, nextResolve) {
24
+ if (context.importAttributes.as !== "cachebust") {
25
+ return nextResolve(specifier, context);
26
+ }
27
+ if (context.importAttributes.format) {
28
+ // no need to resolve at all, we have all necessary information
29
+ return {
30
+ url: bustFileName(specifier),
31
+ format: context.importAttributes.format,
32
+ importAttributes: context.importAttributes,
33
+ shortCircuit: true,
34
+ };
35
+ }
36
+ const result = await nextResolve(specifier, context);
37
+ return {
38
+ ...result,
39
+ url: bustFileName(result.url),
40
+ importAttributes: context.importAttributes,
41
+ };
42
+ }
43
+
44
+ /**
45
+ *
46
+ * @param {string} url
47
+ * @param {ImportContext} context
48
+ * @param {(url: string, context: ImportContext) => Promise<LoadResult>} nextLoad
49
+ * @returns {Promise<LoadResult>}
50
+ */
51
+ async function load(url, context, nextLoad) {
52
+ if (context.importAttributes.as !== "cachebust") {
53
+ return nextLoad(url, context);
54
+ }
55
+ return {
56
+ format: context.format || "module",
57
+ shortCircuit: true,
58
+ source: context.importAttributes.contents,
59
+ };
60
+ }
61
+
62
+ module.exports = {
63
+ resolve,
64
+ load,
65
+ };
@@ -0,0 +1,45 @@
1
+ import { pathToFileURL } from "node:url";
2
+
3
+ export type ImportAttributes =
4
+ | {
5
+ as: "cachebust";
6
+ contents: string;
7
+ format?: Format;
8
+ }
9
+ | { as?: undefined };
10
+
11
+ type Format =
12
+ | "builtin"
13
+ | "commonjs"
14
+ | "json"
15
+ | "module"
16
+ | "wasm"
17
+ | null
18
+ | undefined;
19
+
20
+ export interface ResolveContext {
21
+ conditions: string[];
22
+ importAttributes: ImportAttributes;
23
+ parentURL?: string;
24
+ }
25
+
26
+ export interface ImportContext {
27
+ conditions: string[];
28
+ importAttributes: ImportAttributes;
29
+ format: Format;
30
+ }
31
+
32
+ export interface ResolutionResult {
33
+ format: Format;
34
+ importAttributes?: ImportAttributes;
35
+ shortCircuit?: boolean;
36
+ url: string;
37
+ }
38
+
39
+ export interface LoadResult {
40
+ format: Format;
41
+ shortCircuit?: boolean;
42
+ source: string;
43
+ }
44
+
45
+ export {};