@commercetools/cli-topic-integration-layer 0.2.1 → 0.3.1

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 (54) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/integration-layer/explore.d.ts +20 -0
  3. package/dist/commands/integration-layer/explore.js +209 -0
  4. package/dist/commands/integration-layer/explore.js.map +1 -0
  5. package/dist/commands/integration-layer/extension/build.d.ts +2 -0
  6. package/dist/commands/integration-layer/extension/build.js +24 -3
  7. package/dist/commands/integration-layer/extension/build.js.map +1 -1
  8. package/dist/commands/integration-layer/extension/invoke.d.ts +1 -0
  9. package/dist/commands/integration-layer/extension/invoke.js +9 -4
  10. package/dist/commands/integration-layer/extension/invoke.js.map +1 -1
  11. package/dist/commands/integration-layer/extension/push.d.ts +15 -0
  12. package/dist/commands/integration-layer/extension/push.js +137 -3
  13. package/dist/commands/integration-layer/extension/push.js.map +1 -1
  14. package/dist/commands/integration-layer/extension/serve.d.ts +30 -0
  15. package/dist/commands/integration-layer/extension/serve.js +244 -6
  16. package/dist/commands/integration-layer/extension/serve.js.map +1 -1
  17. package/dist/commands/integration-layer/extension/status.js +4 -0
  18. package/dist/commands/integration-layer/extension/status.js.map +1 -1
  19. package/dist/commands/integration-layer/extension/validate.d.ts +2 -0
  20. package/dist/commands/integration-layer/extension/validate.js +24 -2
  21. package/dist/commands/integration-layer/extension/validate.js.map +1 -1
  22. package/dist/commands/integration-layer/init.js +228 -77
  23. package/dist/commands/integration-layer/init.js.map +1 -1
  24. package/dist/lib/awaitBundleState.d.ts +33 -0
  25. package/dist/lib/awaitBundleState.js +77 -0
  26. package/dist/lib/awaitBundleState.js.map +1 -0
  27. package/dist/lib/base.d.ts +35 -0
  28. package/dist/lib/base.js +99 -13
  29. package/dist/lib/base.js.map +1 -1
  30. package/dist/lib/ilClient.d.ts +62 -2
  31. package/dist/lib/ilClient.js +42 -2
  32. package/dist/lib/ilClient.js.map +1 -1
  33. package/dist/lib/sourceRevision.d.ts +39 -0
  34. package/dist/lib/sourceRevision.js +95 -0
  35. package/dist/lib/sourceRevision.js.map +1 -0
  36. package/dist/lib/tooling/exploreSchema.d.ts +15 -0
  37. package/dist/lib/tooling/exploreSchema.js +77 -0
  38. package/dist/lib/tooling/exploreSchema.js.map +1 -0
  39. package/dist/lib/tooling/explorerPage.d.ts +1 -0
  40. package/dist/lib/tooling/explorerPage.js +96 -0
  41. package/dist/lib/tooling/explorerPage.js.map +1 -0
  42. package/dist/lib/tooling/explorerServer.d.ts +27 -0
  43. package/dist/lib/tooling/explorerServer.js +134 -0
  44. package/dist/lib/tooling/explorerServer.js.map +1 -0
  45. package/dist/lib/tooling/extensions.d.ts +77 -0
  46. package/dist/lib/tooling/extensions.js +209 -0
  47. package/dist/lib/tooling/extensions.js.map +1 -0
  48. package/dist/lib/tooling/introspectionRequest.d.ts +13 -0
  49. package/dist/lib/tooling/introspectionRequest.js +47 -0
  50. package/dist/lib/tooling/introspectionRequest.js.map +1 -0
  51. package/dist/lib/tooling/session.d.ts +36 -0
  52. package/dist/lib/tooling/session.js +99 -0
  53. package/dist/lib/tooling/session.js.map +1 -0
  54. package/package.json +10 -12
