@jtl-software/create-cloud-app 0.3.1 → 0.5.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.
- package/dist/index.js +531 -157
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -157,7 +157,10 @@ function captureAuthCode(authorizeUrl, expectedState, verifier, options = {}) {
|
|
|
157
157
|
const closeAndSettle = (res, next) => {
|
|
158
158
|
if (settled) return;
|
|
159
159
|
settled = true;
|
|
160
|
-
const finish = () =>
|
|
160
|
+
const finish = () => {
|
|
161
|
+
server.closeAllConnections?.();
|
|
162
|
+
server.close(() => next());
|
|
163
|
+
};
|
|
161
164
|
if (res.writableEnded) {
|
|
162
165
|
finish();
|
|
163
166
|
} else {
|
|
@@ -364,6 +367,9 @@ async function acquireAccessToken(opts) {
|
|
|
364
367
|
authorizeUrl.searchParams.set("state", state);
|
|
365
368
|
authorizeUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
366
369
|
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
370
|
+
if (reauth) {
|
|
371
|
+
authorizeUrl.searchParams.set("prompt", "select_account");
|
|
372
|
+
}
|
|
367
373
|
spinner4?.start("Waiting for browser login");
|
|
368
374
|
try {
|
|
369
375
|
const { code, verifier } = await captureAuthCode(
|
|
@@ -471,6 +477,10 @@ function tenantsFromClaims(claims) {
|
|
|
471
477
|
}
|
|
472
478
|
return ordered;
|
|
473
479
|
}
|
|
480
|
+
function activeTenantId(claims) {
|
|
481
|
+
const active = claims[TENANT_ID_CLAIM];
|
|
482
|
+
return typeof active === "string" && active ? active : void 0;
|
|
483
|
+
}
|
|
474
484
|
function validateHost(input, flag) {
|
|
475
485
|
let url;
|
|
476
486
|
try {
|
|
@@ -680,6 +690,20 @@ function detectBackend(root) {
|
|
|
680
690
|
}
|
|
681
691
|
return { kind: "node", envFile: path2.join(root, ".env") };
|
|
682
692
|
}
|
|
693
|
+
function detectFrontend(root) {
|
|
694
|
+
const frontendDir = path2.join(root, "packages", "frontend");
|
|
695
|
+
return fs2.existsSync(path2.join(frontendDir, "package.json")) ? frontendDir : null;
|
|
696
|
+
}
|
|
697
|
+
function writeFrontendPublicClient(root, authIssuer, publicClientId) {
|
|
698
|
+
const frontendDir = detectFrontend(root);
|
|
699
|
+
if (!frontendDir) return null;
|
|
700
|
+
const envFile = path2.join(frontendDir, ".env");
|
|
701
|
+
upsertEnv(envFile, {
|
|
702
|
+
VITE_JTL_CLIENT_ID: publicClientId,
|
|
703
|
+
VITE_JTL_ISSUER: authIssuer
|
|
704
|
+
});
|
|
705
|
+
return envFile;
|
|
706
|
+
}
|
|
683
707
|
function readExistingCredentials(backend) {
|
|
684
708
|
if (backend.kind === "node") {
|
|
685
709
|
const values = readEnvFile(backend.envFile);
|
|
@@ -772,7 +796,8 @@ function resolveOptions(args) {
|
|
|
772
796
|
...COMMON_ARG_OPTIONS,
|
|
773
797
|
"existing-app-id": { type: "string" },
|
|
774
798
|
new: { type: "boolean" },
|
|
775
|
-
"on-collision": { type: "string" }
|
|
799
|
+
"on-collision": { type: "string" },
|
|
800
|
+
"app-service-path": { type: "string" }
|
|
776
801
|
},
|
|
777
802
|
strict: true,
|
|
778
803
|
allowPositionals: false
|
|
@@ -788,11 +813,13 @@ function resolveOptions(args) {
|
|
|
788
813
|
}
|
|
789
814
|
const onCollisionRaw = parsed.values["on-collision"];
|
|
790
815
|
const onCollision = onCollisionRaw ? parseCollisionAction(onCollisionRaw) : void 0;
|
|
816
|
+
const appServicePath = parsed.values["app-service-path"] ?? "app-service";
|
|
791
817
|
return {
|
|
792
818
|
...common,
|
|
793
819
|
existingAppId,
|
|
794
820
|
forceNew,
|
|
795
|
-
onCollision
|
|
821
|
+
onCollision,
|
|
822
|
+
appServicePath
|
|
796
823
|
};
|
|
797
824
|
}
|
|
798
825
|
async function selectExistingOrNew(matches, opts) {
|
|
@@ -847,9 +874,29 @@ async function resolveCollision(targetLabel, credentialPair, opts) {
|
|
|
847
874
|
})
|
|
848
875
|
);
|
|
849
876
|
}
|
|
850
|
-
async function
|
|
877
|
+
async function confirmRegister(opts, message, isUpdate) {
|
|
878
|
+
if (opts.yes) return "proceed";
|
|
879
|
+
return ensureContinue(
|
|
880
|
+
await p2.select({
|
|
881
|
+
message,
|
|
882
|
+
options: [
|
|
883
|
+
{
|
|
884
|
+
value: "proceed",
|
|
885
|
+
label: isUpdate ? "Update the app" : "Create the app"
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
value: "switch-org",
|
|
889
|
+
label: "Use a different organization",
|
|
890
|
+
hint: "sign in again and pick another org"
|
|
891
|
+
},
|
|
892
|
+
{ value: "cancel", label: "Cancel" }
|
|
893
|
+
]
|
|
894
|
+
})
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
async function listTenantApps(apiBaseUrl, jwt, tenantId, appServicePath = "app-service") {
|
|
851
898
|
const res = await apiCall(
|
|
852
|
-
`${apiBaseUrl}/
|
|
899
|
+
`${apiBaseUrl}/${appServicePath}/tenants/apps?onlyAppsOfTenant=true&returnAll=true`,
|
|
853
900
|
{
|
|
854
901
|
headers: {
|
|
855
902
|
Authorization: `Bearer ${jwt}`,
|
|
@@ -868,24 +915,28 @@ async function listTenantApps(apiBaseUrl, jwt, tenantId) {
|
|
|
868
915
|
technicalName,
|
|
869
916
|
clientId: entry.clientId,
|
|
870
917
|
provisioningStatus: entry.activeVersion?.provisioningStatus,
|
|
871
|
-
isInstalled: entry.isInstalled
|
|
918
|
+
isInstalled: entry.isInstalled,
|
|
919
|
+
publicClientId: entry.credentials?.publicClient?.clientId
|
|
872
920
|
};
|
|
873
921
|
}).filter((a) => a !== null);
|
|
874
922
|
}
|
|
875
|
-
async function updateAppManifest(apiBaseUrl, jwt, tenantId, appId, manifest) {
|
|
876
|
-
await apiCall(
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
923
|
+
async function updateAppManifest(apiBaseUrl, jwt, tenantId, appId, manifest, appServicePath = "app-service") {
|
|
924
|
+
await apiCall(
|
|
925
|
+
`${apiBaseUrl}/${appServicePath}/store/apps/${appId}/manifest`,
|
|
926
|
+
{
|
|
927
|
+
method: "PUT",
|
|
928
|
+
headers: {
|
|
929
|
+
Authorization: `Bearer ${jwt}`,
|
|
930
|
+
"Content-Type": "application/json",
|
|
931
|
+
"X-Tenant-Id": tenantId
|
|
932
|
+
},
|
|
933
|
+
body: JSON.stringify({ manifest, tcAccepted: true })
|
|
934
|
+
}
|
|
935
|
+
);
|
|
885
936
|
}
|
|
886
|
-
async function createDraftApp(apiBaseUrl, jwt, tenantId, manifest) {
|
|
937
|
+
async function createDraftApp(apiBaseUrl, jwt, tenantId, manifest, appServicePath = "app-service") {
|
|
887
938
|
const created = await apiCall(
|
|
888
|
-
`${apiBaseUrl}/
|
|
939
|
+
`${apiBaseUrl}/${appServicePath}/store/apps`,
|
|
889
940
|
{
|
|
890
941
|
method: "POST",
|
|
891
942
|
headers: {
|
|
@@ -906,9 +957,9 @@ async function createDraftApp(apiBaseUrl, jwt, tenantId, manifest) {
|
|
|
906
957
|
}
|
|
907
958
|
return created.id;
|
|
908
959
|
}
|
|
909
|
-
async function provisionApp(apiBaseUrl, jwt, tenantId, appId) {
|
|
960
|
+
async function provisionApp(apiBaseUrl, jwt, tenantId, appId, appServicePath = "app-service") {
|
|
910
961
|
const provisioned = await apiCall(
|
|
911
|
-
`${apiBaseUrl}/
|
|
962
|
+
`${apiBaseUrl}/${appServicePath}/store/apps/${appId}/provision`,
|
|
912
963
|
{
|
|
913
964
|
method: "PUT",
|
|
914
965
|
headers: {
|
|
@@ -926,7 +977,11 @@ async function provisionApp(apiBaseUrl, jwt, tenantId, appId) {
|
|
|
926
977
|
`provision response missing clientId or clientSecret: ${JSON.stringify(provisioned)}`
|
|
927
978
|
);
|
|
928
979
|
}
|
|
929
|
-
return {
|
|
980
|
+
return {
|
|
981
|
+
clientId,
|
|
982
|
+
clientSecret,
|
|
983
|
+
publicClientId: provisioned.credentials?.publicClient?.clientId
|
|
984
|
+
};
|
|
930
985
|
}
|
|
931
986
|
function credentialLabels(backend) {
|
|
932
987
|
if (backend.kind === "node") {
|
|
@@ -985,15 +1040,16 @@ async function register(argv = [], runOpts = {}) {
|
|
|
985
1040
|
console.log();
|
|
986
1041
|
p2.intro(pc2.bgCyan(pc2.black(" register cloud app ")));
|
|
987
1042
|
const opts = resolveOptions(argv);
|
|
988
|
-
const backend = detectBackend(root);
|
|
1043
|
+
const backend = fs2.existsSync(path2.join(root, "packages", "backend")) ? detectBackend(root) : null;
|
|
989
1044
|
p2.note(
|
|
990
1045
|
[
|
|
991
1046
|
`${pc2.dim("API")} ${opts.api}`,
|
|
1047
|
+
opts.appServicePath !== "app-service" ? `${pc2.dim("App svc")} /${opts.appServicePath}` : null,
|
|
992
1048
|
`${pc2.dim("Auth")} ${opts.auth}`,
|
|
993
1049
|
`${pc2.dim("Hub")} ${opts.hub}`,
|
|
994
1050
|
`${pc2.dim("Client ID")} ${opts.clientId}`,
|
|
995
1051
|
`${pc2.dim("Scope")} ${opts.scope}`
|
|
996
|
-
].join("\n"),
|
|
1052
|
+
].filter(Boolean).join("\n"),
|
|
997
1053
|
"Target"
|
|
998
1054
|
);
|
|
999
1055
|
const spinner4 = p2.spinner();
|
|
@@ -1010,114 +1066,196 @@ async function register(argv = [], runOpts = {}) {
|
|
|
1010
1066
|
} catch (err) {
|
|
1011
1067
|
bail(err.message);
|
|
1012
1068
|
}
|
|
1013
|
-
const identityJwt = jwt;
|
|
1014
|
-
jwt = apiTokenFor(jwt);
|
|
1015
|
-
spinner4.start("Fetching tenants");
|
|
1016
|
-
let tenants;
|
|
1017
|
-
try {
|
|
1018
|
-
tenants = await fetchTenants(opts.api, identityJwt);
|
|
1019
|
-
spinner4.stop(
|
|
1020
|
-
`Found ${tenants.length} tenant${tenants.length === 1 ? "" : "s"}`
|
|
1021
|
-
);
|
|
1022
|
-
} catch (err) {
|
|
1023
|
-
spinner4.stop("Failed to fetch tenants");
|
|
1024
|
-
bail(err.message);
|
|
1025
|
-
}
|
|
1026
|
-
const tenant = await selectTenant(tenants, opts);
|
|
1027
1069
|
const manifest = readManifest(path2.join(root, "app.json"));
|
|
1028
1070
|
const summary = summarizeManifest(manifest);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
spinner4.stop(
|
|
1035
|
-
`Found ${matches.length} app${matches.length === 1 ? "" : "s"} with technical name ${pc2.cyan(summary.technicalName)}`
|
|
1036
|
-
);
|
|
1037
|
-
} catch (err) {
|
|
1038
|
-
spinner4.stop("Failed to look up tenant apps");
|
|
1039
|
-
bail(err.message);
|
|
1040
|
-
}
|
|
1041
|
-
const existing = await selectExistingOrNew(matches, opts);
|
|
1042
|
-
const reviewLines = [
|
|
1043
|
-
`${pc2.dim("Technical name")} ${summary.technicalName}`,
|
|
1044
|
-
`${pc2.dim("Version")} ${summary.version}`,
|
|
1045
|
-
`${pc2.dim("Tenant")} ${tenant.name} (${tenant.slug})`,
|
|
1046
|
-
existing ? `${pc2.dim("Updating")} ${existing.technicalName} (${existing.id}) [${existing.provisioningStatus ?? "?"}]` : `${pc2.dim("Action")} create new`
|
|
1047
|
-
].filter(Boolean);
|
|
1048
|
-
p2.note(reviewLines.join("\n"), "Review");
|
|
1049
|
-
const confirmMessage = existing ? `Update app "${existing.technicalName}" in tenant "${tenant.name}"?` : `Create app "${summary.technicalName}" in tenant "${tenant.name}"?`;
|
|
1050
|
-
const confirm2 = await confirmAction(opts, confirmMessage, existing !== null);
|
|
1051
|
-
if (!confirm2) bail("Cancelled.");
|
|
1052
|
-
if (existing) {
|
|
1053
|
-
spinner4.start("Updating manifest");
|
|
1071
|
+
for (; ; ) {
|
|
1072
|
+
const identityJwt = jwt;
|
|
1073
|
+
const apiJwt = apiTokenFor(jwt);
|
|
1074
|
+
spinner4.start("Fetching tenants");
|
|
1075
|
+
let tenants;
|
|
1054
1076
|
try {
|
|
1055
|
-
await
|
|
1056
|
-
spinner4.stop(
|
|
1077
|
+
tenants = await fetchTenants(opts.api, identityJwt);
|
|
1078
|
+
spinner4.stop(
|
|
1079
|
+
`Found ${tenants.length} tenant${tenants.length === 1 ? "" : "s"}`
|
|
1080
|
+
);
|
|
1057
1081
|
} catch (err) {
|
|
1058
|
-
spinner4.stop("Failed to
|
|
1082
|
+
spinner4.stop("Failed to fetch tenants");
|
|
1059
1083
|
bail(err.message);
|
|
1060
1084
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1085
|
+
let tenant;
|
|
1086
|
+
const scopedTenantId = opts.tenant ? void 0 : activeTenantId(decodeJwtClaims(identityJwt));
|
|
1087
|
+
const scopedTenant = scopedTenantId ? tenants.find((t) => t.id === scopedTenantId) : void 0;
|
|
1088
|
+
if (scopedTenant) {
|
|
1089
|
+
tenant = scopedTenant;
|
|
1090
|
+
p2.log.info(
|
|
1091
|
+
`Organization ${pc2.cyan(tenant.name)} ${pc2.dim(`(${tenant.slug})`)} \u2014 selected at login`
|
|
1092
|
+
);
|
|
1093
|
+
} else {
|
|
1094
|
+
tenant = await selectTenant(tenants, opts);
|
|
1095
|
+
}
|
|
1096
|
+
spinner4.start("Looking for apps matching this manifest");
|
|
1097
|
+
let matches;
|
|
1098
|
+
try {
|
|
1099
|
+
const apps = await listTenantApps(
|
|
1100
|
+
opts.api,
|
|
1101
|
+
apiJwt,
|
|
1102
|
+
tenant.id,
|
|
1103
|
+
opts.appServicePath
|
|
1104
|
+
);
|
|
1105
|
+
matches = apps.filter((a) => a.technicalName === summary.technicalName);
|
|
1106
|
+
spinner4.stop(
|
|
1107
|
+
`Found ${matches.length} app${matches.length === 1 ? "" : "s"} with technical name ${pc2.cyan(summary.technicalName)}`
|
|
1108
|
+
);
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
spinner4.stop("Failed to look up tenant apps");
|
|
1111
|
+
bail(err.message);
|
|
1112
|
+
}
|
|
1113
|
+
const existing = await selectExistingOrNew(matches, opts);
|
|
1114
|
+
const reviewLines = [
|
|
1115
|
+
`${pc2.dim("Technical name")} ${summary.technicalName}`,
|
|
1116
|
+
`${pc2.dim("Version")} ${summary.version}`,
|
|
1117
|
+
`${pc2.dim("Tenant")} ${tenant.name} (${tenant.slug})`,
|
|
1118
|
+
existing ? `${pc2.dim("Updating")} ${existing.technicalName} (${existing.id}) [${existing.provisioningStatus ?? "?"}]` : `${pc2.dim("Action")} create new`
|
|
1119
|
+
].filter(Boolean);
|
|
1120
|
+
p2.note(reviewLines.join("\n"), "Review");
|
|
1121
|
+
const confirmMessage = existing ? `Update app "${existing.technicalName}" in tenant "${tenant.name}"?` : `Create app "${summary.technicalName}" in tenant "${tenant.name}"?`;
|
|
1122
|
+
const decision = await confirmRegister(
|
|
1123
|
+
opts,
|
|
1124
|
+
confirmMessage,
|
|
1125
|
+
existing !== null
|
|
1066
1126
|
);
|
|
1067
|
-
if (
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1127
|
+
if (decision === "cancel") bail("Cancelled.");
|
|
1128
|
+
if (decision === "switch-org") {
|
|
1129
|
+
try {
|
|
1130
|
+
jwt = await acquireAccessToken({
|
|
1131
|
+
auth: opts.auth,
|
|
1132
|
+
clientId: opts.clientId,
|
|
1133
|
+
scope: opts.scope,
|
|
1134
|
+
reauth: true,
|
|
1135
|
+
// forces prompt=select_account so the user re-picks the organization
|
|
1136
|
+
spinner: spinner4,
|
|
1137
|
+
captureOptions: runOpts.captureOptions
|
|
1138
|
+
});
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
bail(err.message);
|
|
1141
|
+
}
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
if (existing) {
|
|
1145
|
+
spinner4.start("Updating manifest");
|
|
1146
|
+
try {
|
|
1147
|
+
await updateAppManifest(
|
|
1148
|
+
opts.api,
|
|
1149
|
+
apiJwt,
|
|
1150
|
+
tenant.id,
|
|
1151
|
+
existing.id,
|
|
1152
|
+
manifest,
|
|
1153
|
+
opts.appServicePath
|
|
1154
|
+
);
|
|
1155
|
+
spinner4.stop("Manifest updated");
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
spinner4.stop("Failed to update manifest");
|
|
1158
|
+
bail(err.message);
|
|
1159
|
+
}
|
|
1160
|
+
if (existing.publicClientId) {
|
|
1161
|
+
const written = writeFrontendPublicClient(
|
|
1162
|
+
root,
|
|
1163
|
+
opts.auth,
|
|
1164
|
+
existing.publicClientId
|
|
1165
|
+
);
|
|
1166
|
+
if (written) {
|
|
1167
|
+
p2.log.success(
|
|
1168
|
+
`Wrote VITE_JTL_CLIENT_ID/VITE_JTL_ISSUER to ${pc2.cyan(path2.relative(root, written))}.`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
printHubLink(
|
|
1173
|
+
opts.hub,
|
|
1174
|
+
tenant.slug,
|
|
1175
|
+
existing.id,
|
|
1176
|
+
existing.isInstalled ?? false
|
|
1177
|
+
);
|
|
1178
|
+
if (backend && existing.clientId) {
|
|
1179
|
+
const labels = credentialLabels(backend);
|
|
1180
|
+
const envFile = backend.kind === "node" ? backend.envFile : backend.settingsFile;
|
|
1181
|
+
const localCreds = readExistingCredentials(backend);
|
|
1182
|
+
const hasLocalCreds = !!(localCreds.clientId && localCreds.clientSecret);
|
|
1183
|
+
if (!hasLocalCreds) {
|
|
1184
|
+
p2.outro(
|
|
1185
|
+
pc2.yellow(
|
|
1186
|
+
`Manifest updated. Your backend config is missing ${labels.pair} \u2014 they can't be recovered. Re-create the app to get a fresh secret.`
|
|
1187
|
+
)
|
|
1188
|
+
);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1073
1191
|
p2.outro(
|
|
1074
|
-
pc2.
|
|
1075
|
-
`Manifest updated.
|
|
1192
|
+
pc2.green(
|
|
1193
|
+
`Manifest updated. Existing ${labels.pair} in ${pc2.cyan(path2.relative(root, envFile))} kept as-is.`
|
|
1076
1194
|
)
|
|
1077
1195
|
);
|
|
1078
|
-
|
|
1196
|
+
} else {
|
|
1197
|
+
p2.outro(pc2.green("Manifest updated."));
|
|
1079
1198
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
spinner4.start("Creating draft app");
|
|
1202
|
+
let appId;
|
|
1203
|
+
try {
|
|
1204
|
+
appId = await createDraftApp(
|
|
1205
|
+
opts.api,
|
|
1206
|
+
apiJwt,
|
|
1207
|
+
tenant.id,
|
|
1208
|
+
manifest,
|
|
1209
|
+
opts.appServicePath
|
|
1084
1210
|
);
|
|
1085
|
-
|
|
1086
|
-
|
|
1211
|
+
spinner4.stop(`Created app ${pc2.cyan(appId)}`);
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
spinner4.stop("Failed to create app");
|
|
1214
|
+
bail(err.message);
|
|
1215
|
+
}
|
|
1216
|
+
spinner4.start("Provisioning app");
|
|
1217
|
+
let clientId;
|
|
1218
|
+
let clientSecret;
|
|
1219
|
+
let publicClientId;
|
|
1220
|
+
try {
|
|
1221
|
+
({ clientId, clientSecret, publicClientId } = await provisionApp(
|
|
1222
|
+
opts.api,
|
|
1223
|
+
apiJwt,
|
|
1224
|
+
tenant.id,
|
|
1225
|
+
appId,
|
|
1226
|
+
opts.appServicePath
|
|
1227
|
+
));
|
|
1228
|
+
spinner4.stop("Provisioned");
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
spinner4.stop("Failed to provision app");
|
|
1231
|
+
bail(err.message);
|
|
1232
|
+
}
|
|
1233
|
+
if (publicClientId) {
|
|
1234
|
+
const written = writeFrontendPublicClient(
|
|
1235
|
+
root,
|
|
1236
|
+
opts.auth,
|
|
1237
|
+
publicClientId
|
|
1238
|
+
);
|
|
1239
|
+
if (written) {
|
|
1240
|
+
p2.log.success(
|
|
1241
|
+
`Wrote VITE_JTL_CLIENT_ID/VITE_JTL_ISSUER to ${pc2.cyan(path2.relative(root, written))}.`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1087
1244
|
}
|
|
1245
|
+
if (backend) {
|
|
1246
|
+
await persistCredentials(backend, clientId, clientSecret, root, opts, {
|
|
1247
|
+
hub: opts.hub,
|
|
1248
|
+
tenantSlug: tenant.slug,
|
|
1249
|
+
appId,
|
|
1250
|
+
// A freshly created app can't be installed yet.
|
|
1251
|
+
isInstalled: false
|
|
1252
|
+
});
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
printHubLink(opts.hub, tenant.slug, appId, false);
|
|
1256
|
+
p2.outro(pc2.green("Done. App registered."));
|
|
1088
1257
|
return;
|
|
1089
1258
|
}
|
|
1090
|
-
spinner4.start("Creating draft app");
|
|
1091
|
-
let appId;
|
|
1092
|
-
try {
|
|
1093
|
-
appId = await createDraftApp(opts.api, jwt, tenant.id, manifest);
|
|
1094
|
-
spinner4.stop(`Created app ${pc2.cyan(appId)}`);
|
|
1095
|
-
} catch (err) {
|
|
1096
|
-
spinner4.stop("Failed to create app");
|
|
1097
|
-
bail(err.message);
|
|
1098
|
-
}
|
|
1099
|
-
spinner4.start("Provisioning app");
|
|
1100
|
-
let clientId;
|
|
1101
|
-
let clientSecret;
|
|
1102
|
-
try {
|
|
1103
|
-
({ clientId, clientSecret } = await provisionApp(
|
|
1104
|
-
opts.api,
|
|
1105
|
-
jwt,
|
|
1106
|
-
tenant.id,
|
|
1107
|
-
appId
|
|
1108
|
-
));
|
|
1109
|
-
spinner4.stop("Provisioned");
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
spinner4.stop("Failed to provision app");
|
|
1112
|
-
bail(err.message);
|
|
1113
|
-
}
|
|
1114
|
-
await persistCredentials(backend, clientId, clientSecret, root, opts, {
|
|
1115
|
-
hub: opts.hub,
|
|
1116
|
-
tenantSlug: tenant.slug,
|
|
1117
|
-
appId,
|
|
1118
|
-
// A freshly created app can't be installed yet.
|
|
1119
|
-
isInstalled: false
|
|
1120
|
-
});
|
|
1121
1259
|
}
|
|
1122
1260
|
|
|
1123
1261
|
// src/listing.ts
|
|
@@ -1390,7 +1528,25 @@ async function listing(argv = [], runOpts = {}) {
|
|
|
1390
1528
|
// src/scaffold.ts
|
|
1391
1529
|
import crypto2 from "crypto";
|
|
1392
1530
|
import fs4 from "fs";
|
|
1531
|
+
import net from "net";
|
|
1393
1532
|
import path4 from "path";
|
|
1533
|
+
var DEFAULT_FRONTEND_PORT = 3004;
|
|
1534
|
+
var BACKEND_PORT = 3005;
|
|
1535
|
+
function isPortFree(port) {
|
|
1536
|
+
return new Promise((resolve) => {
|
|
1537
|
+
const server = net.createServer();
|
|
1538
|
+
server.once("error", () => resolve(false));
|
|
1539
|
+
server.once("listening", () => server.close(() => resolve(true)));
|
|
1540
|
+
server.listen(port, "127.0.0.1");
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
async function pickFrontendPort() {
|
|
1544
|
+
for (let port = DEFAULT_FRONTEND_PORT; port <= 3999; port++) {
|
|
1545
|
+
if (port === BACKEND_PORT) continue;
|
|
1546
|
+
if (await isPortFree(port)) return port;
|
|
1547
|
+
}
|
|
1548
|
+
return DEFAULT_FRONTEND_PORT;
|
|
1549
|
+
}
|
|
1394
1550
|
function toPascalCase(kebab) {
|
|
1395
1551
|
return kebab.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1396
1552
|
}
|
|
@@ -1412,7 +1568,8 @@ function buildPlaceholders(opts) {
|
|
|
1412
1568
|
"{{APP_NAME_PASCAL}}": toPascalCase(opts.appName),
|
|
1413
1569
|
"{{APP_TECHNICAL_NAME}}": buildTechnicalName(opts.appName),
|
|
1414
1570
|
"{{APP_DESCRIPTION}}": opts.description,
|
|
1415
|
-
"{{APP_DESCRIPTION_SHORT}}": buildShortDescription(opts.description)
|
|
1571
|
+
"{{APP_DESCRIPTION_SHORT}}": buildShortDescription(opts.description),
|
|
1572
|
+
"{{FRONTEND_PORT}}": String(opts.frontendPort)
|
|
1416
1573
|
};
|
|
1417
1574
|
}
|
|
1418
1575
|
function applyPlaceholders(content, replacements) {
|
|
@@ -1448,9 +1605,11 @@ function transformPackageJson(content, appName, templateType, replacements) {
|
|
|
1448
1605
|
const result = applyPlaceholders(JSON.stringify(pkg, null, 2), replacements);
|
|
1449
1606
|
return result + "\n";
|
|
1450
1607
|
}
|
|
1608
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".turbo"]);
|
|
1451
1609
|
function copyDir(src, dest, replacements, templateType, appName) {
|
|
1452
1610
|
fs4.mkdirSync(dest, { recursive: true });
|
|
1453
1611
|
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
1612
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
|
|
1454
1613
|
const srcPath = path4.join(src, entry.name);
|
|
1455
1614
|
let destName = entry.name;
|
|
1456
1615
|
if (destName.startsWith("_git")) {
|
|
@@ -1485,40 +1644,87 @@ function buildReplacements(base, template) {
|
|
|
1485
1644
|
}
|
|
1486
1645
|
return { ...base, ...resolved };
|
|
1487
1646
|
}
|
|
1647
|
+
function applyManifestAuthentication(appJsonPath, opts) {
|
|
1648
|
+
const manifest = JSON.parse(fs4.readFileSync(appJsonPath, "utf-8"));
|
|
1649
|
+
const { frontend, backend } = opts.authentication;
|
|
1650
|
+
const authentication = {};
|
|
1651
|
+
if (frontend) {
|
|
1652
|
+
const origin = `http://localhost:${opts.frontendPort}`;
|
|
1653
|
+
authentication.publicClient = {
|
|
1654
|
+
redirectUris: [`${origin}/callback`],
|
|
1655
|
+
postLogoutRedirectUris: [`${origin}/`]
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
if (backend) {
|
|
1659
|
+
authentication.serviceAccount = {
|
|
1660
|
+
clientCredentials: true,
|
|
1661
|
+
description: `${opts.appName} backend service account`
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
manifest.authentication = authentication;
|
|
1665
|
+
if (!frontend) {
|
|
1666
|
+
delete manifest.lifecycle;
|
|
1667
|
+
if (manifest.capabilities) {
|
|
1668
|
+
delete manifest.capabilities.hub;
|
|
1669
|
+
if (manifest.capabilities.erp) {
|
|
1670
|
+
delete manifest.capabilities.erp.menuItems;
|
|
1671
|
+
delete manifest.capabilities.erp.pane;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
fs4.writeFileSync(appJsonPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
1676
|
+
}
|
|
1488
1677
|
async function scaffold({
|
|
1489
1678
|
appName,
|
|
1490
1679
|
description,
|
|
1491
1680
|
backend,
|
|
1492
1681
|
frontend,
|
|
1493
1682
|
shared,
|
|
1683
|
+
frontendPort = DEFAULT_FRONTEND_PORT,
|
|
1684
|
+
authentication,
|
|
1494
1685
|
targetDir: customTargetDir
|
|
1495
1686
|
}) {
|
|
1496
1687
|
const targetDir = customTargetDir ?? path4.resolve(process.cwd(), appName);
|
|
1497
|
-
const baseReplacements = buildPlaceholders({
|
|
1688
|
+
const baseReplacements = buildPlaceholders({
|
|
1689
|
+
appName,
|
|
1690
|
+
description,
|
|
1691
|
+
frontendPort
|
|
1692
|
+
});
|
|
1498
1693
|
if (fs4.existsSync(targetDir)) {
|
|
1499
1694
|
throw new Error(`Directory "${appName}" already exists`);
|
|
1500
1695
|
}
|
|
1501
1696
|
fs4.mkdirSync(targetDir, { recursive: true });
|
|
1502
1697
|
copyDir(shared.dir, targetDir, baseReplacements);
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1698
|
+
if (frontend) {
|
|
1699
|
+
const frontendDest = path4.join(targetDir, "packages", "frontend");
|
|
1700
|
+
const frontendReplacements = buildReplacements(baseReplacements, frontend);
|
|
1701
|
+
copyDir(
|
|
1702
|
+
frontend.dir,
|
|
1703
|
+
frontendDest,
|
|
1704
|
+
frontendReplacements,
|
|
1705
|
+
frontend.meta.type,
|
|
1706
|
+
appName
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
if (backend) {
|
|
1710
|
+
const backendDest = path4.join(targetDir, "packages", "backend");
|
|
1711
|
+
const backendReplacements = buildReplacements(baseReplacements, backend);
|
|
1712
|
+
copyDir(
|
|
1713
|
+
backend.dir,
|
|
1714
|
+
backendDest,
|
|
1715
|
+
backendReplacements,
|
|
1716
|
+
backend.meta.type,
|
|
1717
|
+
appName
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
if (authentication) {
|
|
1721
|
+
applyManifestAuthentication(path4.join(targetDir, "app.json"), {
|
|
1722
|
+
appName,
|
|
1723
|
+
frontendPort,
|
|
1724
|
+
authentication
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
const runtimeVersions = backend?.meta.runtimeVersions ?? {};
|
|
1522
1728
|
if (Object.keys(runtimeVersions).length > 0) {
|
|
1523
1729
|
const toolVersions = Object.entries(runtimeVersions).map(([tool, version]) => `${tool} ${version}`).join("\n");
|
|
1524
1730
|
fs4.writeFileSync(
|
|
@@ -1585,14 +1791,163 @@ function getTemplatesByType(templates, type) {
|
|
|
1585
1791
|
}
|
|
1586
1792
|
|
|
1587
1793
|
// src/posthog.ts
|
|
1794
|
+
import { randomUUID } from "crypto";
|
|
1795
|
+
import fs6 from "fs";
|
|
1796
|
+
import os2 from "os";
|
|
1797
|
+
import path6 from "path";
|
|
1588
1798
|
import { PostHog } from "posthog-node";
|
|
1589
1799
|
var TOKEN = "phc_SkFAxTIa4RUQxotci1Ftdw7xefZK2Ax0LEIGcl8zuD2";
|
|
1800
|
+
var CLI_VERSION = "0.5.1";
|
|
1590
1801
|
var client = TOKEN ? new PostHog(TOKEN, { host: "https://e.jtl-cloud.com" }) : null;
|
|
1802
|
+
function telemetryDisabledByEnv() {
|
|
1803
|
+
const dnt = process.env.DO_NOT_TRACK;
|
|
1804
|
+
if (dnt && dnt !== "0" && dnt !== "false") return true;
|
|
1805
|
+
const optOut = process.env.JTL_CLI_TELEMETRY;
|
|
1806
|
+
if (optOut === "0" || optOut === "false") return true;
|
|
1807
|
+
return false;
|
|
1808
|
+
}
|
|
1809
|
+
function telemetryClient() {
|
|
1810
|
+
if (!client || telemetryDisabledByEnv()) return null;
|
|
1811
|
+
return client;
|
|
1812
|
+
}
|
|
1813
|
+
function telemetryIdFile() {
|
|
1814
|
+
return process.env.JTL_CLI_TELEMETRY_ID_FILE ?? path6.join(os2.homedir(), ".config", "jtl-cli", "telemetry.json");
|
|
1815
|
+
}
|
|
1816
|
+
var cachedId;
|
|
1817
|
+
function anonymousId() {
|
|
1818
|
+
if (cachedId) return cachedId;
|
|
1819
|
+
const file = telemetryIdFile();
|
|
1820
|
+
try {
|
|
1821
|
+
const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
1822
|
+
if (typeof parsed?.anonymous_id === "string" && parsed.anonymous_id) {
|
|
1823
|
+
cachedId = parsed.anonymous_id;
|
|
1824
|
+
return parsed.anonymous_id;
|
|
1825
|
+
}
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
const id = randomUUID();
|
|
1829
|
+
try {
|
|
1830
|
+
fs6.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1831
|
+
fs6.writeFileSync(file, JSON.stringify({ anonymous_id: id }, null, 2));
|
|
1832
|
+
} catch {
|
|
1833
|
+
}
|
|
1834
|
+
cachedId = id;
|
|
1835
|
+
return id;
|
|
1836
|
+
}
|
|
1837
|
+
function baseProps() {
|
|
1838
|
+
return {
|
|
1839
|
+
cli_version: CLI_VERSION,
|
|
1840
|
+
node_version: process.version,
|
|
1841
|
+
os: process.platform,
|
|
1842
|
+
arch: process.arch,
|
|
1843
|
+
ci: Boolean(process.env.CI)
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function captureCommand(command, properties = {}) {
|
|
1847
|
+
const c = telemetryClient();
|
|
1848
|
+
if (!c) return;
|
|
1849
|
+
c.capture({
|
|
1850
|
+
distinctId: anonymousId(),
|
|
1851
|
+
event: "cli_command",
|
|
1852
|
+
properties: { command, ...baseProps(), ...properties }
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
function captureError(error, properties = {}) {
|
|
1856
|
+
const c = telemetryClient();
|
|
1857
|
+
if (!c) return;
|
|
1858
|
+
try {
|
|
1859
|
+
c.captureException(error, anonymousId(), { ...baseProps(), ...properties });
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1591
1863
|
async function shutdownPostHog() {
|
|
1592
1864
|
await client?.shutdown();
|
|
1593
1865
|
}
|
|
1594
1866
|
|
|
1595
1867
|
// src/index.ts
|
|
1868
|
+
var NONE = "__none__";
|
|
1869
|
+
function templateSelectOptions(templates) {
|
|
1870
|
+
return templates.map((t) => ({
|
|
1871
|
+
value: t.packageName,
|
|
1872
|
+
label: t.meta.label ?? t.packageName,
|
|
1873
|
+
hint: t.meta.hint
|
|
1874
|
+
}));
|
|
1875
|
+
}
|
|
1876
|
+
function halfSelectOptions(templates, noneHint) {
|
|
1877
|
+
return [
|
|
1878
|
+
...templateSelectOptions(templates),
|
|
1879
|
+
{ value: NONE, label: "None", hint: noneHint }
|
|
1880
|
+
];
|
|
1881
|
+
}
|
|
1882
|
+
function cancelScaffold() {
|
|
1883
|
+
p4.cancel("Cancelled.");
|
|
1884
|
+
process.exit(0);
|
|
1885
|
+
}
|
|
1886
|
+
async function runNextScaffold(backends, frontends, shared) {
|
|
1887
|
+
const cancelOpts = { onCancel: cancelScaffold };
|
|
1888
|
+
const answers = await p4.group(
|
|
1889
|
+
{
|
|
1890
|
+
appName: () => p4.text({
|
|
1891
|
+
message: "App name",
|
|
1892
|
+
placeholder: "my-jtl-app",
|
|
1893
|
+
validate: (value) => {
|
|
1894
|
+
if (!value) return "App name is required";
|
|
1895
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
1896
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
1897
|
+
}
|
|
1898
|
+
}),
|
|
1899
|
+
description: () => p4.text({ message: "Description", placeholder: "A JTL Platform app" }),
|
|
1900
|
+
frontend: () => p4.select({
|
|
1901
|
+
message: "Frontend (adds a public client for browser login)",
|
|
1902
|
+
options: halfSelectOptions(frontends, "no frontend")
|
|
1903
|
+
}),
|
|
1904
|
+
backend: () => p4.select({
|
|
1905
|
+
message: "Backend (adds a service account for the JTL API)",
|
|
1906
|
+
options: halfSelectOptions(backends, "no backend")
|
|
1907
|
+
})
|
|
1908
|
+
},
|
|
1909
|
+
cancelOpts
|
|
1910
|
+
);
|
|
1911
|
+
const selectedFrontend = frontends.find(
|
|
1912
|
+
(t) => t.packageName === answers.frontend
|
|
1913
|
+
);
|
|
1914
|
+
const selectedBackend = backends.find(
|
|
1915
|
+
(t) => t.packageName === answers.backend
|
|
1916
|
+
);
|
|
1917
|
+
if (!selectedFrontend && !selectedBackend) {
|
|
1918
|
+
p4.cancel("Pick at least a frontend or a backend.");
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
const s = p4.spinner();
|
|
1922
|
+
s.start("Scaffolding project");
|
|
1923
|
+
const frontendPort = selectedFrontend ? await pickFrontendPort() : void 0;
|
|
1924
|
+
await scaffold({
|
|
1925
|
+
appName: answers.appName,
|
|
1926
|
+
description: answers.description || "A JTL Platform app",
|
|
1927
|
+
backend: selectedBackend,
|
|
1928
|
+
frontend: selectedFrontend,
|
|
1929
|
+
shared,
|
|
1930
|
+
frontendPort,
|
|
1931
|
+
authentication: {
|
|
1932
|
+
frontend: Boolean(selectedFrontend),
|
|
1933
|
+
backend: Boolean(selectedBackend)
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
s.stop("Project scaffolded");
|
|
1937
|
+
if (selectedFrontend) {
|
|
1938
|
+
p4.note(
|
|
1939
|
+
`The frontend dev server runs on ${pc4.cyan(`http://localhost:${frontendPort}`)}, and its ${pc4.cyan("/callback")} route is registered as the public client's redirect URI.`,
|
|
1940
|
+
"Frontend login"
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
printNextSteps(answers.appName);
|
|
1944
|
+
p4.outro(pc4.green("Done! Happy building."));
|
|
1945
|
+
return {
|
|
1946
|
+
next: true,
|
|
1947
|
+
frontend: selectedFrontend ? selectedFrontend.meta.label ?? selectedFrontend.packageName : "none",
|
|
1948
|
+
backend: selectedBackend ? selectedBackend.meta.label ?? selectedBackend.packageName : "none"
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1596
1951
|
async function runScaffold() {
|
|
1597
1952
|
console.log();
|
|
1598
1953
|
p4.intro(pc4.bgCyan(pc4.black(" create-cloud-app ")));
|
|
@@ -1604,6 +1959,9 @@ async function runScaffold() {
|
|
|
1604
1959
|
p4.cancel("No templates found. Ensure template packages are installed.");
|
|
1605
1960
|
process.exit(1);
|
|
1606
1961
|
}
|
|
1962
|
+
if (process.argv.includes("--next")) {
|
|
1963
|
+
return runNextScaffold(backends, frontends, shared);
|
|
1964
|
+
}
|
|
1607
1965
|
const answers = await p4.group(
|
|
1608
1966
|
{
|
|
1609
1967
|
appName: () => p4.text({
|
|
@@ -1621,19 +1979,11 @@ async function runScaffold() {
|
|
|
1621
1979
|
}),
|
|
1622
1980
|
backend: () => p4.select({
|
|
1623
1981
|
message: "Backend",
|
|
1624
|
-
options: backends
|
|
1625
|
-
value: t.packageName,
|
|
1626
|
-
label: t.meta.label ?? t.packageName,
|
|
1627
|
-
hint: t.meta.hint
|
|
1628
|
-
}))
|
|
1982
|
+
options: templateSelectOptions(backends)
|
|
1629
1983
|
}),
|
|
1630
1984
|
frontend: () => p4.select({
|
|
1631
1985
|
message: "Frontend",
|
|
1632
|
-
options: frontends
|
|
1633
|
-
value: t.packageName,
|
|
1634
|
-
label: t.meta.label ?? t.packageName,
|
|
1635
|
-
hint: t.meta.hint
|
|
1636
|
-
}))
|
|
1986
|
+
options: templateSelectOptions(frontends)
|
|
1637
1987
|
})
|
|
1638
1988
|
},
|
|
1639
1989
|
{
|
|
@@ -1659,9 +2009,17 @@ async function runScaffold() {
|
|
|
1659
2009
|
shared
|
|
1660
2010
|
});
|
|
1661
2011
|
s.stop("Project scaffolded");
|
|
2012
|
+
printNextSteps(answers.appName);
|
|
2013
|
+
p4.outro(pc4.green("Done! Happy building."));
|
|
2014
|
+
return {
|
|
2015
|
+
backend: selectedBackend.meta.label ?? selectedBackend.packageName,
|
|
2016
|
+
frontend: selectedFrontend.meta.label ?? selectedFrontend.packageName
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
function printNextSteps(appName) {
|
|
1662
2020
|
p4.note(
|
|
1663
2021
|
[
|
|
1664
|
-
`${pc4.cyan("cd")} ${
|
|
2022
|
+
`${pc4.cyan("cd")} ${appName}`,
|
|
1665
2023
|
`${pc4.cyan("npm install")}`,
|
|
1666
2024
|
`${pc4.cyan("npm run register")}`,
|
|
1667
2025
|
`${pc4.cyan("npm run dev")}`,
|
|
@@ -1669,18 +2027,34 @@ async function runScaffold() {
|
|
|
1669
2027
|
].join("\n"),
|
|
1670
2028
|
"Next steps"
|
|
1671
2029
|
);
|
|
1672
|
-
p4.outro(pc4.green("Done! Happy building."));
|
|
1673
2030
|
}
|
|
1674
2031
|
async function main() {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
2032
|
+
const command = process.argv[2] === "register" ? "register" : process.argv[2] === "listing" ? "listing" : "scaffold";
|
|
2033
|
+
const startedAt = Date.now();
|
|
2034
|
+
try {
|
|
2035
|
+
let detail = {};
|
|
2036
|
+
if (command === "register") {
|
|
2037
|
+
await register(process.argv.slice(3));
|
|
2038
|
+
} else if (command === "listing") {
|
|
2039
|
+
await listing(process.argv.slice(3));
|
|
2040
|
+
} else {
|
|
2041
|
+
detail = await runScaffold();
|
|
2042
|
+
}
|
|
2043
|
+
captureCommand(command, {
|
|
2044
|
+
success: true,
|
|
2045
|
+
duration_ms: Date.now() - startedAt,
|
|
2046
|
+
...detail
|
|
2047
|
+
});
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
const bail2 = err instanceof BailError;
|
|
2050
|
+
captureCommand(command, {
|
|
2051
|
+
success: false,
|
|
2052
|
+
duration_ms: Date.now() - startedAt,
|
|
2053
|
+
outcome: bail2 ? "bailed" : "error"
|
|
2054
|
+
});
|
|
2055
|
+
if (!bail2) captureError(err, { command });
|
|
2056
|
+
throw err;
|
|
1682
2057
|
}
|
|
1683
|
-
await runScaffold();
|
|
1684
2058
|
}
|
|
1685
2059
|
main().then(() => shutdownPostHog()).catch(async (err) => {
|
|
1686
2060
|
await shutdownPostHog();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtl-software/create-cloud-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool for scaffolding JTL Platform cloud apps",
|
|
6
6
|
"bin": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"@clack/prompts": "^1.7.0",
|
|
21
21
|
"@jtl-software/cloud-app-template-backend-dotnet": "0.0.12",
|
|
22
22
|
"@jtl-software/cloud-app-template-backend-node": "0.0.15",
|
|
23
|
-
"@jtl-software/cloud-app-template-backend-php": "0.1.
|
|
24
|
-
"@jtl-software/cloud-app-template-frontend-react": "0.
|
|
25
|
-
"@jtl-software/cloud-app-template-shared": "0.1.
|
|
23
|
+
"@jtl-software/cloud-app-template-backend-php": "0.1.2",
|
|
24
|
+
"@jtl-software/cloud-app-template-frontend-react": "0.1.1",
|
|
25
|
+
"@jtl-software/cloud-app-template-shared": "0.1.2",
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
27
|
"posthog-node": "^5.48.1"
|
|
28
28
|
},
|