@jtl-software/create-cloud-app 0.5.2 → 0.5.4

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 (2) hide show
  1. package/dist/index.js +130 -39
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -694,6 +694,29 @@ function detectFrontend(root) {
694
694
  const frontendDir = path2.join(root, "packages", "frontend");
695
695
  return fs2.existsSync(path2.join(frontendDir, "package.json")) ? frontendDir : null;
696
696
  }
697
+ function readScaffoldStack(root) {
698
+ try {
699
+ const pkg = JSON.parse(
700
+ fs2.readFileSync(path2.join(root, "package.json"), "utf-8")
701
+ );
702
+ const meta = pkg.jtlCloudApp;
703
+ if (meta && typeof meta === "object") {
704
+ return {
705
+ backend: typeof meta.backend === "string" ? meta.backend : null,
706
+ frontend: typeof meta.frontend === "string" ? meta.frontend : null
707
+ };
708
+ }
709
+ } catch {
710
+ }
711
+ return null;
712
+ }
713
+ function environmentFromApi(api) {
714
+ const h = api.toLowerCase();
715
+ if (h.includes("api.qa.")) return "qa";
716
+ if (h.includes("api.dev.")) return "dev";
717
+ if (h.includes("api.jtl-cloud.com")) return "prod";
718
+ return "other";
719
+ }
697
720
  function writeFrontendPublicClient(root, authIssuer, publicClientId) {
698
721
  const frontendDir = detectFrontend(root);
699
722
  if (!frontendDir) return null;
@@ -1002,6 +1025,34 @@ async function provisionApp(apiBaseUrl, jwt, tenantId, appId, appServicePath = "
1002
1025
  publicClientId: provisioned.credentials?.publicClient?.clientId
1003
1026
  };
1004
1027
  }
