@jtl-software/create-cloud-app 0.7.0 → 0.8.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 (3) hide show
  1. package/README.md +9 -1
  2. package/dist/index.js +148 -109
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -40,6 +40,14 @@ npm run register
40
40
  npm run dev
41
41
  ```
42
42
 
43
+ Pass `--legacy` to scaffold the backend for the legacy Ory M2M flow instead of the Zitadel service account (compat testing only, node backend only):
44
+
45
+ ```bash
46
+ npm create @jtl-software/cloud-app@latest -- --legacy
47
+ ```
48
+
49
+ `npm run register` then writes the app's Ory client credentials, and the backend mints its M2M token against the Ory auth host. Everything else is unchanged: the backend still verifies the incoming app token and reads the tenant id from it.
50
+
43
51
  The generated project includes:
44
52
 
45
53
  - A monorepo wired up with [Turborepo](https://turbo.build/)
@@ -65,7 +73,7 @@ Flags:
65
73
  | Flag | Description |
66
74
  | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67
75
  | `--api-host <url>` | API host. Default `https://api.jtl-cloud.com`. |
68
- | `--auth-host <url>` | OAuth host. Default `https://auth.jtl-cloud.com`. The CLI picks the matching client ID for the known prod/QA/dev hosts. |
76
+ | `--auth-host <url>` | OAuth host. Default `https://id.jtl-cloud.com`. The CLI picks the matching client ID for the known prod/QA/dev hosts. |
69
77
  | `--hub-host <url>` | Hub host (used in printed links). Default `https://hub.jtl-cloud.com`. |
70
78
  | `--client-id <id>` | Override the OAuth client ID. Only needed for custom auth hosts. |
71
79
  | `--scope <scope>` | OAuth scope. Default `openid offline_access`. |
package/dist/index.js CHANGED
@@ -703,7 +703,8 @@ function readScaffoldStack(root) {
703
703
  if (meta && typeof meta === "object") {
704
704
  return {
705
705
  backend: typeof meta.backend === "string" ? meta.backend : null,
706
- frontend: typeof meta.frontend === "string" ? meta.frontend : null
706
+ frontend: typeof meta.frontend === "string" ? meta.frontend : null,
707
+ legacy: meta.legacy === true
707
708
  };
708
709
  }
709
710
  } catch {
@@ -764,19 +765,18 @@ function readExistingCredentials(backend) {
764
765
  function authBaseFromApi(apiHost) {
765
766
  return apiHost.replace(/:\/\/api\./, "://auth.").replace(/\/$/, "");
766
767
  }
767
- function writeBackendCredentials(backend, clientId, clientSecret, issuer, apiBase, appId) {
768
- const hosts = apiBase ? {
769
- JTL_API_BASE: apiBase.replace(/\/$/, ""),
770
- JTL_AUTH_BASE: authBaseFromApi(apiBase)
771
- } : {};
768
+ function writeBackendCredentials(backend, clientId, clientSecret, issuer, apiBase, appId, authBase) {
769
+ const hosts = apiBase ? { JTL_API_BASE: apiBase.replace(/\/$/, "") } : {};
772
770
  if (backend.kind === "node") {
773
- upsertEnv(backend.envFile, {
771
+ const updates = {
774
772
  CLIENT_ID: clientId,
775
- CLIENT_SECRET: clientSecret,
776
- ...issuer ? { JTL_ISSUER: issuer } : {},
777
- ...appId ? { JTL_APP_ID: appId } : {},
778
- ...hosts
779
- });
773
+ CLIENT_SECRET: clientSecret
774
+ };
775
+ if (issuer) updates.JTL_ISSUER = issuer;
776
+ if (appId) updates.JTL_APP_ID = appId;
777
+ Object.assign(updates, hosts);
778
+ if (authBase) updates.JTL_AUTH_BASE = authBase.replace(/\/$/, "");
779
+ upsertEnv(backend.envFile, updates);
780
780
  return backend.envFile;
781
781
  }
782
782
  let settings = {};
@@ -791,7 +791,8 @@ function writeBackendCredentials(backend, clientId, clientSecret, issuer, apiBas
791
791
  ...settings.JtlPlatform ?? {},
792
792
  ClientId: clientId,
793
793
  ClientSecret: clientSecret,
794
- ...issuer ? { Issuer: issuer } : {}
794
+ ...issuer ? { Issuer: issuer } : {},
795
+ ...appId ? { AppId: appId } : {}
795
796
  };
796
797
  fs2.writeFileSync(
797
798
  backend.settingsFile,
@@ -968,7 +969,8 @@ async function listTenantApps(apiBaseUrl, jwt, tenantId, appServicePath = "app-s
968
969
  clientId: entry.clientId,
969
970
  provisioningStatus: entry.activeVersion?.provisioningStatus,
970
971
  isInstalled: entry.isInstalled,
971
- publicClientId: entry.credentials?.publicClient?.clientId
972
+ publicClientId: entry.credentials?.publicClient?.clientId,
973
+ serviceAccountClientId: entry.credentials?.serviceAccount?.clientId
972
974
  };
973
975
  }).filter((a) => a !== null);
974
976
  }
@@ -1029,13 +1031,30 @@ async function provisionApp(apiBaseUrl, jwt, tenantId, appId, appServicePath = "
1029
1031
  `provision response missing clientId or clientSecret: ${JSON.stringify(provisioned)}`
1030
1032
  );
1031
1033
  }
1034
+ const sa = provisioned.credentials?.serviceAccount;
1032
1035
  return {
1033
1036
  clientId,
1034
1037
  clientSecret,
1035
- publicClientId: provisioned.credentials?.publicClient?.clientId
1038
+ publicClientId: provisioned.credentials?.publicClient?.clientId,
1039
+ serviceAccount: sa?.clientId && sa.clientSecret ? { clientId: sa.clientId, clientSecret: sa.clientSecret } : void 0
1036
1040
  };
1037
1041
  }
