@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.
- package/dist/index.js +130 -39
- 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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
27
|
+
"posthog-node": "^5.49.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^26.2.0",
|