@commercetools/cli-topic-integration-layer 0.0.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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/commands/integration-layer/config/get.d.ts +9 -0
- package/dist/commands/integration-layer/config/get.js +22 -0
- package/dist/commands/integration-layer/config/get.js.map +1 -0
- package/dist/commands/integration-layer/config/list.d.ts +6 -0
- package/dist/commands/integration-layer/config/list.js +20 -0
- package/dist/commands/integration-layer/config/list.js.map +1 -0
- package/dist/commands/integration-layer/config/set.d.ts +13 -0
- package/dist/commands/integration-layer/config/set.js +29 -0
- package/dist/commands/integration-layer/config/set.js.map +1 -0
- package/dist/commands/integration-layer/config/unset.d.ts +9 -0
- package/dist/commands/integration-layer/config/unset.js +18 -0
- package/dist/commands/integration-layer/config/unset.js.map +1 -0
- package/dist/commands/integration-layer/extension/build.d.ts +12 -0
- package/dist/commands/integration-layer/extension/build.js +40 -0
- package/dist/commands/integration-layer/extension/build.js.map +1 -0
- package/dist/commands/integration-layer/extension/delete.d.ts +9 -0
- package/dist/commands/integration-layer/extension/delete.js +39 -0
- package/dist/commands/integration-layer/extension/delete.js.map +1 -0
- package/dist/commands/integration-layer/extension/invoke.d.ts +13 -0
- package/dist/commands/integration-layer/extension/invoke.js +89 -0
- package/dist/commands/integration-layer/extension/invoke.js.map +1 -0
- package/dist/commands/integration-layer/extension/push.d.ts +28 -0
- package/dist/commands/integration-layer/extension/push.js +217 -0
- package/dist/commands/integration-layer/extension/push.js.map +1 -0
- package/dist/commands/integration-layer/extension/serve.d.ts +37 -0
- package/dist/commands/integration-layer/extension/serve.js +528 -0
- package/dist/commands/integration-layer/extension/serve.js.map +1 -0
- package/dist/commands/integration-layer/extension/status.d.ts +6 -0
- package/dist/commands/integration-layer/extension/status.js +28 -0
- package/dist/commands/integration-layer/extension/status.js.map +1 -0
- package/dist/commands/integration-layer/extension/validate.d.ts +15 -0
- package/dist/commands/integration-layer/extension/validate.js +98 -0
- package/dist/commands/integration-layer/extension/validate.js.map +1 -0
- package/dist/commands/integration-layer/hello.d.ts +6 -0
- package/dist/commands/integration-layer/hello.js +10 -0
- package/dist/commands/integration-layer/hello.js.map +1 -0
- package/dist/commands/integration-layer/init.d.ts +13 -0
- package/dist/commands/integration-layer/init.js +279 -0
- package/dist/commands/integration-layer/init.js.map +1 -0
- package/dist/commands/integration-layer/schema/fetch.d.ts +9 -0
- package/dist/commands/integration-layer/schema/fetch.js +27 -0
- package/dist/commands/integration-layer/schema/fetch.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/awaitBundleState.d.ts +33 -0
- package/dist/lib/awaitBundleState.js +77 -0
- package/dist/lib/awaitBundleState.js.map +1 -0
- package/dist/lib/base.d.ts +50 -0
- package/dist/lib/base.js +85 -0
- package/dist/lib/base.js.map +1 -0
- package/dist/lib/ilClient.d.ts +93 -0
- package/dist/lib/ilClient.js +120 -0
- package/dist/lib/ilClient.js.map +1 -0
- package/dist/lib/sourceRevision.d.ts +39 -0
- package/dist/lib/sourceRevision.js +95 -0
- package/dist/lib/sourceRevision.js.map +1 -0
- package/dist/lib/tooling/apiExtension.d.ts +31 -0
- package/dist/lib/tooling/apiExtension.js +39 -0
- package/dist/lib/tooling/apiExtension.js.map +1 -0
- package/dist/lib/tooling/build.d.ts +18 -0
- package/dist/lib/tooling/build.js +54 -0
- package/dist/lib/tooling/build.js.map +1 -0
- package/dist/lib/tooling/compose.d.ts +28 -0
- package/dist/lib/tooling/compose.js +48 -0
- package/dist/lib/tooling/compose.js.map +1 -0
- package/dist/lib/tooling/extensions.d.ts +77 -0
- package/dist/lib/tooling/extensions.js +209 -0
- package/dist/lib/tooling/extensions.js.map +1 -0
- package/dist/lib/tooling/gateway.d.ts +21 -0
- package/dist/lib/tooling/gateway.js +65 -0
- package/dist/lib/tooling/gateway.js.map +1 -0
- package/dist/lib/tooling/loadBundle.d.ts +10 -0
- package/dist/lib/tooling/loadBundle.js +27 -0
- package/dist/lib/tooling/loadBundle.js.map +1 -0
- package/dist/lib/tooling/staticAnalysis.d.ts +11 -0
- package/dist/lib/tooling/staticAnalysis.js +167 -0
- package/dist/lib/tooling/staticAnalysis.js.map +1 -0
- package/dist/lib/tooling/validateBundle.d.ts +19 -0
- package/dist/lib/tooling/validateBundle.js +144 -0
- package/dist/lib/tooling/validateBundle.js.map +1 -0
- package/package.json +47 -5
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Bundle an extension source into a single self-contained CommonJS artifact.
|
|
2
|
+
//
|
|
3
|
+
// Copied from commercetools/integration-layer-extension-examples
|
|
4
|
+
// (packages/tooling/src/build.ts) — a LOW-LEVEL function reused verbatim; the
|
|
5
|
+
// argv/env-driven `buildCommand` wrapper is NOT copied (this plugin's oclif command
|
|
6
|
+
// is the wrapper). esbuild bundles local helper modules inline. The output is
|
|
7
|
+
// CommonJS so the runtime can evaluate it as a script and read `module.exports`.
|
|
8
|
+
// `platform: "neutral"` keeps Node built-ins out (an import of one fails the build,
|
|
9
|
+
// not at load time), and `conditions: ["worker"]` steers a bundled SDK to its
|
|
10
|
+
// fetch-based build (the runtime provides a global `fetch`, not Node `http`/`https`).
|
|
11
|
+
// `graphql` stays EXTERNAL — the runtime supplies its single instance; a second
|
|
12
|
+
// inlined copy would break its `instanceof` checks.
|
|
13
|
+
import { build } from "esbuild";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
/** Peer modules the runtime owns — bundled never, resolved at load time. */
|
|
17
|
+
export const HOST_PROVIDED_EXTERNALS = ["graphql"];
|
|
18
|
+
/** The extension source of the example the tool is invoked inside (cwd-relative). */
|
|
19
|
+
export function defaultEntry() {
|
|
20
|
+
return join(process.cwd(), "src", "extension.ts");
|
|
21
|
+
}
|
|
22
|
+
/** The bundle output for the example the tool is invoked inside (cwd-relative). */
|
|
23
|
+
export function defaultOutfile() {
|
|
24
|
+
return join(process.cwd(), "dist", "extension.js");
|
|
25
|
+
}
|
|
26
|
+
/** Bundle `entry` into a single self-contained CJS file at `outfile`. */
|
|
27
|
+
export async function buildBundle(entry = defaultEntry(), outfile = defaultOutfile()) {
|
|
28
|
+
const result = await build({
|
|
29
|
+
entryPoints: [entry],
|
|
30
|
+
outfile,
|
|
31
|
+
bundle: true,
|
|
32
|
+
format: "cjs",
|
|
33
|
+
platform: "neutral",
|
|
34
|
+
target: "node22",
|
|
35
|
+
conditions: ["worker"],
|
|
36
|
+
// `platform: "neutral"` leaves `mainFields` empty, so a package that ships only
|
|
37
|
+
// classic `main`/`module` fields (no `exports`) — e.g. `@commercetools/ts-client`
|
|
38
|
+
// / `@commercetools/platform-sdk`, which an extension reaches for to read CT from
|
|
39
|
+
// a resolver — is otherwise unresolvable ("Could not resolve"). Preferring
|
|
40
|
+
// `module` (the fetch-based ESM build, no Node built-ins) then `main` fixes that;
|
|
41
|
+
// packages that DO declare `exports` (e.g. algoliasearch) resolve through it and
|
|
42
|
+
// ignore this. (ILEE's build.ts lacks this line — its examples don't hit the case;
|
|
43
|
+
// keep the two in sync when it's added there.)
|
|
44
|
+
mainFields: ["module", "main"],
|
|
45
|
+
external: HOST_PROVIDED_EXTERNALS,
|
|
46
|
+
metafile: true,
|
|
47
|
+
logLevel: "silent",
|
|
48
|
+
});
|
|
49
|
+
const sourceFiles = Object.keys(result.metafile.inputs)
|
|
50
|
+
.filter((input) => !input.includes("node_modules"))
|
|
51
|
+
.map((input) => resolve(process.cwd(), input));
|
|
52
|
+
return { outfile, sourceFiles };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/lib/tooling/build.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,iEAAiE;AACjE,8EAA8E;AAC9E,oFAAoF;AACpF,8EAA8E;AAC9E,iFAAiF;AACjF,oFAAoF;AACpF,8EAA8E;AAC9E,sFAAsF;AACtF,gFAAgF;AAChF,oDAAoD;AAEpD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,SAAS,CAAC,CAAC;AAEnD,qFAAqF;AACrF,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;AACpD,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AACrD,CAAC;AAaD,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,YAAY,EAAE,EAC9B,UAAkB,cAAc,EAAE;IAElC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;QACzB,WAAW,EAAE,CAAC,KAAK,CAAC;QACpB,OAAO;QACP,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,CAAC,QAAQ,CAAC;QACtB,gFAAgF;QAChF,kFAAkF;QAClF,kFAAkF;QAClF,2EAA2E;QAC3E,kFAAkF;QAClF,iFAAiF;QACjF,mFAAmF;QACnF,+CAA+C;QAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC9B,QAAQ,EAAE,uBAAuB;QACjC,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,QAAQ;KACnB,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;SACpD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;SAClD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IACjD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type GraphQLSchema } from "graphql";
|
|
2
|
+
/** The subgraph names the integration layer composes under — matched here so the
|
|
3
|
+
* local composition mirrors the deployed one. `integration-layer` is the name an
|
|
4
|
+
* extension's `@override(from: …)` references. */
|
|
5
|
+
export declare const INTEGRATION_LAYER_SERVICE = "integration-layer";
|
|
6
|
+
export declare const EXTENSION_SERVICE = "extensions";
|
|
7
|
+
export type ComposeResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
/** The client-facing API schema (federation directives stripped), browsable. */
|
|
10
|
+
apiSchema: GraphQLSchema;
|
|
11
|
+
/** Printed API-schema SDL — "what the full schema looks like" to a consumer. */
|
|
12
|
+
apiSdl: string;
|
|
13
|
+
/** The raw composed supergraph SDL the router plans against. */
|
|
14
|
+
supergraphSdl: string;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
errors: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Compose the integration layer + the extension into a supergraph and derive the
|
|
21
|
+
* client-facing API schema. Never throws — a composition failure comes back in
|
|
22
|
+
* `errors` so the dev server can surface it and keep serving. The URLs are baked into
|
|
23
|
+
* the supergraph so `--gateway` routes correctly; for `--compose` they're cosmetic.
|
|
24
|
+
*/
|
|
25
|
+
export declare function composeWithIntegrationLayer(integrationLayerSdl: string, extensionSdl: string, urls: {
|
|
26
|
+
integrationLayerUrl: string;
|
|
27
|
+
extensionUrl: string;
|
|
28
|
+
}): ComposeResult;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Compose the local extension subgraph with the project's live integration-layer
|
|
2
|
+
// subgraph — the same two-subgraph composition the integration layer runs on publish.
|
|
3
|
+
//
|
|
4
|
+
// The LOW-LEVEL `composeWithIntegrationLayer` is copied verbatim from
|
|
5
|
+
// commercetools/integration-layer-extension-examples (packages/tooling/src/compose.ts).
|
|
6
|
+
// The env/token-minting `fetchIntegrationLayerSubgraphSdl` wrapper is NOT copied —
|
|
7
|
+
// fetching the project's subgraph SDL is a plain authenticated HTTP call, so it lives
|
|
8
|
+
// in `../ilClient.ts` parameterised by (baseUrl, projectKey, token), and the command
|
|
9
|
+
// layer supplies the token.
|
|
10
|
+
import { parse, printSchema } from "graphql";
|
|
11
|
+
import { composeServices } from "@apollo/composition";
|
|
12
|
+
/** The subgraph names the integration layer composes under — matched here so the
|
|
13
|
+
* local composition mirrors the deployed one. `integration-layer` is the name an
|
|
14
|
+
* extension's `@override(from: …)` references. */
|
|
15
|
+
export const INTEGRATION_LAYER_SERVICE = "integration-layer";
|
|
16
|
+
export const EXTENSION_SERVICE = "extensions";
|
|
17
|
+
/**
|
|
18
|
+
* Compose the integration layer + the extension into a supergraph and derive the
|
|
19
|
+
* client-facing API schema. Never throws — a composition failure comes back in
|
|
20
|
+
* `errors` so the dev server can surface it and keep serving. The URLs are baked into
|
|
21
|
+
* the supergraph so `--gateway` routes correctly; for `--compose` they're cosmetic.
|
|
22
|
+
*/
|
|
23
|
+
export function composeWithIntegrationLayer(integrationLayerSdl, extensionSdl, urls) {
|
|
24
|
+
let services;
|
|
25
|
+
try {
|
|
26
|
+
services = [
|
|
27
|
+
{ name: INTEGRATION_LAYER_SERVICE, typeDefs: parse(integrationLayerSdl), url: urls.integrationLayerUrl },
|
|
28
|
+
{ name: EXTENSION_SERVICE, typeDefs: parse(extensionSdl), url: urls.extensionUrl },
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return { ok: false, errors: [`SDL is not valid GraphQL: ${err.message}`] };
|
|
33
|
+
}
|
|
34
|
+
const result = composeServices(services);
|
|
35
|
+
// `errors` is the discriminant of the composition-failure variant; its presence
|
|
36
|
+
// narrows `result` to the success variant (with `schema` + `supergraphSdl`).
|
|
37
|
+
if (result.errors) {
|
|
38
|
+
return { ok: false, errors: result.errors.map((e) => e.message) };
|
|
39
|
+
}
|
|
40
|
+
const apiSchema = result.schema.toAPISchema().toGraphQLJSSchema();
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
apiSchema,
|
|
44
|
+
apiSdl: printSchema(apiSchema),
|
|
45
|
+
supergraphSdl: result.supergraphSdl,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/lib/tooling/compose.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,sFAAsF;AACtF,EAAE;AACF,sEAAsE;AACtE,wFAAwF;AACxF,mFAAmF;AACnF,sFAAsF;AACtF,qFAAqF;AACrF,4BAA4B;AAE5B,OAAO,EAAE,KAAK,EAAE,WAAW,EAAsB,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;mDAEmD;AACnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC;AAc9C;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,mBAA2B,EAC3B,YAAoB,EACpB,IAA2D;IAE3D,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG;YACT,EAAE,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,KAAK,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE;YACxG,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;SACnF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,6BAA8B,GAAa,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACzC,gFAAgF;IAChF,6EAA6E;IAC7E,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAClE,OAAO;QACL,EAAE,EAAE,IAAI;QACR,SAAS;QACT,MAAM,EAAE,WAAW,CAAC,SAAS,CAAC;QAC9B,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type GraphQLSchema } from "graphql";
|
|
2
|
+
/** An extension package discovered under the workspace's extensions/ directory. */
|
|
3
|
+
export interface DiscoveredExtension {
|
|
4
|
+
/** The package/directory name (used only for messages — the combined subgraph has one name). */
|
|
5
|
+
name: string;
|
|
6
|
+
/** The extension source entry (`<dir>/src/extension.ts`). */
|
|
7
|
+
entry: string;
|
|
8
|
+
/** The esbuild bundle output for a standalone build (`<dir>/dist/extension.js`). */
|
|
9
|
+
outfile: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Discover every extension package under `<root>/<dirName>` that has a
|
|
13
|
+
* `src/extension.ts`. Sorted by name for stable, deterministic merge output.
|
|
14
|
+
*/
|
|
15
|
+
export declare function discoverExtensions(root: string, dirName: string): Promise<DiscoveredExtension[]>;
|
|
16
|
+
/** One extension's contribution to the combined subgraph. */
|
|
17
|
+
export interface ExtensionModule {
|
|
18
|
+
name: string;
|
|
19
|
+
typeDefs: string;
|
|
20
|
+
resolvers: object;
|
|
21
|
+
}
|
|
22
|
+
/** The single subgraph all extensions merge into. */
|
|
23
|
+
export interface MergedSubgraph {
|
|
24
|
+
/** Executable subgraph schema (typeDefs + wired resolvers) — for serving. */
|
|
25
|
+
schema: GraphQLSchema;
|
|
26
|
+
/** Federation SDL of the merged subgraph — for composing with the core / publishing. */
|
|
27
|
+
sdl: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Merge several extension modules into ONE federation subgraph — the single bundle a
|
|
31
|
+
* project deploys. Each extension's SDL is a self-contained subgraph (its own
|
|
32
|
+
* `type Query`, its own `@link` header), so we can't just concatenate them
|
|
33
|
+
* (`buildSubgraphSchema` would see two `type Query`). `mergeTypeDefs` folds same-named
|
|
34
|
+
* types into one — so two extensions can each contribute fields to `Query` — and dedupes
|
|
35
|
+
* the repeated `@link`.
|
|
36
|
+
*
|
|
37
|
+
* First we reject two extensions declaring the SAME field (even identically): merged into
|
|
38
|
+
* one bundle their resolvers would silently shadow each other, so it's an authoring error
|
|
39
|
+
* we name loudly rather than paper over. The merged SDL is printed with
|
|
40
|
+
* `printSubgraphSchema`, so it round-trips as the exact federation SDL the IL validates /
|
|
41
|
+
* publishes for the deployed bundle.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mergeExtensionSubgraph(modules: ExtensionModule[]): MergedSubgraph;
|
|
44
|
+
/** Result of a combined build — the same shape {@link buildBundle} returns. */
|
|
45
|
+
export interface CombinedBuildResult {
|
|
46
|
+
/** Absolute path to the single combined bundle. */
|
|
47
|
+
outfile: string;
|
|
48
|
+
/** The authors' source files that went into it (for static analysis; excludes the generated entry). */
|
|
49
|
+
sourceFiles: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build ONE combined bundle from every discovered extension — the artifact `push`
|
|
53
|
+
* uploads. Each extension is bundled + loaded to read its `typeDefs` (merged into one
|
|
54
|
+
* SDL, so a clash fails here on the author's machine) and to be re-imported by a
|
|
55
|
+
* generated entry that, at load time, merges the resolvers (per type, then per field),
|
|
56
|
+
* concatenates `apiExtensions`, and merges `hooks`. That entry is then bundled into the
|
|
57
|
+
* final self-contained artifact — a faithful stand-in for the single deployed bundle.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildCombinedBundle(extensions: DiscoveredExtension[], outfile: string): Promise<CombinedBuildResult>;
|
|
60
|
+
/** What {@link bundleForFlags} produced, plus a label for the command's log line. */
|
|
61
|
+
export interface BundleForFlags extends CombinedBuildResult {
|
|
62
|
+
/** Human description of what was bundled (the entry, or "N extension(s) (…)"). */
|
|
63
|
+
describe: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The single build step shared by `build`/`validate`/`push`: bundle either the one
|
|
67
|
+
* extension in the cwd (default) or — with `--all` — every extension under
|
|
68
|
+
* `<extensionsDir>/*` merged into the one combined bundle a project deploys. Throws a
|
|
69
|
+
* clean message when `--all` finds nothing to build.
|
|
70
|
+
*/
|
|
71
|
+
export declare function bundleForFlags(opts: {
|
|
72
|
+
all: boolean;
|
|
73
|
+
extensionsDir: string;
|
|
74
|
+
entry: string;
|
|
75
|
+
out: string;
|
|
76
|
+
cwd?: string;
|
|
77
|
+
}): Promise<BundleForFlags>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Monorepo helpers for the "one bundle per project" model. In production a project
|
|
2
|
+
// has a SINGLE extension bundle = one federation subgraph (all of a repo's extensions
|
|
3
|
+
// merged), which the router then composes with the integration-layer core subgraph.
|
|
4
|
+
// So a monorepo of `extensions/*` packages must:
|
|
5
|
+
// - MERGE into one subgraph for local `serve --all` (mergeExtensionSubgraph), and
|
|
6
|
+
// - BUILD into one bundle for `build/validate/push --all` (buildCombinedBundle),
|
|
7
|
+
// never one-subgraph-per-extension (that's not the deployed shape).
|
|
8
|
+
import { readdir, readFile, stat, writeFile, mkdtemp } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
import { buildSubgraphSchema, printSubgraphSchema } from "@apollo/subgraph";
|
|
13
|
+
import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
14
|
+
import { Kind, parse } from "graphql";
|
|
15
|
+
import { buildBundle } from "./build.js";
|
|
16
|
+
import { loadBundleSource } from "./loadBundle.js";
|
|
17
|
+
/**
|
|
18
|
+
* Discover every extension package under `<root>/<dirName>` that has a
|
|
19
|
+
* `src/extension.ts`. Sorted by name for stable, deterministic merge output.
|
|
20
|
+
*/
|
|
21
|
+
export async function discoverExtensions(root, dirName) {
|
|
22
|
+
const base = join(root, dirName);
|
|
23
|
+
const dirents = await readdir(base, { withFileTypes: true }).catch(() => []);
|
|
24
|
+
const found = [];
|
|
25
|
+
for (const dirent of dirents) {
|
|
26
|
+
if (!dirent.isDirectory())
|
|
27
|
+
continue;
|
|
28
|
+
const entry = join(base, dirent.name, "src", "extension.ts");
|
|
29
|
+
const hasEntry = await stat(entry)
|
|
30
|
+
.then((s) => s.isFile())
|
|
31
|
+
.catch(() => false);
|
|
32
|
+
if (!hasEntry)
|
|
33
|
+
continue;
|
|
34
|
+
found.push({
|
|
35
|
+
name: dirent.name,
|
|
36
|
+
entry,
|
|
37
|
+
outfile: join(base, dirent.name, "dist", "extension.js"),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
|
+
}
|
|
42
|
+
/** Collect `Type.field` keys an SDL declares on object/interface types (incl. extensions). */
|
|
43
|
+
function declaredFields(sdl) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const def of parse(sdl).definitions) {
|
|
46
|
+
if (def.kind === Kind.OBJECT_TYPE_DEFINITION ||
|
|
47
|
+
def.kind === Kind.OBJECT_TYPE_EXTENSION ||
|
|
48
|
+
def.kind === Kind.INTERFACE_TYPE_DEFINITION ||
|
|
49
|
+
def.kind === Kind.INTERFACE_TYPE_EXTENSION) {
|
|
50
|
+
for (const f of def.fields ?? [])
|
|
51
|
+
out.push({ type: def.name.value, field: f.name.value });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Merge several extension modules into ONE federation subgraph — the single bundle a
|
|
58
|
+
* project deploys. Each extension's SDL is a self-contained subgraph (its own
|
|
59
|
+
* `type Query`, its own `@link` header), so we can't just concatenate them
|
|
60
|
+
* (`buildSubgraphSchema` would see two `type Query`). `mergeTypeDefs` folds same-named
|
|
61
|
+
* types into one — so two extensions can each contribute fields to `Query` — and dedupes
|
|
62
|
+
* the repeated `@link`.
|
|
63
|
+
*
|
|
64
|
+
* First we reject two extensions declaring the SAME field (even identically): merged into
|
|
65
|
+
* one bundle their resolvers would silently shadow each other, so it's an authoring error
|
|
66
|
+
* we name loudly rather than paper over. The merged SDL is printed with
|
|
67
|
+
* `printSubgraphSchema`, so it round-trips as the exact federation SDL the IL validates /
|
|
68
|
+
* publishes for the deployed bundle.
|
|
69
|
+
*/
|
|
70
|
+
export function mergeExtensionSubgraph(modules) {
|
|
71
|
+
if (modules.length === 0) {
|
|
72
|
+
throw new Error("no extension exports a GraphQL subgraph (typeDefs) to merge");
|
|
73
|
+
}
|
|
74
|
+
const resolvers = {};
|
|
75
|
+
const sdls = [];
|
|
76
|
+
const owner = new Map(); // "Type.field" -> first extension that declared it
|
|
77
|
+
for (const m of modules) {
|
|
78
|
+
if (typeof m.typeDefs !== "string" || m.typeDefs.trim() === "") {
|
|
79
|
+
throw new Error(`extension '${m.name}' must export a non-empty \`typeDefs\` string`);
|
|
80
|
+
}
|
|
81
|
+
if (m.resolvers === null || typeof m.resolvers !== "object") {
|
|
82
|
+
throw new Error(`extension '${m.name}' with \`typeDefs\` must also export a \`resolvers\` object`);
|
|
83
|
+
}
|
|
84
|
+
let fields;
|
|
85
|
+
try {
|
|
86
|
+
fields = declaredFields(m.typeDefs);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw new Error(`extension '${m.name}' has invalid \`typeDefs\`: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
for (const { type, field } of fields) {
|
|
92
|
+
const key = `${type}.${field}`;
|
|
93
|
+
const prev = owner.get(key);
|
|
94
|
+
if (prev !== undefined && prev !== m.name) {
|
|
95
|
+
throw new Error(`extensions '${prev}' and '${m.name}' both declare \`${key}\` — a project ships one bundle, so each field must be owned by a single extension`);
|
|
96
|
+
}
|
|
97
|
+
owner.set(key, m.name);
|
|
98
|
+
}
|
|
99
|
+
sdls.push(m.typeDefs);
|
|
100
|
+
// Merge resolvers per type then per field — the same shape the combined bundle uses.
|
|
101
|
+
for (const [type, fieldResolvers] of Object.entries(m.resolvers)) {
|
|
102
|
+
resolvers[type] = Object.assign(resolvers[type] ?? {}, fieldResolvers);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let schema;
|
|
106
|
+
try {
|
|
107
|
+
// `mergeTypeDefs` folds the per-extension subgraphs into one document. `@apollo/subgraph`
|
|
108
|
+
// types `resolvers` as its internal `GraphQLResolverMap`; cast through the param type.
|
|
109
|
+
const typeDefs = mergeTypeDefs(sdls);
|
|
110
|
+
schema = buildSubgraphSchema({ typeDefs, resolvers });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw new Error(`extensions do not merge into a single subgraph: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
return { schema, sdl: printSubgraphSchema(schema) };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build ONE combined bundle from every discovered extension — the artifact `push`
|
|
119
|
+
* uploads. Each extension is bundled + loaded to read its `typeDefs` (merged into one
|
|
120
|
+
* SDL, so a clash fails here on the author's machine) and to be re-imported by a
|
|
121
|
+
* generated entry that, at load time, merges the resolvers (per type, then per field),
|
|
122
|
+
* concatenates `apiExtensions`, and merges `hooks`. That entry is then bundled into the
|
|
123
|
+
* final self-contained artifact — a faithful stand-in for the single deployed bundle.
|
|
124
|
+
*/
|
|
125
|
+
export async function buildCombinedBundle(extensions, outfile) {
|
|
126
|
+
if (extensions.length === 0) {
|
|
127
|
+
throw new Error("no extensions to build");
|
|
128
|
+
}
|
|
129
|
+
// 1. Bundle + load each extension to read its exported subgraph (typeDefs/resolvers).
|
|
130
|
+
const tmp = await mkdtemp(join(tmpdir(), "il-combine-"));
|
|
131
|
+
const loaded = [];
|
|
132
|
+
for (const ext of extensions) {
|
|
133
|
+
const out = join(tmp, `${ext.name}.js`);
|
|
134
|
+
await buildBundle(ext.entry, out);
|
|
135
|
+
loaded.push({ name: ext.name, module: loadBundleSource(await readFile(out, "utf8")) });
|
|
136
|
+
}
|
|
137
|
+
// 2. Merge the subgraph-bearing extensions into one SDL (conflict detection). An
|
|
138
|
+
// extension may be API-extensions-only (no typeDefs) — those contribute no SDL.
|
|
139
|
+
const withSubgraph = loaded.filter((l) => typeof l.module.typeDefs === "string" && l.module.typeDefs.trim() !== "");
|
|
140
|
+
const mergedSdl = withSubgraph.length > 0
|
|
141
|
+
? mergeExtensionSubgraph(withSubgraph.map((l) => ({
|
|
142
|
+
name: l.name,
|
|
143
|
+
typeDefs: l.module.typeDefs,
|
|
144
|
+
resolvers: l.module.resolvers ?? {},
|
|
145
|
+
}))).sdl
|
|
146
|
+
: undefined;
|
|
147
|
+
// 3. Generate the combining entry: re-import each extension, merge at load time.
|
|
148
|
+
const imports = extensions
|
|
149
|
+
.map((e, i) => `import * as e${i} from ${JSON.stringify(e.entry)};`)
|
|
150
|
+
.join("\n");
|
|
151
|
+
const modulesArray = `[${extensions.map((_, i) => `e${i}`).join(", ")}]`;
|
|
152
|
+
const entrySource = `${imports}
|
|
153
|
+
|
|
154
|
+
const __modules = ${modulesArray};
|
|
155
|
+
|
|
156
|
+
${mergedSdl !== undefined ? `export const typeDefs = ${JSON.stringify(mergedSdl)};` : ""}
|
|
157
|
+
|
|
158
|
+
const __mergeResolvers = (mods) => {
|
|
159
|
+
const out = {};
|
|
160
|
+
for (const m of mods) {
|
|
161
|
+
const r = m && m.resolvers;
|
|
162
|
+
if (!r || typeof r !== "object") continue;
|
|
163
|
+
for (const type of Object.keys(r)) {
|
|
164
|
+
out[type] = Object.assign(out[type] ?? {}, r[type]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const resolvers = __mergeResolvers(__modules);
|
|
171
|
+
export const apiExtensions = __modules.flatMap((m) =>
|
|
172
|
+
m && Array.isArray(m.apiExtensions) ? m.apiExtensions : [],
|
|
173
|
+
);
|
|
174
|
+
export const hooks = Object.assign(
|
|
175
|
+
{},
|
|
176
|
+
...__modules.map((m) => (m && m.hooks && typeof m.hooks === "object" ? m.hooks : {})),
|
|
177
|
+
);
|
|
178
|
+
`;
|
|
179
|
+
const entryPath = join(tmp, "combined-entry.ts");
|
|
180
|
+
await writeFile(entryPath, entrySource, "utf8");
|
|
181
|
+
// 4. Bundle the generated entry into the single artifact. Its imported author sources
|
|
182
|
+
// feed static analysis; the generated entry itself is trusted, so drop it.
|
|
183
|
+
const { sourceFiles } = await buildBundle(entryPath, outfile);
|
|
184
|
+
const generated = resolve(entryPath);
|
|
185
|
+
return { outfile, sourceFiles: sourceFiles.filter((f) => resolve(f) !== generated) };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The single build step shared by `build`/`validate`/`push`: bundle either the one
|
|
189
|
+
* extension in the cwd (default) or — with `--all` — every extension under
|
|
190
|
+
* `<extensionsDir>/*` merged into the one combined bundle a project deploys. Throws a
|
|
191
|
+
* clean message when `--all` finds nothing to build.
|
|
192
|
+
*/
|
|
193
|
+
export async function bundleForFlags(opts) {
|
|
194
|
+
if (!opts.all) {
|
|
195
|
+
const { outfile, sourceFiles } = await buildBundle(opts.entry, opts.out);
|
|
196
|
+
return { outfile, sourceFiles, describe: opts.entry };
|
|
197
|
+
}
|
|
198
|
+
const extensions = await discoverExtensions(opts.cwd ?? process.cwd(), opts.extensionsDir);
|
|
199
|
+
if (extensions.length === 0) {
|
|
200
|
+
throw new Error(`no extensions found under ./${opts.extensionsDir}/*/src/extension.ts — run --all from the monorepo root`);
|
|
201
|
+
}
|
|
202
|
+
const { outfile, sourceFiles } = await buildCombinedBundle(extensions, opts.out);
|
|
203
|
+
return {
|
|
204
|
+
outfile,
|
|
205
|
+
sourceFiles,
|
|
206
|
+
describe: `${extensions.length} extension(s) (${extensions.map((e) => e.name).join(", ")})`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../src/lib/tooling/extensions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApolloGateway } from "@apollo/gateway";
|
|
2
|
+
/**
|
|
3
|
+
* Mint an anonymous session and return its bearer token. The gateway attaches it to
|
|
4
|
+
* every integration-layer subgraph request; the integration layer 403s a bearer
|
|
5
|
+
* whose project doesn't match the URL, so an anonymous session is the minimum needed
|
|
6
|
+
* to route real queries through. (A 30-day sliding TTL easily outlasts a dev session.)
|
|
7
|
+
*/
|
|
8
|
+
export declare function mintAnonymousSession(integrationLayerUrl: string, projectKey: string): Promise<string>;
|
|
9
|
+
export interface GatewayOptions {
|
|
10
|
+
/** The composed supergraph the planner routes against (from `composeWithIntegrationLayer`). */
|
|
11
|
+
supergraphSdl: string;
|
|
12
|
+
/** Anonymous session bearer, attached to the integration-layer data source's requests. */
|
|
13
|
+
integrationLayerBearer: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build and load an Apollo Gateway over the composed supergraph. Each subgraph is an
|
|
17
|
+
* HTTP data source addressed by its baked URL; the integration-layer one carries the
|
|
18
|
+
* session bearer. Resolves once the gateway has loaded its query planner. Call
|
|
19
|
+
* `.stop()` on the returned gateway when replacing it (on a schema change).
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeGateway(opts: GatewayOptions): Promise<ApolloGateway>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The `--gateway` half of the local dev server: an executable merged schema that
|
|
2
|
+
// routes each field to its owner — the local extension and the project's deployed
|
|
3
|
+
// integration layer — so a query like `{ product(id:…) { name loyaltyPoints(price:…) }}`
|
|
4
|
+
// resolves `name` from the integration layer and `loyaltyPoints` from the local
|
|
5
|
+
// extension in one request, the production topology in miniature.
|
|
6
|
+
//
|
|
7
|
+
// Copied verbatim from commercetools/integration-layer-extension-examples
|
|
8
|
+
// (packages/tooling/src/gateway.ts). We use Apollo Gateway (the reference Federation
|
|
9
|
+
// implementation) because the extension model leans on `@interfaceObject` and only
|
|
10
|
+
// the reference gateway resolves that correctly. Both subgraphs are reached over
|
|
11
|
+
// HTTP, so the extension runs as a genuine subgraph; the integration-layer data
|
|
12
|
+
// source carries an anonymous session bearer (the integration layer re-validates the
|
|
13
|
+
// bearer's project against the URL).
|
|
14
|
+
import { ApolloGateway, RemoteGraphQLDataSource } from "@apollo/gateway";
|
|
15
|
+
import { INTEGRATION_LAYER_SERVICE } from "./compose.js";
|
|
16
|
+
/**
|
|
17
|
+
* Mint an anonymous session and return its bearer token. The gateway attaches it to
|
|
18
|
+
* every integration-layer subgraph request; the integration layer 403s a bearer
|
|
19
|
+
* whose project doesn't match the URL, so an anonymous session is the minimum needed
|
|
20
|
+
* to route real queries through. (A 30-day sliding TTL easily outlasts a dev session.)
|
|
21
|
+
*/
|
|
22
|
+
export async function mintAnonymousSession(integrationLayerUrl, projectKey) {
|
|
23
|
+
const base = integrationLayerUrl.replace(/\/+$/, "");
|
|
24
|
+
const url = `${base}/${encodeURIComponent(projectKey)}/session`;
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "content-type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ grant_type: "anonymous" }),
|
|
29
|
+
});
|
|
30
|
+
const text = await res.text();
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
throw new Error(`could not mint an anonymous session (${res.status}) at ${url}: ${text}`);
|
|
33
|
+
}
|
|
34
|
+
const token = JSON.parse(text).token;
|
|
35
|
+
if (!token)
|
|
36
|
+
throw new Error("token response had no `token`");
|
|
37
|
+
return token;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build and load an Apollo Gateway over the composed supergraph. Each subgraph is an
|
|
41
|
+
* HTTP data source addressed by its baked URL; the integration-layer one carries the
|
|
42
|
+
* session bearer. Resolves once the gateway has loaded its query planner. Call
|
|
43
|
+
* `.stop()` on the returned gateway when replacing it (on a schema change).
|
|
44
|
+
*/
|
|
45
|
+
export async function makeGateway(opts) {
|
|
46
|
+
const gateway = new ApolloGateway({
|
|
47
|
+
supergraphSdl: opts.supergraphSdl,
|
|
48
|
+
buildService: ({ name, url }) => new RemoteGraphQLDataSource({
|
|
49
|
+
url,
|
|
50
|
+
// Use Node's global `fetch` (undici) for every data source; Apollo Gateway's
|
|
51
|
+
// default fetcher breaks its agent selection in some environments.
|
|
52
|
+
fetcher: globalThis.fetch,
|
|
53
|
+
willSendRequest: ({ request }) => {
|
|
54
|
+
// Authenticate only the integration-layer subgraph; the extension subgraph
|
|
55
|
+
// is the dev server's own internal endpoint and needs no bearer.
|
|
56
|
+
if (name === INTEGRATION_LAYER_SERVICE) {
|
|
57
|
+
request.http?.headers.set("authorization", `Bearer ${opts.integrationLayerBearer}`);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
await gateway.load();
|
|
63
|
+
return gateway;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/lib/tooling/gateway.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,kFAAkF;AAClF,yFAAyF;AACzF,gFAAgF;AAChF,kEAAkE;AAClE,EAAE;AACF,0EAA0E;AAC1E,qFAAqF;AACrF,mFAAmF;AACnF,iFAAiF;AACjF,gFAAgF;AAChF,qFAAqF;AACrF,qCAAqC;AAErC,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,mBAA2B,EAC3B,UAAkB;IAElB,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC;IAChE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC,KAAK,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC;AASD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAoB;IACpD,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC;QAChC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,YAAY,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAC9B,IAAI,uBAAuB,CAAC;YAC1B,GAAG;YACH,6EAA6E;YAC7E,mEAAmE;YACnE,OAAO,EAAE,UAAU,CAAC,KAAsD;YAC1E,eAAe,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;gBAC/B,2EAA2E;gBAC3E,iEAAiE;gBACjE,IAAI,IAAI,KAAK,yBAAyB,EAAE,CAAC;oBACvC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC;gBACtF,CAAC;YACH,CAAC;SACF,CAAC;KACL,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACrB,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface EvaluatedBundle {
|
|
2
|
+
typeDefs?: unknown;
|
|
3
|
+
resolvers?: unknown;
|
|
4
|
+
apiExtensions?: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Evaluate an esbuild-CJS extension bundle and return its `module.exports`.
|
|
8
|
+
* `require` resolves only `graphql`; any other module id throws.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadBundleSource(source: string): EvaluatedBundle;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Load a built extension bundle and return its exported `typeDefs` + `resolvers`,
|
|
2
|
+
// used by the resolver-coherence check and the local dev server.
|
|
3
|
+
//
|
|
4
|
+
// Copied verbatim from commercetools/integration-layer-extension-examples
|
|
5
|
+
// (packages/tooling/src/loadBundle.ts). The bundle is esbuild's single-file
|
|
6
|
+
// CommonJS artifact, so we evaluate it with the CommonJS calling convention and a
|
|
7
|
+
// `require` that resolves only `graphql` (a second copy would break `instanceof`).
|
|
8
|
+
// It runs as an ordinary module — not a security boundary; the runtime enforces
|
|
9
|
+
// that, and `staticAnalysis.ts` screens the source first.
|
|
10
|
+
import { GraphQLError } from "graphql";
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate an esbuild-CJS extension bundle and return its `module.exports`.
|
|
13
|
+
* `require` resolves only `graphql`; any other module id throws.
|
|
14
|
+
*/
|
|
15
|
+
export function loadBundleSource(source) {
|
|
16
|
+
const moduleObject = { exports: {} };
|
|
17
|
+
const requireShim = (id) => {
|
|
18
|
+
if (id === "graphql")
|
|
19
|
+
return { GraphQLError };
|
|
20
|
+
throw new Error(`extension bundle may not require "${id}"`);
|
|
21
|
+
};
|
|
22
|
+
// The CommonJS wrapper: bind `module`/`exports`/`require` and run the source.
|
|
23
|
+
const evaluate = new Function("module", "exports", "require", source);
|
|
24
|
+
evaluate(moduleObject, moduleObject.exports, requireShim);
|
|
25
|
+
return moduleObject.exports;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=loadBundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadBundle.js","sourceRoot":"","sources":["../../../src/lib/tooling/loadBundle.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,kFAAkF;AAClF,mFAAmF;AACnF,gFAAgF;AAChF,0DAA0D;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAUvC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,YAAY,GAAiC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACnE,MAAM,WAAW,GAAG,CAAC,EAAU,EAAW,EAAE;QAC1C,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC,CAAC;IACF,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACtE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1D,OAAO,YAAY,CAAC,OAAO,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AnalysisIssue {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Analyse the given source files (the author's entry + its local imports) and
|
|
8
|
+
* return every runtime-incompatibility issue found. An empty array means the source
|
|
9
|
+
* is clean of the patterns this checks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function analyzeSources(files: string[]): Promise<AnalysisIssue[]>;
|