@jtl-software/create-cloud-app 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +9 -1
  2. package/dist/index.js +146 -108
  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 = {};
@@ -968,7 +968,8 @@ async function listTenantApps(apiBaseUrl, jwt, tenantId, appServicePath = "app-s
968
968
  clientId: entry.clientId,
969
969
  provisioningStatus: entry.activeVersion?.provisioningStatus,
970
970
  isInstalled: entry.isInstalled,
971
- publicClientId: entry.credentials?.publicClient?.clientId
971
+ publicClientId: entry.credentials?.publicClient?.clientId,
972
+ serviceAccountClientId: entry.credentials?.serviceAccount?.clientId
972
973
  };
973
974
  }).filter((a) => a !== null);
974
975
  }
@@ -1029,13 +1030,30 @@ async function provisionApp(apiBaseUrl, jwt, tenantId, appId, appServicePath = "
1029
1030
  `provision response missing clientId or clientSecret: ${JSON.stringify(provisioned)}`
1030
1031
  );
1031
1032
  }
1033
+ const sa = provisioned.credentials?.serviceAccount;
1032
1034
  return {
1033
1035
  clientId,
1034
1036
  clientSecret,
1035
- publicClientId: provisioned.credentials?.publicClient?.clientId
1037
+ publicClientId: provisioned.credentials?.publicClient?.clientId,
1038
+ serviceAccount: sa?.clientId && sa.clientSecret ? { clientId: sa.clientId, clientSecret: sa.clientSecret } : void 0
1036
1039
  };
1037
1040
  }