1028
+ async function applyProvisionResult(root, backend, opts, tenant, appId, provisioned, isInstalled) {
1029
+ if (provisioned.publicClientId) {
1030
+ const written = writeFrontendPublicClient(
1031
+ root,
1032
+ opts.auth,
1033
+ provisioned.publicClientId
1034
+ );
1035
+ if (written) {
1036
+ p2.log.success(
1037
+ `Wrote VITE_JTL_CLIENT_ID/VITE_JTL_ISSUER to ${pc2.cyan(path2.relative(root, written))}.`
1038
+ );
1039
+ }
1040
+ }
1041
+ writeVerifyDemoEnv(root, backend);
1042
+ if (backend) {
1043
+ await persistCredentials(
1044
+ backend,
1045
+ provisioned.clientId,
1046
+ provisioned.clientSecret,
1047
+ root,
1048
+ opts,
1049
+ { hub: opts.hub, tenantSlug: tenant.slug, appId, isInstalled }
1050
+ );
1051
+ return;
1052
+ }
1053
+ printHubLink(opts.hub, tenant.slug, appId, isInstalled);
1054
+ p2.outro(pc2.green("Done. App registered."));
1055
+ }
1005
1056
  function credentialLabels(backend) {
1006
1057
  if (backend.kind === "node") {
1007
1058
  return {
@@ -1092,6 +1143,18 @@ async function register(argv = [], runOpts = {}) {
1092
1143
  }
1093
1144
  const manifest = readManifest(path2.join(root, "app.json"));
1094
1145
  const summary = summarizeManifest(manifest);
1146
+ const stack = readScaffoldStack(root);
1147
+ const auth = manifest.authentication;
1148
+ const hasFrontend = stack ? !!stack.frontend : !!auth?.publicClient;
1149
+ const hasBackend = stack ? !!stack.backend : !!auth?.serviceAccount;
1150
+ const registerTelemetry = {
1151
+ environment: environmentFromApi(opts.api),
1152
+ has_frontend: hasFrontend,
1153
+ has_backend: hasBackend,
1154
+ backend_tech: stack?.backend ?? (hasBackend ? "unknown" : "none"),
1155
+ frontend_tech: stack?.frontend ?? (hasFrontend ? "unknown" : "none"),
1156
+ composition: hasFrontend && hasBackend ? "both" : hasFrontend ? "frontend_only" : hasBackend ? "backend_only" : "none"
1157
+ };
1095
1158
  for (; ; ) {
1096
1159
  const identityJwt = jwt;
1097
1160
  const apiJwt = apiTokenFor(jwt);
@@ -1181,6 +1244,33 @@ async function register(argv = [], runOpts = {}) {
1181
1244
  spinner4.stop("Failed to update manifest");
1182
1245
  bail(err.message);
1183
1246
  }
1247
+ if (existing.provisioningStatus === "DRAFT") {
1248
+ spinner4.start("Provisioning app");
1249
+ let provisioned;
1250
+ try {
1251
+ provisioned = await provisionApp(
1252
+ opts.api,
1253
+ apiJwt,
1254
+ tenant.id,
1255
+ existing.id,
1256
+ opts.appServicePath
1257
+ );
1258
+ spinner4.stop("Provisioned");
1259
+ } catch (err) {
1260
+ spinner4.stop("Failed to provision app");
1261
+ bail(err.message);
1262
+ }
1263
+ await applyProvisionResult(
1264
+ root,
1265
+ backend,
1266
+ opts,
1267
+ tenant,
1268
+ existing.id,
1269
+ provisioned,
1270
+ existing.isInstalled ?? false
1271
+ );
1272
+ return { ...registerTelemetry, is_new_app: false };
1273
+ }
1184
1274
  if (existing.publicClientId) {
1185
1275
  const written = writeFrontendPublicClient(
1186
1276
  root,
@@ -1211,7 +1301,7 @@ async function register(argv = [], runOpts = {}) {
1211
1301
  `Manifest updated. Your backend config is missing ${labels.pair} \u2014 they can't be recovered. Re-create the app to get a fresh secret.`
1212
1302
  )
1213
1303
  );
1214
- return;
1304
+ return { ...registerTelemetry, is_new_app: false };
1215
1305
  }
1216
1306
  p2.outro(
1217
1307
  pc2.green(
@@ -1221,7 +1311,7 @@ async function register(argv = [], runOpts = {}) {
1221
1311
  } else {
1222
1312
  p2.outro(pc2.green("Manifest updated."));
1223
1313
  }
1224
- return;
1314
+ return { ...registerTelemetry, is_new_app: false };
1225
1315
  }
1226
1316
  spinner4.start("Creating draft app");
1227
1317
  let appId;
@@ -1255,32 +1345,16 @@ async function register(argv = [], runOpts = {}) {
1255
1345
  spinner4.stop("Failed to provision app");
1256
1346
  bail(err.message);
1257
1347
  }
1258
- if (publicClientId) {
1259
- const written = writeFrontendPublicClient(
1260
- root,
1261
- opts.auth,
1262
- publicClientId
1263
- );
1264
- if (written) {
1265
- p2.log.success(
1266
- `Wrote VITE_JTL_CLIENT_ID/VITE_JTL_ISSUER to ${pc2.cyan(path2.relative(root, written))}.`
1267
- );
1268
- }
1269
- }
1270
- writeVerifyDemoEnv(root, backend);
1271
- if (backend) {
1272
- await persistCredentials(backend, clientId, clientSecret, root, opts, {
1273
- hub: opts.hub,
1274
- tenantSlug: tenant.slug,
1275
- appId,
1276
- // A freshly created app can't be installed yet.
1277
- isInstalled: false
1278
- });
1279
- return;
1280
- }
1281
- printHubLink(opts.hub, tenant.slug, appId, false);
1282
- p2.outro(pc2.green("Done. App registered."));
1283
- return;
1348
+ await applyProvisionResult(
1349
+ root,
1350
+ backend,
1351
+ opts,
1352
+ tenant,
1353
+ appId,
1354
+ { clientId, clientSecret, publicClientId },
1355
+ false
1356
+ );
1357
+ return { ...registerTelemetry, is_new_app: true };
1284
1358
  }
1285
1359
  }
1286
1360
 
@@ -1573,6 +1647,12 @@ async function pickFrontendPort() {
1573
1647
  }
1574
1648
  return DEFAULT_FRONTEND_PORT;
1575
1649
  }
1650
+ function templateTech(template) {
1651
+ const m = template.packageName.match(
1652
+ /cloud-app-template-(?:backend|frontend)-(.+)$/
1653
+ );
1654
+ return m?.[1] ?? template.packageName;
1655
+ }
1576
1656
  function toPascalCase(kebab) {
1577
1657
  return kebab.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1578
1658
  }
@@ -1777,6 +1857,12 @@ async function scaffold({
1777
1857
  packageManager: "npm@10.8.2",
1778
1858
  devDependencies: {
1779
1859
  turbo: "^2.5.5"
1860
+ },
1861
+ // The stack the developer picked, recorded so `register` can report it in telemetry without
1862
+ // guessing from the generated files.
1863
+ jtlCloudApp: {
1864
+ backend: backend ? templateTech(backend) : null,
1865
+ frontend: frontend ? templateTech(frontend) : null
1780
1866
  }
1781
1867
  };
1782
1868
  fs4.writeFileSync(
@@ -1823,7 +1909,7 @@ import os2 from "os";
1823
1909
  import path6 from "path";
1824
1910
  import { PostHog } from "posthog-node";
1825
1911
  var TOKEN = "phc_SkFAxTIa4RUQxotci1Ftdw7xefZK2Ax0LEIGcl8zuD2";
1826
- var CLI_VERSION = "0.5.2";
1912
+ var CLI_VERSION = "0.5.4";
1827
1913
  var client = TOKEN ? new PostHog(TOKEN, { host: "https://e.jtl-cloud.com" }) : null;
1828
1914
  function telemetryDisabledByEnv() {
1829
1915
  const dnt = process.env.DO_NOT_TRACK;
@@ -1892,6 +1978,18 @@ async function shutdownPostHog() {
1892
1978
 
1893
1979
  // src/index.ts
1894
1980
  var NONE = "__none__";
1981
+ function scaffoldChoice(backend, frontend, next) {
1982
+ const hasBackend = Boolean(backend);
1983
+ const hasFrontend = Boolean(frontend);
1984
+ return {
1985
+ next,
1986
+ backend_tech: hasBackend ? templateTech(backend) : "none",
1987
+ frontend_tech: hasFrontend ? templateTech(frontend) : "none",
1988
+ has_backend: hasBackend,
1989
+ has_frontend: hasFrontend,
1990
+ composition: hasFrontend && hasBackend ? "both" : hasFrontend ? "frontend_only" : hasBackend ? "backend_only" : "none"
1991
+ };
1992
+ }
1895
1993
  function templateSelectOptions(templates) {
1896
1994
  return templates.map((t) => ({
1897
1995
  value: t.packageName,
@@ -1968,11 +2066,7 @@ async function runNextScaffold(backends, frontends, shared) {
1968
2066
  }
1969
2067
  printNextSteps(answers.appName);
1970
2068
  p4.outro(pc4.green("Done! Happy building."));
1971
- return {
1972
- next: true,
1973
- frontend: selectedFrontend ? selectedFrontend.meta.label ?? selectedFrontend.packageName : "none",
1974
- backend: selectedBackend ? selectedBackend.meta.label ?? selectedBackend.packageName : "none"
1975
- };
2069
+ return scaffoldChoice(selectedBackend, selectedFrontend, true);
1976
2070
  }
1977
2071
  async function runScaffold() {
1978
2072
  console.log();
@@ -2037,10 +2131,7 @@ async function runScaffold() {
2037
2131
  s.stop("Project scaffolded");
2038
2132
  printNextSteps(answers.appName);
2039
2133
  p4.outro(pc4.green("Done! Happy building."));
2040
- return {
2041
- backend: selectedBackend.meta.label ?? selectedBackend.packageName,
2042
- frontend: selectedFrontend.meta.label ?? selectedFrontend.packageName
2043
- };
2134
+ return scaffoldChoice(selectedBackend, selectedFrontend, false);
2044
2135
  }
2045
2136
  function printNextSteps(appName) {
2046
2137
  p4.note(
@@ -2060,7 +2151,7 @@ async function main() {
2060
2151
  try {
2061
2152
  let detail = {};
2062
2153
  if (command === "register") {
2063
- await register(process.argv.slice(3));
2154
+ detail = await register(process.argv.slice(3));
2064
2155
  } else if (command === "listing") {
2065
2156
  await listing(process.argv.slice(3));
2066
2157
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jtl-software/create-cloud-app",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "type": "module",
5
5
  "description": "CLI tool for scaffolding JTL Platform cloud apps",
6
6
  "bin": {
@@ -21,10 +21,10 @@
21
21
  "@jtl-software/cloud-app-template-backend-dotnet": "0.0.13",
22
22
  "@jtl-software/cloud-app-template-backend-node": "0.0.16",
23
23
  "@jtl-software/cloud-app-template-backend-php": "0.1.3",
24
- "@jtl-software/cloud-app-template-frontend-react": "0.1.2",
24
+ "@jtl-software/cloud-app-template-frontend-react": "0.1.3",
25
25
  "@jtl-software/cloud-app-template-shared": "0.1.2",
26
26
  "picocolors": "^1.1.1",
27
- "posthog-node": "^5.48.1"
27
+ "posthog-node": "^5.49.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.2.0",