1038
- async function applyProvisionResult(root, backend, opts, tenant, appId, provisioned, isInstalled) {
1042
+ function resolveBackendAuth(provisioned, legacy = false) {
1043
+ if (legacy) {
1044
+ return {
1045
+ clientId: provisioned.clientId,
1046
+ clientSecret: provisioned.clientSecret
1047
+ };
1048
+ }
1049
+ const sa = provisioned.serviceAccount;
1050
+ if (!sa) {
1051
+ throw new Error(
1052
+ "Provision response did not include a service account; the backend cannot authenticate."
1053
+ );
1054
+ }
1055
+ return { clientId: sa.clientId, clientSecret: sa.clientSecret };
1056
+ }
1057
+ async function applyProvisionResult(root, backend, opts, tenant, appId, provisioned, isInstalled, legacy) {
1039
1058
  if (provisioned.publicClientId) {
1040
1059
  const written = writeFrontendPublicClient(
1041
1060
  root,
@@ -1051,13 +1070,14 @@ async function applyProvisionResult(root, backend, opts, tenant, appId, provisio
1051
1070
  }
1052
1071
  writeVerifyDemoEnv(root, backend);
1053
1072
  if (backend) {
1073
+ const auth = resolveBackendAuth(provisioned, legacy);
1054
1074
  await persistCredentials(
1055
1075
  backend,
1056
- provisioned.clientId,
1057
- provisioned.clientSecret,
1076
+ auth.clientId,
1077
+ auth.clientSecret,
1058
1078
  root,
1059
1079
  opts,
1060
- { hub: opts.hub, tenantSlug: tenant.slug, appId, isInstalled }
1080
+ { hub: opts.hub, tenantSlug: tenant.slug, appId, isInstalled, legacy }
1061
1081
  );
1062
1082
  return;
1063
1083
  }
@@ -1109,7 +1129,8 @@ async function persistCredentials(backend, clientId, clientSecret, root, opts, c
1109
1129
  clientSecret,
1110
1130
  opts.auth,
1111
1131
  opts.api,
1112
- context.appId
1132
+ context.appId,
1133
+ context.legacy ? authBaseFromApi(opts.api) : void 0
1113
1134
  );
1114
1135
  printHubLink(
1115
1136
  context.hub,
@@ -1157,11 +1178,18 @@ async function register(argv = [], runOpts = {}) {
1157
1178
  const manifest = readManifest(path2.join(root, "app.json"));
1158
1179
  const summary = summarizeManifest(manifest);
1159
1180
  const stack = readScaffoldStack(root);
1181
+ const legacy = stack?.legacy ?? false;
1160
1182
  const auth = manifest.authentication;
1183
+ if (!legacy && backend && !auth?.serviceAccount) {
1184
+ bail(
1185
+ "This project has a backend but app.json declares no authentication.serviceAccount. Add it (or re-scaffold) before registering, so the backend gets a Zitadel service account."
1186
+ );
1187
+ }
1161
1188
  const hasFrontend = stack ? !!stack.frontend : !!auth?.publicClient;
1162
1189
  const hasBackend = stack ? !!stack.backend : !!auth?.serviceAccount;
1163
1190
  const registerTelemetry = {
1164
1191
  environment: environmentFromApi(opts.api),
1192
+ legacy,
1165
1193
  has_frontend: hasFrontend,
1166
1194
  has_backend: hasBackend,
1167
1195
  backend_tech: stack?.backend ?? (hasBackend ? "unknown" : "none"),
@@ -1170,7 +1198,7 @@ async function register(argv = [], runOpts = {}) {
1170
1198
  };
1171
1199
  for (; ; ) {
1172
1200
  const identityJwt = jwt;
1173
- const apiJwt = apiTokenFor(jwt);
1201
+ const apiJwt = jwt;
1174
1202
  spinner4.start("Fetching tenants");
1175
1203
  let tenants;
1176
1204
  try {
@@ -1259,9 +1287,9 @@ async function register(argv = [], runOpts = {}) {
1259
1287
  }
1260
1288
  if (existing.provisioningStatus === "DRAFT") {
1261
1289
  spinner4.start("Provisioning app");
1262
- let provisioned;
1290
+ let provisioned2;
1263
1291
  try {
1264
- provisioned = await provisionApp(
1292
+ provisioned2 = await provisionApp(
1265
1293
  opts.api,
1266
1294
  apiJwt,
1267
1295
  tenant.id,
@@ -1279,8 +1307,9 @@ async function register(argv = [], runOpts = {}) {
1279
1307
  opts,
1280
1308
  tenant,
1281
1309
  existing.id,
1282
- provisioned,
1283
- existing.isInstalled ?? false
1310
+ provisioned2,
1311
+ existing.isInstalled ?? false,
1312
+ legacy
1284
1313
  );
1285
1314
  return { ...registerTelemetry, is_new_app: false };
1286
1315
  }
@@ -1316,6 +1345,18 @@ async function register(argv = [], runOpts = {}) {
1316
1345
  );
1317
1346
  return { ...registerTelemetry, is_new_app: false };
1318
1347
  }
1348
+ if (!legacy && localCreds.clientId !== existing.serviceAccountClientId) {
1349
+ p2.outro(
1350
+ pc2.yellow(
1351
+ `Manifest updated, but your ${labels.pair} in ${pc2.cyan(path2.relative(root, envFile))} need to be migrated to the new authentication platform. Go to the Partner Portal (${pc2.cyan(opts.partner)}) and follow the migration instructions to get your new ${labels.pair}.`
1352
+ )
1353
+ );
1354
+ return {
1355
+ ...registerTelemetry,
1356
+ is_new_app: false,
1357
+ credential_migration_required: true
1358
+ };
1359
+ }
1319
1360
  p2.outro(
1320
1361
  pc2.green(
1321
1362
  `Manifest updated. Existing ${labels.pair} in ${pc2.cyan(path2.relative(root, envFile))} kept as-is.`
@@ -1342,17 +1383,15 @@ async function register(argv = [], runOpts = {}) {
1342
1383
  bail(err.message);
1343
1384
  }
1344
1385
  spinner4.start("Provisioning app");
1345
- let clientId;
1346
- let clientSecret;
1347
- let publicClientId;
1386
+ let provisioned;
1348
1387
  try {
1349
- ({ clientId, clientSecret, publicClientId } = await provisionApp(
1388
+ provisioned = await provisionApp(
1350
1389
  opts.api,
1351
1390
  apiJwt,
1352
1391
  tenant.id,
1353
1392
  appId,
1354
1393
  opts.appServicePath
1355
- ));
1394
+ );
1356
1395
  spinner4.stop("Provisioned");
1357
1396
  } catch (err) {
1358
1397
  spinner4.stop("Failed to provision app");
@@ -1364,8 +1403,9 @@ async function register(argv = [], runOpts = {}) {
1364
1403
  opts,
1365
1404
  tenant,
1366
1405
  appId,
1367
- { clientId, clientSecret, publicClientId },
1368
- false
1406
+ provisioned,
1407
+ false,
1408
+ legacy
1369
1409
  );
1370
1410
  return { ...registerTelemetry, is_new_app: true };
1371
1411
  }
@@ -1546,7 +1586,6 @@ async function listing(argv = [], runOpts = {}) {
1546
1586
  bail(err.message);
1547
1587
  }
1548
1588
  const identityJwt = jwt;
1549
- jwt = apiTokenFor(jwt);
1550
1589
  spinner4.start("Fetching tenants");
1551
1590
  let tenants;
1552
1591
  try {
@@ -1666,6 +1705,17 @@ function templateTech(template) {
1666
1705
  );
1667
1706
  return m?.[1] ?? template.packageName;
1668
1707
  }
1708
+ var NODE_TOKEN_BLOCK_ZITADEL = `// The service account mints its token at the issuer's token endpoint via the client_credentials
1709
+ // grant. Zitadel rejects a client_credentials request that asks for no scope.
1710
+ const TOKEN_URL = \`\${JTL_ISSUER}/oauth/v2/token\`;
1711
+ const TOKEN_SCOPE = 'openid';`;
1712
+ var NODE_TOKEN_BLOCK_LEGACY = `// Legacy Ory client-credentials M2M: mint the token at the Ory auth host. \`npm run register\` writes
1713
+ // JTL_AUTH_BASE for a --legacy app; the fallback is production. No scope is requested.
1714
+ const AUTH_BASE = (process.env.JTL_AUTH_BASE || 'https://auth.jtl-cloud.com').replace(/\\/$/, '');
1715
+ const TOKEN_URL = \`\${AUTH_BASE}/oauth2/token\`;
1716
+ const TOKEN_SCOPE = '';`;
1717
+ var NODE_ENV_EXAMPLE_ZITADEL = "# The service account mints its token at JTL_ISSUER + /oauth/v2/token; no extra config needed.";
1718
+ var NODE_ENV_EXAMPLE_LEGACY = "# Legacy Ory M2M: the backend mints its token at JTL_AUTH_BASE + /oauth2/token; `npm run register` writes JTL_AUTH_BASE.";
1669
1719
  function toPascalCase(kebab) {
1670
1720
  return kebab.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1671
1721
  }
@@ -1774,7 +1824,7 @@ function applyManifestAuthentication(appJsonPath, opts) {
1774
1824
  postLogoutRedirectUris: [`${origin}/`]
1775
1825
  };
1776
1826
  }
1777
- if (backend) {
1827
+ if (backend && !opts.legacy) {
1778
1828
  authentication.serviceAccount = {
1779
1829
  clientCredentials: true,
1780
1830
  description: `${opts.appName} backend service account`
@@ -1803,8 +1853,14 @@ async function scaffold({
1803
1853
  shared,
1804
1854
  frontendPort = DEFAULT_FRONTEND_PORT,
1805
1855
  authentication,
1856
+ legacy = false,
1806
1857
  targetDir: customTargetDir
1807
1858
  }) {
1859
+ if (legacy && backend && templateTech(backend) !== "node") {
1860
+ throw new Error(
1861
+ `--legacy is currently only supported for the node backend (got "${templateTech(backend)}")`
1862
+ );
1863
+ }
1808
1864
  const targetDir = customTargetDir ?? path4.resolve(process.cwd(), appName);
1809
1865
  const baseReplacements = buildPlaceholders({
1810
1866
  appName,
@@ -1837,12 +1893,37 @@ async function scaffold({
1837
1893
  backend.meta.type,
1838
1894
  appName
1839
1895
  );
1896
+ if (legacy) {
1897
+ const backendIndex = path4.join(backendDest, "src", "index.ts");
1898
+ const src = fs4.readFileSync(backendIndex, "utf-8");
1899
+ if (!src.includes(NODE_TOKEN_BLOCK_ZITADEL)) {
1900
+ throw new Error(
1901
+ "node backend token block not found; the --legacy swap is out of date with the template"
1902
+ );
1903
+ }
1904
+ fs4.writeFileSync(
1905
+ backendIndex,
1906
+ src.replace(NODE_TOKEN_BLOCK_ZITADEL, NODE_TOKEN_BLOCK_LEGACY)
1907
+ );
1908
+ const envExample = path4.join(backendDest, ".env.example");
1909
+ const env = fs4.readFileSync(envExample, "utf-8");
1910
+ if (!env.includes(NODE_ENV_EXAMPLE_ZITADEL)) {
1911
+ throw new Error(
1912
+ "node backend .env.example token line not found; the --legacy swap is out of date with the template"
1913
+ );
1914
+ }
1915
+ fs4.writeFileSync(
1916
+ envExample,
1917
+ env.replace(NODE_ENV_EXAMPLE_ZITADEL, NODE_ENV_EXAMPLE_LEGACY)
1918
+ );
1919
+ }
1840
1920
  }
1841
1921
  if (authentication) {
1842
1922
  applyManifestAuthentication(path4.join(targetDir, "app.json"), {
1843
1923
  appName,
1844
1924
  frontendPort,
1845
- authentication
1925
+ authentication,
1926
+ legacy
1846
1927
  });
1847
1928
  }
1848
1929
  const runtimeVersions = backend?.meta.runtimeVersions ?? {};
@@ -1874,10 +1955,12 @@ async function scaffold({
1874
1955
  turbo: "^2.5.5"
1875
1956
  },
1876
1957
  // The stack the developer picked, recorded so `register` can report it in telemetry without
1877
- // guessing from the generated files.
1958
+ // guessing from the generated files. `legacy` tells register to persist the Ory M2M client
1959
+ // instead of the Zitadel service account.
1878
1960
  jtlCloudApp: {
1879
1961
  backend: backend ? templateTech(backend) : null,
1880
- frontend: frontend ? templateTech(frontend) : null
1962
+ frontend: frontend ? templateTech(frontend) : null,
1963
+ legacy
1881
1964
  }
1882
1965
  };
1883
1966
  fs4.writeFileSync(
@@ -1924,7 +2007,7 @@ import os2 from "os";
1924
2007
  import path6 from "path";
1925
2008
  import { PostHog } from "posthog-node";
1926
2009
  var TOKEN = "phc_SkFAxTIa4RUQxotci1Ftdw7xefZK2Ax0LEIGcl8zuD2";
1927
- var CLI_VERSION = "0.7.0";
2010
+ var CLI_VERSION = "0.8.1";
1928
2011
  var client = TOKEN ? new PostHog(TOKEN, { host: "https://e.jtl-cloud.com" }) : null;
1929
2012
  function telemetryDisabledByEnv() {
1930
2013
  const dnt = process.env.DO_NOT_TRACK;
@@ -1993,11 +2076,11 @@ async function shutdownPostHog() {
1993
2076
 
1994
2077
  // src/index.ts
1995
2078
  var NONE = "__none__";
1996
- function scaffoldChoice(backend, frontend, next) {
2079
+ function scaffoldChoice(backend, frontend, legacy) {
1997
2080
  const hasBackend = Boolean(backend);
1998
2081
  const hasFrontend = Boolean(frontend);
1999
2082
  return {
2000
- next,
2083
+ legacy,
2001
2084
  backend_tech: hasBackend ? templateTech(backend) : "none",
2002
2085
  frontend_tech: hasFrontend ? templateTech(frontend) : "none",
2003
2086
  has_backend: hasBackend,
@@ -2022,8 +2105,17 @@ function cancelScaffold() {
2022
2105
  p4.cancel("Cancelled.");
2023
2106
  process.exit(0);
2024
2107
  }
2025
- async function runNextScaffold(backends, frontends, shared) {
2026
- const cancelOpts = { onCancel: cancelScaffold };
2108
+ async function runScaffold() {
2109
+ console.log();
2110
+ p4.intro(pc4.bgCyan(pc4.black(" create-cloud-app ")));
2111
+ const templates = discoverTemplates();
2112
+ const backends = getTemplatesByType(templates, "backend");
2113
+ const frontends = getTemplatesByType(templates, "frontend");
2114
+ const shared = templates.find((t) => t.meta.type === "shared");
2115
+ if (!backends.length || !frontends.length || !shared) {
2116
+ p4.cancel("No templates found. Ensure template packages are installed.");
2117
+ process.exit(1);
2118
+ }
2027
2119
  const answers = await p4.group(
2028
2120
  {
2029
2121
  appName: () => p4.text({
@@ -2045,7 +2137,7 @@ async function runNextScaffold(backends, frontends, shared) {
2045
2137
  options: halfSelectOptions(backends, "no backend")
2046
2138
  })
2047
2139
  },
2048
- cancelOpts
2140
+ { onCancel: cancelScaffold }
2049
2141
  );
2050
2142
  const selectedFrontend = frontends.find(
2051
2143
  (t) => t.packageName === answers.frontend
@@ -2057,6 +2149,11 @@ async function runNextScaffold(backends, frontends, shared) {
2057
2149
  p4.cancel("Pick at least a frontend or a backend.");
2058
2150
  process.exit(1);
2059
2151
  }
2152
+ const legacy = process.argv.includes("--legacy");
2153
+ if (legacy && selectedBackend && templateTech(selectedBackend) !== "node") {
2154
+ p4.cancel("--legacy is only supported for the node backend.");
2155
+ process.exit(1);
2156
+ }
2060
2157
  const s = p4.spinner();
2061
2158
  s.start("Scaffolding project");
2062
2159
  const frontendPort = selectedFrontend ? await pickFrontendPort() : void 0;
@@ -2070,7 +2167,8 @@ async function runNextScaffold(backends, frontends, shared) {
2070
2167
  authentication: {
2071
2168
  frontend: Boolean(selectedFrontend),
2072
2169
  backend: Boolean(selectedBackend)
2073
- }
2170
+ },
2171
+ legacy
2074
2172
  });
2075
2173
  s.stop("Project scaffolded");
2076
2174
  if (selectedFrontend) {
@@ -2079,74 +2177,15 @@ async function runNextScaffold(backends, frontends, shared) {
2079
2177
  "Frontend login"
2080
2178
  );
2081
2179
  }
2082
- printNextSteps(answers.appName);
2083
- p4.outro(pc4.green("Done! Happy building."));
2084
- return scaffoldChoice(selectedBackend, selectedFrontend, true);
2085
- }
2086
- async function runScaffold() {
2087
- console.log();
2088
- p4.intro(pc4.bgCyan(pc4.black(" create-cloud-app ")));
2089
- const templates = discoverTemplates();
2090
- const backends = getTemplatesByType(templates, "backend");
2091
- const frontends = getTemplatesByType(templates, "frontend");
2092
- const shared = templates.find((t) => t.meta.type === "shared");
2093
- if (!backends.length || !frontends.length || !shared) {
2094
- p4.cancel("No templates found. Ensure template packages are installed.");
2095
- process.exit(1);
2096
- }
2097
- if (process.argv.includes("--next")) {
2098
- return runNextScaffold(backends, frontends, shared);
2180
+ if (legacy && selectedBackend) {
2181
+ p4.note(
2182
+ "The backend is scaffolded for the legacy Ory M2M flow. `npm run register` will write its Ory client credentials.",
2183
+ "Legacy backend"
2184
+ );
2099
2185
  }
2100
- const answers = await p4.group(
2101
- {
2102
- appName: () => p4.text({
2103
- message: "App name",
2104
- placeholder: "my-jtl-app",
2105
- validate: (value) => {
2106
- if (!value) return "App name is required";
2107
- if (!/^[a-z0-9-]+$/.test(value))
2108
- return "Use lowercase letters, numbers, and hyphens only";
2109
- }
2110
- }),
2111
- description: () => p4.text({
2112
- message: "Description",
2113
- placeholder: "A JTL Platform app"
2114
- }),
2115
- backend: () => p4.select({
2116
- message: "Backend",
2117
- options: templateSelectOptions(backends)
2118
- }),
2119
- frontend: () => p4.select({
2120
- message: "Frontend",
2121
- options: templateSelectOptions(frontends)
2122
- })
2123
- },
2124
- {
2125
- onCancel: () => {
2126
- p4.cancel("Cancelled.");
2127
- process.exit(0);
2128
- }
2129
- }
2130
- );
2131
- const selectedBackend = templates.find(
2132
- (t) => t.packageName === answers.backend
2133
- );
2134
- const selectedFrontend = templates.find(
2135
- (t) => t.packageName === answers.frontend
2136
- );
2137
- const s = p4.spinner();
2138
- s.start("Scaffolding project");
2139
- await scaffold({
2140
- appName: answers.appName,
2141
- description: answers.description || "A JTL Platform app",
2142
- backend: selectedBackend,
2143
- frontend: selectedFrontend,
2144
- shared
2145
- });
2146
- s.stop("Project scaffolded");
2147
2186
  printNextSteps(answers.appName);
2148
2187
  p4.outro(pc4.green("Done! Happy building."));
2149
- return scaffoldChoice(selectedBackend, selectedFrontend, false);
2188
+ return scaffoldChoice(selectedBackend, selectedFrontend, legacy);
2150
2189
  }
2151
2190
  function printNextSteps(appName) {
2152
2191
  p4.note(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jtl-software/create-cloud-app",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "CLI tool for scaffolding JTL Platform cloud apps",
6
6
  "bin": {
@@ -18,10 +18,10 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^1.7.0",
21
- "@jtl-software/cloud-app-template-backend-dotnet": "0.0.13",
22
- "@jtl-software/cloud-app-template-backend-node": "0.2.1",
23
- "@jtl-software/cloud-app-template-backend-php": "0.1.3",
24
- "@jtl-software/cloud-app-template-frontend-react": "0.3.0",
21
+ "@jtl-software/cloud-app-template-backend-dotnet": "0.2.0",
22
+ "@jtl-software/cloud-app-template-backend-node": "0.3.0",
23
+ "@jtl-software/cloud-app-template-backend-php": "0.3.0",
24
+ "@jtl-software/cloud-app-template-frontend-react": "0.3.1",
25
25
  "@jtl-software/cloud-app-template-shared": "0.2.0",
26
26
  "picocolors": "^1.1.1",
27
27
  "posthog-node": "^5.51.6"