1038
- async function applyProvisionResult(root, backend, opts, tenant, appId, provisioned, isInstalled) {
1041
+ function resolveBackendAuth(provisioned, legacy = false) {
1042
+ if (legacy) {
1043
+ return {
1044
+ clientId: provisioned.clientId,
1045
+ clientSecret: provisioned.clientSecret
1046
+ };
1047
+ }
1048
+ const sa = provisioned.serviceAccount;
1049
+ if (!sa) {
1050
+ throw new Error(
1051
+ "Provision response did not include a service account; the backend cannot authenticate."
1052
+ );
1053
+ }
1054
+ return { clientId: sa.clientId, clientSecret: sa.clientSecret };
1055
+ }
1056
+ async function applyProvisionResult(root, backend, opts, tenant, appId, provisioned, isInstalled, legacy) {
1039
1057
  if (provisioned.publicClientId) {
1040
1058
  const written = writeFrontendPublicClient(
1041
1059
  root,
@@ -1051,13 +1069,14 @@ async function applyProvisionResult(root, backend, opts, tenant, appId, provisio
1051
1069
  }
1052
1070
  writeVerifyDemoEnv(root, backend);
1053
1071
  if (backend) {
1072
+ const auth = resolveBackendAuth(provisioned, legacy);
1054
1073
  await persistCredentials(
1055
1074
  backend,
1056
- provisioned.clientId,
1057
- provisioned.clientSecret,
1075
+ auth.clientId,
1076
+ auth.clientSecret,
1058
1077
  root,
1059
1078
  opts,
1060
- { hub: opts.hub, tenantSlug: tenant.slug, appId, isInstalled }
1079
+ { hub: opts.hub, tenantSlug: tenant.slug, appId, isInstalled, legacy }
1061
1080
  );
1062
1081
  return;
1063
1082
  }
@@ -1109,7 +1128,8 @@ async function persistCredentials(backend, clientId, clientSecret, root, opts, c
1109
1128
  clientSecret,
1110
1129
  opts.auth,
1111
1130
  opts.api,
1112
- context.appId
1131
+ context.appId,
1132
+ context.legacy ? authBaseFromApi(opts.api) : void 0
1113
1133
  );
1114
1134
  printHubLink(
1115
1135
  context.hub,
@@ -1157,11 +1177,18 @@ async function register(argv = [], runOpts = {}) {
1157
1177
  const manifest = readManifest(path2.join(root, "app.json"));
1158
1178
  const summary = summarizeManifest(manifest);
1159
1179
  const stack = readScaffoldStack(root);
1180
+ const legacy = stack?.legacy ?? false;
1160
1181
  const auth = manifest.authentication;
1182
+ if (!legacy && backend && !auth?.serviceAccount) {
1183
+ bail(
1184
+ "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."
1185
+ );
1186
+ }
1161
1187
  const hasFrontend = stack ? !!stack.frontend : !!auth?.publicClient;
1162
1188
  const hasBackend = stack ? !!stack.backend : !!auth?.serviceAccount;
1163
1189
  const registerTelemetry = {
1164
1190
  environment: environmentFromApi(opts.api),
1191
+ legacy,
1165
1192
  has_frontend: hasFrontend,
1166
1193
  has_backend: hasBackend,
1167
1194
  backend_tech: stack?.backend ?? (hasBackend ? "unknown" : "none"),
@@ -1170,7 +1197,7 @@ async function register(argv = [], runOpts = {}) {
1170
1197
  };
1171
1198
  for (; ; ) {
1172
1199
  const identityJwt = jwt;
1173
- const apiJwt = apiTokenFor(jwt);
1200
+ const apiJwt = jwt;
1174
1201
  spinner4.start("Fetching tenants");
1175
1202
  let tenants;
1176
1203
  try {
@@ -1259,9 +1286,9 @@ async function register(argv = [], runOpts = {}) {
1259
1286
  }
1260
1287
  if (existing.provisioningStatus === "DRAFT") {
1261
1288
  spinner4.start("Provisioning app");
1262
- let provisioned;
1289
+ let provisioned2;
1263
1290
  try {
1264
- provisioned = await provisionApp(
1291
+ provisioned2 = await provisionApp(
1265
1292
  opts.api,
1266
1293
  apiJwt,
1267
1294
  tenant.id,
@@ -1279,8 +1306,9 @@ async function register(argv = [], runOpts = {}) {
1279
1306
  opts,
1280
1307
  tenant,
1281
1308
  existing.id,
1282
- provisioned,
1283
- existing.isInstalled ?? false
1309
+ provisioned2,
1310
+ existing.isInstalled ?? false,
1311
+ legacy
1284
1312
  );
1285
1313
  return { ...registerTelemetry, is_new_app: false };
1286
1314
  }
@@ -1316,6 +1344,18 @@ async function register(argv = [], runOpts = {}) {
1316
1344
  );
1317
1345
  return { ...registerTelemetry, is_new_app: false };
1318
1346
  }
1347
+ if (!legacy && localCreds.clientId !== existing.serviceAccountClientId) {
1348
+ p2.outro(
1349
+ pc2.yellow(
1350
+ `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}.`
1351
+ )
1352
+ );
1353
+ return {
1354
+ ...registerTelemetry,
1355
+ is_new_app: false,
1356
+ credential_migration_required: true
1357
+ };
1358
+ }
1319
1359
  p2.outro(
1320
1360
  pc2.green(
1321
1361
  `Manifest updated. Existing ${labels.pair} in ${pc2.cyan(path2.relative(root, envFile))} kept as-is.`
@@ -1342,17 +1382,15 @@ async function register(argv = [], runOpts = {}) {
1342
1382
  bail(err.message);
1343
1383
  }
1344
1384
  spinner4.start("Provisioning app");
1345
- let clientId;
1346
- let clientSecret;
1347
- let publicClientId;
1385
+ let provisioned;
1348
1386
  try {
1349
- ({ clientId, clientSecret, publicClientId } = await provisionApp(
1387
+ provisioned = await provisionApp(
1350
1388
  opts.api,
1351
1389
  apiJwt,
1352
1390
  tenant.id,
1353
1391
  appId,
1354
1392
  opts.appServicePath
1355
- ));
1393
+ );
1356
1394
  spinner4.stop("Provisioned");
1357
1395
  } catch (err) {
1358
1396
  spinner4.stop("Failed to provision app");
@@ -1364,8 +1402,9 @@ async function register(argv = [], runOpts = {}) {
1364
1402
  opts,
1365
1403
  tenant,
1366
1404
  appId,
1367
- { clientId, clientSecret, publicClientId },
1368
- false
1405
+ provisioned,
1406
+ false,
1407
+ legacy
1369
1408
  );
1370
1409
  return { ...registerTelemetry, is_new_app: true };
1371
1410
  }
@@ -1546,7 +1585,6 @@ async function listing(argv = [], runOpts = {}) {
1546
1585
  bail(err.message);
1547
1586
  }
1548
1587
  const identityJwt = jwt;
1549
- jwt = apiTokenFor(jwt);
1550
1588
  spinner4.start("Fetching tenants");
1551
1589
  let tenants;
1552
1590
  try {
@@ -1666,6 +1704,17 @@ function templateTech(template) {
1666
1704
  );
1667
1705
  return m?.[1] ?? template.packageName;
1668
1706
  }
1707
+ var NODE_TOKEN_BLOCK_ZITADEL = `// The service account mints its token at the issuer's token endpoint via the client_credentials
1708
+ // grant. Zitadel rejects a client_credentials request that asks for no scope.
1709
+ const TOKEN_URL = \`\${JTL_ISSUER}/oauth/v2/token\`;
1710
+ const TOKEN_SCOPE = 'openid';`;
1711
+ var NODE_TOKEN_BLOCK_LEGACY = `// Legacy Ory client-credentials M2M: mint the token at the Ory auth host. \`npm run register\` writes
1712
+ // JTL_AUTH_BASE for a --legacy app; the fallback is production. No scope is requested.
1713
+ const AUTH_BASE = (process.env.JTL_AUTH_BASE || 'https://auth.jtl-cloud.com').replace(/\\/$/, '');
1714
+ const TOKEN_URL = \`\${AUTH_BASE}/oauth2/token\`;
1715
+ const TOKEN_SCOPE = '';`;
1716
+ var NODE_ENV_EXAMPLE_ZITADEL = "# The service account mints its token at JTL_ISSUER + /oauth/v2/token; no extra config needed.";
1717
+ 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
1718
  function toPascalCase(kebab) {
1670
1719
  return kebab.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1671
1720
  }
@@ -1774,7 +1823,7 @@ function applyManifestAuthentication(appJsonPath, opts) {
1774
1823
  postLogoutRedirectUris: [`${origin}/`]
1775
1824
  };
1776
1825
  }
1777
- if (backend) {
1826
+ if (backend && !opts.legacy) {
1778
1827
  authentication.serviceAccount = {
1779
1828
  clientCredentials: true,
1780
1829
  description: `${opts.appName} backend service account`
@@ -1803,8 +1852,14 @@ async function scaffold({
1803
1852
  shared,
1804
1853
  frontendPort = DEFAULT_FRONTEND_PORT,
1805
1854
  authentication,
1855
+ legacy = false,
1806
1856
  targetDir: customTargetDir
1807
1857
  }) {
1858
+ if (legacy && backend && templateTech(backend) !== "node") {
1859
+ throw new Error(
1860
+ `--legacy is currently only supported for the node backend (got "${templateTech(backend)}")`
1861
+ );
1862
+ }
1808
1863
  const targetDir = customTargetDir ?? path4.resolve(process.cwd(), appName);
1809
1864
  const baseReplacements = buildPlaceholders({
1810
1865
  appName,
@@ -1837,12 +1892,37 @@ async function scaffold({
1837
1892
  backend.meta.type,
1838
1893
  appName
1839
1894
  );
1895
+ if (legacy) {
1896
+ const backendIndex = path4.join(backendDest, "src", "index.ts");
1897
+ const src = fs4.readFileSync(backendIndex, "utf-8");
1898
+ if (!src.includes(NODE_TOKEN_BLOCK_ZITADEL)) {
1899
+ throw new Error(
1900
+ "node backend token block not found; the --legacy swap is out of date with the template"
1901
+ );
1902
+ }
1903
+ fs4.writeFileSync(
1904
+ backendIndex,
1905
+ src.replace(NODE_TOKEN_BLOCK_ZITADEL, NODE_TOKEN_BLOCK_LEGACY)
1906
+ );
1907
+ const envExample = path4.join(backendDest, ".env.example");
1908
+ const env = fs4.readFileSync(envExample, "utf-8");
1909
+ if (!env.includes(NODE_ENV_EXAMPLE_ZITADEL)) {
1910
+ throw new Error(
1911
+ "node backend .env.example token line not found; the --legacy swap is out of date with the template"
1912
+ );
1913
+ }
1914
+ fs4.writeFileSync(
1915
+ envExample,
1916
+ env.replace(NODE_ENV_EXAMPLE_ZITADEL, NODE_ENV_EXAMPLE_LEGACY)
1917
+ );
1918
+ }
1840
1919
  }
1841
1920
  if (authentication) {
1842
1921
  applyManifestAuthentication(path4.join(targetDir, "app.json"), {
1843
1922
  appName,
1844
1923
  frontendPort,
1845
- authentication
1924
+ authentication,
1925
+ legacy
1846
1926
  });
1847
1927
  }
1848
1928
  const runtimeVersions = backend?.meta.runtimeVersions ?? {};
@@ -1874,10 +1954,12 @@ async function scaffold({
1874
1954
  turbo: "^2.5.5"
1875
1955
  },
1876
1956
  // The stack the developer picked, recorded so `register` can report it in telemetry without
1877
- // guessing from the generated files.
1957
+ // guessing from the generated files. `legacy` tells register to persist the Ory M2M client
1958
+ // instead of the Zitadel service account.
1878
1959
  jtlCloudApp: {
1879
1960
  backend: backend ? templateTech(backend) : null,
1880
- frontend: frontend ? templateTech(frontend) : null
1961
+ frontend: frontend ? templateTech(frontend) : null,
1962
+ legacy
1881
1963
  }
1882
1964
  };
1883
1965
  fs4.writeFileSync(
@@ -1924,7 +2006,7 @@ import os2 from "os";
1924
2006
  import path6 from "path";
1925
2007
  import { PostHog } from "posthog-node";
1926
2008
  var TOKEN = "phc_SkFAxTIa4RUQxotci1Ftdw7xefZK2Ax0LEIGcl8zuD2";
1927
- var CLI_VERSION = "0.7.0";
2009
+ var CLI_VERSION = "0.8.0";
1928
2010
  var client = TOKEN ? new PostHog(TOKEN, { host: "https://e.jtl-cloud.com" }) : null;
1929
2011
  function telemetryDisabledByEnv() {
1930
2012
  const dnt = process.env.DO_NOT_TRACK;
@@ -1993,11 +2075,11 @@ async function shutdownPostHog() {
1993
2075
 
1994
2076
  // src/index.ts
1995
2077
  var NONE = "__none__";
1996
- function scaffoldChoice(backend, frontend, next) {
2078
+ function scaffoldChoice(backend, frontend, legacy) {
1997
2079
  const hasBackend = Boolean(backend);
1998
2080
  const hasFrontend = Boolean(frontend);
1999
2081
  return {
2000
- next,
2082
+ legacy,
2001
2083
  backend_tech: hasBackend ? templateTech(backend) : "none",
2002
2084
  frontend_tech: hasFrontend ? templateTech(frontend) : "none",
2003
2085
  has_backend: hasBackend,
@@ -2022,8 +2104,17 @@ function cancelScaffold() {
2022
2104
  p4.cancel("Cancelled.");
2023
2105
  process.exit(0);
2024
2106
  }
2025
- async function runNextScaffold(backends, frontends, shared) {
2026
- const cancelOpts = { onCancel: cancelScaffold };
2107
+ async function runScaffold() {
2108
+ console.log();
2109
+ p4.intro(pc4.bgCyan(pc4.black(" create-cloud-app ")));
2110
+ const templates = discoverTemplates();
2111
+ const backends = getTemplatesByType(templates, "backend");
2112
+ const frontends = getTemplatesByType(templates, "frontend");
2113
+ const shared = templates.find((t) => t.meta.type === "shared");
2114
+ if (!backends.length || !frontends.length || !shared) {
2115
+ p4.cancel("No templates found. Ensure template packages are installed.");
2116
+ process.exit(1);
2117
+ }
2027
2118
  const answers = await p4.group(
2028
2119
  {
2029
2120
  appName: () => p4.text({
@@ -2045,7 +2136,7 @@ async function runNextScaffold(backends, frontends, shared) {
2045
2136
  options: halfSelectOptions(backends, "no backend")
2046
2137
  })
2047
2138
  },
2048
- cancelOpts
2139
+ { onCancel: cancelScaffold }
2049
2140
  );
2050
2141
  const selectedFrontend = frontends.find(
2051
2142
  (t) => t.packageName === answers.frontend
@@ -2057,6 +2148,11 @@ async function runNextScaffold(backends, frontends, shared) {
2057
2148
  p4.cancel("Pick at least a frontend or a backend.");
2058
2149
  process.exit(1);
2059
2150
  }
2151
+ const legacy = process.argv.includes("--legacy");
2152
+ if (legacy && selectedBackend && templateTech(selectedBackend) !== "node") {
2153
+ p4.cancel("--legacy is only supported for the node backend.");
2154
+ process.exit(1);
2155
+ }
2060
2156
  const s = p4.spinner();
2061
2157
  s.start("Scaffolding project");
2062
2158
  const frontendPort = selectedFrontend ? await pickFrontendPort() : void 0;
@@ -2070,7 +2166,8 @@ async function runNextScaffold(backends, frontends, shared) {
2070
2166
  authentication: {
2071
2167
  frontend: Boolean(selectedFrontend),
2072
2168
  backend: Boolean(selectedBackend)
2073
- }
2169
+ },
2170
+ legacy
2074
2171
  });
2075
2172
  s.stop("Project scaffolded");
2076
2173
  if (selectedFrontend) {
@@ -2079,74 +2176,15 @@ async function runNextScaffold(backends, frontends, shared) {
2079
2176
  "Frontend login"
2080
2177
  );
2081
2178
  }
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);
2179
+ if (legacy && selectedBackend) {
2180
+ p4.note(
2181
+ "The backend is scaffolded for the legacy Ory M2M flow. `npm run register` will write its Ory client credentials.",
2182
+ "Legacy backend"
2183
+ );
2099
2184
  }
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
2185
  printNextSteps(answers.appName);
2148
2186
  p4.outro(pc4.green("Done! Happy building."));
2149
- return scaffoldChoice(selectedBackend, selectedFrontend, false);
2187
+ return scaffoldChoice(selectedBackend, selectedFrontend, legacy);
2150
2188
  }
2151
2189
  function printNextSteps(appName) {
2152
2190
  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.0",
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.1.0",
22
+ "@jtl-software/cloud-app-template-backend-node": "0.3.0",
23
+ "@jtl-software/cloud-app-template-backend-php": "0.2.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"