@@ -1,105 +1,252 @@
1
1
  import { mkdir, readdir, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
2
+ import { basename, join, resolve } from "node:path";
3
3
  import { Args, Command, Flags } from "@oclif/core";
4
- // The `basic` template is vendored inline (no network fetch) — a minimal, buildable
5
- // extension: a GraphQL subgraph field plus a commented API-Extension example. Keep it
6
- // dependency-light so `extension build`/`validate` work on it out of the box.
7
- const TEMPLATE_FILES = {
8
- "src/extension.ts": `// A minimal integration-layer extension.
9
- //
10
- // Export a GraphQL subgraph (\`typeDefs\` + \`resolvers\`) to add fields to the graph,
11
- // and/or \`apiExtensions\` to run synchronous commercetools cart/order callbacks.
12
- // Build it with: commercetools integration-layer extension build
13
- // Serve it with: commercetools integration-layer extension serve
14
-
15
- export const typeDefs = /* GraphQL */ \`
16
- type Query {
17
- extensionInfo: String
18
- }
19
- \`;
20
-
21
- export const resolvers = {
22
- Query: {
23
- extensionInfo: () => "hello from the integration-layer extension",
24
- },
25
- };
26
-
27
- // // Example API Extension (uncomment to block a cart line item by SKU):
28
- // export const apiExtensions = [
29
- // {
30
- // key: "block-sku",
31
- // resourceTypeId: "cart",
32
- // actions: ["Create", "Update"],
33
- // handler: (input, ctx) => {
34
- // const blocked = ctx.config.BLOCKED_SKU ?? "BLOCKED-SKU";
35
- // const items = input.resource.obj?.lineItems ?? [];
36
- // if (items.some((li) => li.variant?.sku === blocked)) {
37
- // return { errors: [{ code: "SkuBlocked", message: \\\`\\\${blocked} is not purchasable\\\` }] };
38
- // }
39
- // return {};
40
- // },
41
- // },
42
- // ];
43
- `,
44
- "package.json": `{
45
- "name": "integration-layer-extension",
46
- "version": "0.0.0",
4
+ // `init` scaffolds a MONOREPO you own and grow — a pnpm workspace whose members are
5
+ // individual extensions (`extensions/*`), seeded with one buildable hello-world
6
+ // extension that extends `Query`. The build/validate/push flow itself lives in this
7
+ // published CLI plugin, not in the scaffold, so an extension package stays tiny (just
8
+ // `src/extension.ts` + a thin package.json/tsconfig). Everything is vendored inline —
9
+ // no network fetch — and kept dependency-light so the extension commands work on it
10
+ // out of the box. The `basic` template is the only one today.
11
+ /** Derive a valid npm package name from the scaffold directory (fallback: a generic one). */
12
+ function projectNameFor(dir) {
13
+ const sanitized = basename(resolve(dir))
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9-]+/g, "-")
16
+ .replace(/^-+|-+$/g, "");
17
+ return sanitized || "integration-layer-extensions";
18
+ }
19
+ /** The monorepo file set, parameterized by the root package name. */
20
+ function templateFiles(projectName) {
21
+ return {
22
+ "package.json": `{
23
+ "name": ${JSON.stringify(projectName)},
47
24
  "private": true,
48
25
  "type": "module",
26
+ "packageManager": "pnpm@11.17.0",
27
+ "engines": {
28
+ "node": "22.x || 24.x"
29
+ },
49
30
  "scripts": {
50
- "build": "commercetools integration-layer extension build",
51
- "serve": "commercetools integration-layer extension serve",
52
- "push": "commercetools integration-layer extension push"
31
+ "build": "commercetools integration-layer extension build --all",
32
+ "typecheck": "pnpm -r typecheck",
33
+ "lint": "eslint .",
34
+ "dev": "commercetools integration-layer extension serve --all",
35
+ "validate": "commercetools integration-layer extension validate --all",
36
+ "push": "commercetools integration-layer extension push --all"
53
37
  },
54
38
  "devDependencies": {
55
- "@commercetools/platform-sdk": "8.25.0",
56
- "graphql": "^16.9.0",
57
- "typescript": "^5.7.2"
39
+ "eslint": "^10.8.0",
40
+ "typescript": "~6.0.3",
41
+ "typescript-eslint": "^8.65.0"
58
42
  }
59
43
  }
60
44
  `,
61
- "tsconfig.json": `{
45
+ "pnpm-workspace.yaml": `packages:
46
+ # Each extension is a workspace member (edit its src/extension.ts). A project deploys
47
+ # ONE bundle, so the root \`pnpm build\` / \`validate\` / \`push\` scripts MERGE every
48
+ # extension here into that single combined bundle — never one per package.
49
+ - 'extensions/*'
50
+
51
+ # Refuse dependency versions published less than 24h ago. Minutes; 1440 = 24h.
52
+ minimumReleaseAge: 1440
53
+ `,
54
+ "tsconfig.base.json": `{
62
55
  "compilerOptions": {
63
56
  "target": "ES2022",
64
57
  "module": "NodeNext",
65
58
  "moduleResolution": "NodeNext",
66
59
  "strict": true,
60
+ "esModuleInterop": true,
67
61
  "skipLibCheck": true,
68
- "noEmit": true
69
- },
70
- "include": ["src/**/*"]
62
+ "forceConsistentCasingInFileNames": true,
63
+ "resolveJsonModule": true
64
+ }
71
65
  }
72
66
  `,
73
- ".env.example": `# The authenticated commands (validate/push/status/delete, schema, config) reuse the
74
- # token from \`commercetools auth login\` (as a manage_project client) — no credentials
75
- # go here. Optionally override the integration-layer edge URL. Copy to .env.
76
- INTEGRATION_LAYER_URL=
67
+ "eslint.config.js": `import tseslint from 'typescript-eslint';
68
+
69
+ export default tseslint.config(
70
+ { ignores: ['**/dist/**', '**/node_modules/**'] },
71
+ ...tseslint.configs.recommended,
72
+ {
73
+ // Allow \`_\`-prefixed unused args (resolver signatures often ignore arguments).
74
+ files: ['**/*.ts'],
75
+ rules: {
76
+ '@typescript-eslint/no-unused-vars': [
77
+ 'error',
78
+ {
79
+ argsIgnorePattern: '^_',
80
+ varsIgnorePattern: '^_',
81
+ caughtErrorsIgnorePattern: '^_',
82
+ },
83
+ ],
84
+ },
85
+ },
86
+ );
77
87
  `,
78
- ".gitignore": `node_modules/
88
+ ".gitignore": `node_modules/
79
89
  dist/
80
90
  .env
81
91
  `,
82
- "README.md": `# integration-layer extension
92
+ ".env.example": `# The authenticated commands (validate/push/status/delete, schema, config) reuse the
93
+ # token from \`commercetools auth login\` (as a manage_project client) — no credentials
94
+ # go here. Optionally override the integration-layer edge URL. Copy to .env.
95
+ INTEGRATION_LAYER_URL=
96
+ `,
97
+ "README.md": `# ${projectName}
98
+
99
+ A monorepo of commercetools integration-layer extensions, scaffolded by
100
+ \`commercetools integration-layer init\`. Each extension is a workspace member under
101
+ \`extensions/\`; the starter one (\`extensions/hello-world\`) adds a \`hello\` field to
102
+ \`Query\`.
103
+
104
+ A project deploys a **single bundle** — one federation subgraph the router composes
105
+ with the integration layer. So this repo's extensions are **merged into that one
106
+ bundle**: the root \`build\` / \`validate\` / \`push\` scripts combine everything under
107
+ \`extensions/\` and push it as one artifact (not one per package), and \`dev\` serves that
108
+ same merged subgraph locally. Split your code into as many \`extensions/*\` packages as
109
+ makes sense to author — they always ship as one.
110
+
111
+ ## Set up the CLI (once)
112
+
113
+ The \`build\`/\`serve\`/\`validate\`/\`push\` scripts run through the **commercetools CLI**'s
114
+ \`integration-layer\` topic, added by the published
115
+ \`@commercetools/cli-topic-integration-layer\` plugin. Install the CLI globally with
116
+ pnpm, add the plugin, then log in:
117
+
118
+ \`\`\`sh
119
+ # Install the commercetools CLI globally with pnpm, then add the integration-layer
120
+ # topic. The plugin is on the public npm registry, so no auth or scope mapping is
121
+ # needed. (Install from @dev for now — the \`plugins\` command is only in the CLI's dev
122
+ # prerelease; drop @dev once it ships to @latest.)
123
+ pnpm add -g @commercetools/cli@dev
124
+ commercetools plugins install @commercetools/cli-topic-integration-layer
125
+
126
+ # Log in once — mints the manage_project token the authenticated commands reuse
127
+ # (validate/push/status/delete, schema, config) and sets your target project.
128
+ commercetools auth login --project-key <your-project-key>
129
+ \`\`\`
130
+
131
+ ## Getting started
83
132
 
84
- Scaffolded by \`commercetools integration-layer init\`.
133
+ Everything runs through pnpm — the scripts below wrap the \`commercetools
134
+ integration-layer extension …\` commands.
85
135
 
86
- - \`commercetools integration-layer extension build\` — bundle \`src/extension.ts\`
87
- - \`commercetools integration-layer extension serve\` — local dev server
88
- - \`commercetools integration-layer extension push\` — validate + upload
136
+ \`\`\`sh
137
+ pnpm install
89
138
 
90
- Run \`commercetools auth login\` first — the authenticated commands (push/validate/
91
- status/delete, schema, config) reuse that token. Optionally copy \`.env.example\` to
92
- \`.env\` to override the integration-layer edge URL.
139
+ # From the repo root — these operate on the ONE combined bundle:
140
+ pnpm dev # merge every extension into one subgraph and serve it behind a local
141
+ # federated gateway (+ the integration layer) at
142
+ # http://localhost:4000/graphql — the production topology in miniature.
143
+ # Needs \`commercetools auth login\` (composes against your live schema).
144
+ pnpm validate # compose the combined bundle against the live schema (no upload)
145
+ pnpm push # validate + upload the combined bundle (replaces the project's one)
146
+
147
+ # …or focus on a single extension in isolation (standalone subgraph, no login needed):
148
+ pnpm --filter @extensions/hello-world dev
149
+ \`\`\`
150
+
151
+ With \`pnpm dev\` running, open http://localhost:4000/graphql for a GraphiQL over the
152
+ merged schema; the raw combined extensions subgraph is at \`/_extension\`, and the
153
+ browsable merged (extensions + integration layer) schema at \`/composed\`.
154
+
155
+ Optionally copy \`.env.example\` to \`.env\` to override the integration-layer edge URL.
156
+
157
+ ## Adding another extension
158
+
159
+ Copy \`extensions/hello-world\` to \`extensions/<your-extension>\`, rename it in its
160
+ \`package.json\`, and edit its \`src/extension.ts\`. The root scripts discover every folder
161
+ under \`extensions/\` automatically and merge it into the single bundle — so \`pnpm dev\`,
162
+ \`validate\`, and \`push\` all just pick it up, with no port wrangling. (Two extensions
163
+ can each add fields to \`Query\`; they only clash if they declare the *same* field.)
93
164
  `,
165
+ "extensions/hello-world/package.json": `{
166
+ "name": "@extensions/hello-world",
167
+ "version": "0.1.0",
168
+ "private": true,
169
+ "type": "module",
170
+ "scripts": {
171
+ "build": "commercetools integration-layer extension build",
172
+ "dev": "commercetools integration-layer extension serve",
173
+ "typecheck": "tsc --noEmit"
174
+ },
175
+ "devDependencies": {
176
+ "@types/node": "^22.20.1",
177
+ "typescript": "~6.0.3"
178
+ }
179
+ }
180
+ `,
181
+ "extensions/hello-world/tsconfig.json": `{
182
+ "extends": "../../tsconfig.base.json",
183
+ "compilerOptions": {
184
+ "noEmit": true,
185
+ "types": ["node"]
186
+ },
187
+ "include": ["src"],
188
+ "exclude": ["node_modules", "dist"]
189
+ }
190
+ `,
191
+ "extensions/hello-world/src/extension.ts": `/**
192
+ * A hello-world integration-layer extension — the PURELY ADDITIVE pattern: it adds a
193
+ * brand-new root field (\`Query.hello\`) and shares nothing with the integration layer,
194
+ * so the two \`Query\` types merge with no change to it and composition is trivial.
195
+ *
196
+ * An extension exports \`typeDefs\` (a federation-v2 SDL string) + \`resolvers\` and runs
197
+ * in a restricted runtime. Edit this file, then from the repo ROOT run \`pnpm validate\` /
198
+ * \`pnpm push\` — every extension is merged into the one bundle the project deploys (the
199
+ * target project comes from your \`commercetools auth login\`). Iterate on just this one
200
+ * with \`pnpm --filter @extensions/hello-world dev\`.
201
+ */
202
+
203
+ export const typeDefs = \`
204
+ extend schema @link(url: "https://specs.apollo.dev/federation/v2.3")
205
+
206
+ type Query {
207
+ "A friendly greeting contributed by this extension."
208
+ hello(name: String): String!
209
+ }
210
+ \`;
211
+
212
+ /**
213
+ * The per-call capability context the restricted runtime hands each resolver.
214
+ * \`now()\` is the current epoch millis (a convenience — \`Date.now()\` works too);
215
+ * \`config\` is this extension's merchant-supplied \`{ key: value }\` map, with secret
216
+ * values decrypted host-side (e.g. \`ctx.config.API_KEY\`). Set values with
217
+ * \`commercetools integration-layer config set <KEY> <VALUE>\`.
218
+ */
219
+ interface ExtensionContext {
220
+ now(): number;
221
+ config: Readonly<Record<string, string>>;
222
+ }
223
+
224
+ export const resolvers = {
225
+ Query: {
226
+ // A resolver receives (parent, args, context): \`_parent\` is the object the field
227
+ // hangs off (nothing here — this is a root field), \`args\` are the field arguments
228
+ // from the query, and \`ctx\` exposes the restricted runtime's host capabilities.
229
+ hello: (_parent: unknown, { name }: { name?: string }, ctx: ExtensionContext) => {
230
+ const greeting = ctx.config.GREETING ?? "Hello";
231
+ return \`\${greeting}, \${name ?? "world"}, from your integration-layer extension!\`;
232
+ },
233
+ },
94
234
  };
235
+ `,
236
+ };
237
+ }
95
238
  export default class Init extends Command {
96
- static description = "Scaffold an integration-layer extension project from a template";
239
+ static description = "Scaffold an integration-layer extensions monorepo (a pnpm workspace with one hello-world extension)";
97
240
  static examples = [
98
- "<%= config.bin %> integration-layer init my-extension",
99
- "<%= config.bin %> integration-layer init my-extension --template basic",
241
+ "<%= config.bin %> integration-layer init",
242
+ "<%= config.bin %> integration-layer init my-extensions",
243
+ "<%= config.bin %> integration-layer init my-extensions --template basic",
100
244
  ];
101
245
  static args = {
102
- directory: Args.string({ description: "directory to scaffold into", required: true }),
246
+ directory: Args.string({
247
+ description: "directory to scaffold the monorepo into (defaults to the current directory)",
248
+ default: ".",
249
+ }),
103
250
  };
104
251
  static flags = {
105
252
  template: Flags.string({
@@ -118,18 +265,22 @@ export default class Init extends Command {
118
265
  const dir = args.directory;
119
266
  const existing = await readdir(dir).catch(() => []);
120
267
  if (existing.length > 0 && !flags.force) {
121
- this.error(`Directory '${dir}' is not empty. Pass --force to scaffold into it anyway.`);
268
+ const what = dir === "." ? "The current directory" : `Directory '${dir}'`;
269
+ this.error(`${what} is not empty. Pass --force to scaffold into it anyway.`);
122
270
  }
123
- for (const [relPath, contents] of Object.entries(TEMPLATE_FILES)) {
271
+ const files = templateFiles(projectNameFor(dir));
272
+ for (const [relPath, contents] of Object.entries(files)) {
124
273
  const target = join(dir, relPath);
125
274
  await mkdir(join(target, ".."), { recursive: true });
126
275
  await writeFile(target, contents, "utf8");
127
276
  }
128
- this.log(`✓ scaffolded a '${flags.template}' extension in ${dir}`);
277
+ const location = dir === "." ? "the current directory" : dir;
278
+ this.log(`✓ scaffolded a '${flags.template}' extensions monorepo in ${location}`);
129
279
  this.log("Next steps:");
130
- this.log(` cd ${dir}`);
131
- this.log(" npm install");
132
- this.log(" commercetools integration-layer extension serve");
280
+ if (dir !== ".")
281
+ this.log(` cd ${dir}`);
282
+ this.log(" pnpm install");
283
+ this.log(" pnpm dev # run the hello-world extension locally");
133
284
  }
134
285
  }
135
286
  //# sourceMappingURL=init.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/integration-layer/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEnD,oFAAoF;AACpF,sFAAsF;AACtF,8EAA8E;AAC9E,MAAM,cAAc,GAA2B;IAC7C,kBAAkB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCrB;IACC,cAAc,EAAE;;;;;;;;;;;;;;;;CAgBjB;IACC,eAAe,EAAE;;;;;;;;;;;CAWlB;IACC,cAAc,EAAE;;;;CAIjB;IACC,YAAY,EAAE;;;CAGf;IACC,WAAW,EAAE;;;;;;;;;;;CAWd;CACA,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,OAAO;IACvC,MAAM,CAAU,WAAW,GAAG,iEAAiE,CAAC;IAEhG,MAAM,CAAU,QAAQ,GAAG;QACzB,uDAAuD;QACvD,wEAAwE;KACzE,CAAC;IAEF,MAAM,CAAU,IAAI,GAAG;QACrB,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KACtF,CAAC;IAEF,MAAM,CAAU,KAAK,GAAG;QACtB,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;YACrB,WAAW,EAAE,qBAAqB;YAClC,OAAO,EAAE,CAAC,OAAO,CAAC;YAClB,OAAO,EAAE,OAAO;SACjB,CAAC;QACF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,qCAAqC;YAClD,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QAE3B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAc,CAAC,CAAC;QAChE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,0DAA0D,CAAC,CAAC;QAC1F,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YACjE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAClC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,mBAAmB,KAAK,CAAC,QAAQ,kBAAkB,GAAG,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IAChE,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/integration-layer/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type ExtensionMeta } from "./ilClient.js";
2
+ /** How the wait ended. Only `failed` means "the push produced a broken bundle". */
3
+ export type BundleOutcome = {
4
+ kind: "running";
5
+ meta: ExtensionMeta;
6
+ } | {
7
+ kind: "failed";
8
+ meta: ExtensionMeta;
9
+ } | {
10
+ kind: "superseded";
11
+ meta: ExtensionMeta;
12
+ } | {
13
+ kind: "unknown";
14
+ reason: string;
15
+ meta?: ExtensionMeta;
16
+ };
17
+ export interface AwaitOptions {
18
+ /** Give up after this long. */
19
+ timeoutMs: number;
20
+ /** Gap between metadata reads. */
21
+ intervalMs: number;
22
+ /** Injected so tests don't actually sleep. */
23
+ sleep?: (ms: number) => Promise<void>;
24
+ /** Injected so tests control elapsed time. */
25
+ now?: () => number;
26
+ }
27
+ /**
28
+ * Poll until `version` leaves `pending`, or we run out of time.
29
+ *
30
+ * The first read happens immediately — an extension that was already mid-poll can
31
+ * settle the version before we ever sleep.
32
+ */
33
+ export declare function awaitBundleState(baseUrl: string, projectKey: string, token: string, version: number, options: AwaitOptions): Promise<BundleOutcome>;
@@ -0,0 +1,77 @@
1
+ // Wait for a just-pushed bundle version to prove itself.
2
+ //
3
+ // A push only STORES a bundle. The extension picks it up on its own poll, tries to
4
+ // load it, and reports back — so the version starts `pending` and only becomes
5
+ // `running` (it loaded and published its schema) or `failed` (it didn't, with a
6
+ // reason) a little later. Until this, `push` reported success the moment the bytes
7
+ // landed, which meant a bundle the sandbox refuses to run looked like a clean push.
8
+ //
9
+ // This polls the project's bundle metadata until the pushed version settles, and
10
+ // hands the caller back an outcome it can turn into an exit code.
11
+ //
12
+ // Three things it deliberately does NOT do:
13
+ //
14
+ // - It never treats "I couldn't find out" as a failure. An integration layer that
15
+ // predates bundle state reports no `state` at all, and a project whose extension
16
+ // isn't deployed yet will never move off `pending`. Both are `unknown`, not
17
+ // `failed` — failing a push for them would break perfectly good workflows.
18
+ // - It doesn't watch for the version to change under it. If someone else pushes
19
+ // while we wait, the newest version is no longer ours; we say so and stop rather
20
+ // than report on somebody else's push.
21
+ // - It doesn't retry transient metadata-read errors into a verdict. A read that
22
+ // throws ends the wait as `unknown` with the error attached.
23
+ import { fetchExtensionMeta } from "./ilClient.js";
24
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
25
+ /**
26
+ * Poll until `version` leaves `pending`, or we run out of time.
27
+ *
28
+ * The first read happens immediately — an extension that was already mid-poll can
29
+ * settle the version before we ever sleep.
30
+ */
31
+ export async function awaitBundleState(baseUrl, projectKey, token, version, options) {
32
+ const sleep = options.sleep ?? defaultSleep;
33
+ const now = options.now ?? Date.now;
34
+ const deadline = now() + options.timeoutMs;
35
+ for (;;) {
36
+ let meta;
37
+ try {
38
+ meta = await fetchExtensionMeta(baseUrl, projectKey, token);
39
+ }
40
+ catch (err) {
41
+ return {
42
+ kind: "unknown",
43
+ reason: `could not read the stored bundle's status: ${err.message}`,
44
+ };
45
+ }
46
+ if (meta === null) {
47
+ // Deleted out from under us between the push and this read.
48
+ return { kind: "unknown", reason: "no bundle is stored for this project any more" };
49
+ }
50
+ if (meta.state === undefined) {
51
+ // An integration layer that doesn't report state. Nothing to wait for.
52
+ return {
53
+ kind: "unknown",
54
+ reason: "this integration layer does not report bundle status",
55
+ meta,
56
+ };
57
+ }
58
+ if (meta.version !== version) {
59
+ // Someone else pushed while we were waiting; the newest version isn't ours.
60
+ return { kind: "superseded", meta };
61
+ }
62
+ if (meta.state === "running")
63
+ return { kind: "running", meta };
64
+ if (meta.state === "failed")
65
+ return { kind: "failed", meta };
66
+ // Still pending.
67
+ if (now() >= deadline) {
68
+ return {
69
+ kind: "unknown",
70
+ reason: `the extension did not report on version ${version} within ${Math.round(options.timeoutMs / 1000)}s`,
71
+ meta,
72
+ };
73
+ }
74
+ await sleep(options.intervalMs);
75
+ }
76
+ }
77
+ //# sourceMappingURL=awaitBundleState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"awaitBundleState.js","sourceRoot":"","sources":["../../src/lib/awaitBundleState.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,mFAAmF;AACnF,+EAA+E;AAC/E,gFAAgF;AAChF,mFAAmF;AACnF,oFAAoF;AACpF,EAAE;AACF,iFAAiF;AACjF,kEAAkE;AAClE,EAAE;AACF,4CAA4C;AAC5C,EAAE;AACF,oFAAoF;AACpF,qFAAqF;AACrF,gFAAgF;AAChF,+EAA+E;AAC/E,kFAAkF;AAClF,qFAAqF;AACrF,2CAA2C;AAC3C,kFAAkF;AAClF,iEAAiE;AAEjE,OAAO,EAAE,kBAAkB,EAAsB,MAAM,eAAe,CAAC;AAoBvE,MAAM,YAAY,GAAG,CAAC,EAAU,EAAiB,EAAE,CACjD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,UAAkB,EAClB,KAAa,EACb,OAAe,EACf,OAAqB;IAErB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;IAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAE3C,SAAS,CAAC;QACR,IAAI,IAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,8CAA+C,GAAa,CAAC,OAAO,EAAE;aAC/E,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,4DAA4D;YAC5D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC;QACtF,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,uEAAuE;YACvE,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,sDAAsD;gBAC9D,IAAI;aACL,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAC7B,4EAA4E;YAC5E,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAE7D,iBAAiB;QACjB,IAAI,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACtB,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,2CAA2C,OAAO,WAAW,IAAI,CAAC,KAAK,CAC7E,OAAO,CAAC,SAAS,GAAG,IAAI,CACzB,GAAG;gBACJ,IAAI;aACL,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;AACH,CAAC"}
@@ -1,4 +1,11 @@
1
1
  import { AuthCommand, CtpAuthorizedClient } from "@commercetools/cli-plugin-auth";
2
+ /**
3
+ * Reconstruct the logged-in principal from the persisted credentials file. Returns
4
+ * undefined when no valid client-credentials login is on disk (absent file, or a
5
+ * different auth kind), so unauthorized commands still run. No network. Exported for
6
+ * tests; commands go through {@link IntegrationLayerCommand.requirePrincipal}.
7
+ */
8
+ export declare function loadPersistedPrincipal(credentialsFile?: string): Promise<CtpAuthorizedClient | undefined>;
2
9
  /** The resolved, ready-to-use integration-layer context: where to call and as whom. */
3
10
  export interface IlContext {
4
11
  /** The integration-layer edge base URL, trailing slashes trimmed. */
@@ -26,6 +33,21 @@ export interface IlFlagValues {
26
33
  * only when the region is absent, so resolveIlContext then fails loudly.
27
34
  */
28
35
  export declare function edgeUrlForRegion(region: string): string | undefined;
36
+ /**
37
+ * The GRAPHQL edge (the integration-router) for a region — where operations are
38
+ * actually served, at `/{project}/graphql`. A distinct host from the extensions
39
+ * edge above: the extensions edge is the connector backend (manage_project routes),
40
+ * the graphql edge is the router. `--graphql-url` / `IL_GRAPHQL_URL` overrides it
41
+ * for staging zones, which don't follow the production convention.
42
+ */
43
+ export declare function graphqlEdgeUrlForRegion(region: string): string | undefined;
44
+ /**
45
+ * The IDENTITY edge (the storefront pod) for a region — where sessions are minted,
46
+ * at `POST /{project}/session`. Again a distinct host from both of the above; the
47
+ * four-pod split put shopper identity, shopper GraphQL, and the machine surface on
48
+ * separate ingresses. `--auth-url` / `IL_AUTH_URL` overrides it.
49
+ */
50
+ export declare function authEdgeUrlForRegion(region: string): string | undefined;
29
51
  export declare abstract class IntegrationLayerCommand extends AuthCommand {
30
52
  /**
31
53
  * Whether the command needs a logged-in principal. True for every command that
@@ -34,11 +56,24 @@ export declare abstract class IntegrationLayerCommand extends AuthCommand {
34
56
  * to fail loudly if the network path is actually taken.
35
57
  */
36
58
  protected authorized: boolean;
59
+ /**
60
+ * The principal loaded from `~/.commercetools/credentials` in {@link init}, or
61
+ * undefined when no valid login is persisted. Resolved once per command run so the
62
+ * synchronous {@link requirePrincipal} call sites (here and in `explore`) are unchanged.
63
+ */
64
+ private persistedPrincipal?;
37
65
  static baseFlags: {
38
66
  "integration-layer-url": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
39
67
  "project-key": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
40
68
  };
41
69
  protected init(): Promise<void>;
70
+ /**
71
+ * Read the persisted login from `~/.commercetools/credentials`. Returns undefined when
72
+ * no login is on disk (so unauthorized commands like standalone `serve` still run) and
73
+ * only fails loudly when the file is present but malformed. No network — the token was
74
+ * already minted by `auth login` and serialized whole.
75
+ */
76
+ private loadPersistedPrincipal;
42
77
  /** The logged-in commercetools principal, or an `AuthorizationError` pointing at `auth login`. */
43
78
  protected requirePrincipal(): CtpAuthorizedClient;
44
79
  /**