@capgo/cli 4.7.0 → 4.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [4.8.1](https://github.com/Cap-go/CLI/compare/v4.8.0...v4.8.1) (2024-05-11)
6
+
7
+ ## [4.8.0](https://github.com/Cap-go/CLI/compare/v4.7.0...v4.8.0) (2024-05-10)
8
+
9
+
10
+ ### Features
11
+
12
+ * auto select package manager and runner ([94f8bef](https://github.com/Cap-go/CLI/commit/94f8bef06efeafbd5cff1979f5ed75de2a523caa))
13
+
5
14
  ## [4.7.0](https://github.com/Cap-go/CLI/compare/v4.6.3...v4.7.0) (2024-05-10)
6
15
 
7
16
 
package/bun.lockb CHANGED
Binary file
package/dist/index.js CHANGED
@@ -92373,7 +92373,7 @@ var {
92373
92373
  // package.json
92374
92374
  var package_default = {
92375
92375
  name: "@capgo/cli",
92376
- version: "4.7.0",
92376
+ version: "4.8.1",
92377
92377
  description: "A CLI to upload to capgo servers",
92378
92378
  author: "github.com/riderx",
92379
92379
  license: "Apache 2.0",
@@ -92418,7 +92418,7 @@ var package_default = {
92418
92418
  dependencies: {
92419
92419
  "@aws-sdk/client-s3": "^3.563.0",
92420
92420
  "@capacitor/cli": "6.0.0",
92421
- "@capgo/find-package-manager": "^0.0.13",
92421
+ "@capgo/find-package-manager": "^0.0.16",
92422
92422
  "@clack/prompts": "^0.7.0",
92423
92423
  "@supabase/supabase-js": "^2.42.7",
92424
92424
  "@tomasklaen/checksum": "^1.1.0",
@@ -93558,20 +93558,41 @@ var findPackageManagerType = (path3 = ".", defaultPackageManager = "unknown") =>
93558
93558
  }
93559
93559
  return defaultPm;
93560
93560
  };
93561
- var findInstallCommand = (packageManagerType = findPackageManagerType()) => {
93561
+ var findInstallCommand = (packageManagerType = findPackageManagerType(), prefix = false) => {
93562
93562
  switch (packageManagerType) {
93563
93563
  case "bun":
93564
- return "install";
93564
+ return prefix ? "bun install" : "install";
93565
93565
  case "pnpm":
93566
- return "install";
93566
+ return prefix ? "pnpm install" : "install";
93567
93567
  case "yarn":
93568
- return "add";
93568
+ return prefix ? "yarn install" : "install";
93569
93569
  case "npm":
93570
- return "install";
93570
+ return prefix ? "npm install" : "install";
93571
+ case "unknown":
93572
+ return prefix ? "unknown unknown" : "unknown";
93571
93573
  default:
93572
- return "install";
93574
+ return prefix ? "npm install" : "install";
93573
93575
  }
93574
93576
  };
93577
+ var findPackageManagerRunner = (path3 = ".", defaultPackageManagerRunner = "npx") => {
93578
+ const bunPath = `${path3}/bun.lockb`;
93579
+ const pnpmPath = `${path3}/pnpm-lock.yaml`;
93580
+ const yarnPath = `${path3}/yarn.lock`;
93581
+ const npmPath = `${path3}/package-lock.json`;
93582
+ if ((0, import_fs.existsSync)(bunPath)) {
93583
+ return "bunx";
93584
+ }
93585
+ if ((0, import_fs.existsSync)(pnpmPath)) {
93586
+ return "pnpm exec";
93587
+ }
93588
+ if ((0, import_fs.existsSync)(yarnPath)) {
93589
+ return "yarn dlx";
93590
+ }
93591
+ if ((0, import_fs.existsSync)(npmPath)) {
93592
+ return "npx";
93593
+ }
93594
+ return defaultPackageManagerRunner;
93595
+ };
93575
93596
 
93576
93597
  // src/utils.ts
93577
93598
  var baseKey = ".capgo_key";
@@ -93592,7 +93613,7 @@ async function getConfig() {
93592
93613
  try {
93593
93614
  config = await (0, import_config.loadConfig)();
93594
93615
  } catch (err) {
93595
- f2.error("No capacitor config file found, run `cap init` first");
93616
+ f2.error(`No capacitor config file found, run \`cap init\` first ${formatError(err)}`);
93596
93617
  program.error("");
93597
93618
  }
93598
93619
  return config;
@@ -93793,7 +93814,7 @@ function findSavedKey(quiet = false) {
93793
93814
  key2 = (0, import_node_fs4.readFileSync)(keyPath, "utf8").trim();
93794
93815
  }
93795
93816
  if (!key2) {
93796
- f2.error(`Cannot find API key in local folder or global, please login first with npx @capacitor/cli login`);
93817
+ f2.error(`Cannot find API key in local folder or global, please login first with ${getPMAndCommand().runner} @capacitor/cli login`);
93797
93818
  program.error("");
93798
93819
  }
93799
93820
  return key2;
@@ -93895,7 +93916,8 @@ async function updateOrCreateVersion(supabase, update) {
93895
93916
  async function uploadUrl(supabase, appId, name) {
93896
93917
  const data = {
93897
93918
  app_id: appId,
93898
- name
93919
+ name,
93920
+ version: 0
93899
93921
  };
93900
93922
  try {
93901
93923
  const pathUploadLink = "private/upload_link";
@@ -93906,6 +93928,52 @@ async function uploadUrl(supabase, appId, name) {
93906
93928
  }
93907
93929
  return "";
93908
93930
  }
93931
+ async function prepareMultipart(supabase, appId, name) {
93932
+ const data = {
93933
+ app_id: appId,
93934
+ name,
93935
+ version: 1
93936
+ };
93937
+ try {
93938
+ const pathUploadLink = "private/upload_link";
93939
+ const res = await supabase.functions.invoke(pathUploadLink, { body: JSON.stringify(data) });
93940
+ return res.data;
93941
+ } catch (error) {
93942
+ f2.error(`Cannot get upload url ${formatError(error)}`);
93943
+ return null;
93944
+ }
93945
+ }
93946
+ async function finishMultipartDownload(key2, uploadId, url, parts) {
93947
+ const metadata = {
93948
+ action: "mpu-complete",
93949
+ uploadId,
93950
+ key: key2
93951
+ };
93952
+ await distribution_default.post(url, {
93953
+ json: {
93954
+ parts
93955
+ },
93956
+ searchParams: new URLSearchParams({ body: btoa(JSON.stringify(metadata)) })
93957
+ });
93958
+ }
93959
+ var PART_SIZE = 10 * 1024 * 1024;
93960
+ async function uploadMultipart(supabase, appId, name, data) {
93961
+ try {
93962
+ const multipartPrep = await prepareMultipart(supabase, appId, name);
93963
+ if (!multipartPrep) {
93964
+ return false;
93965
+ }
93966
+ const fileSize = data.length;
93967
+ const partCount = Math.ceil(fileSize / PART_SIZE);
93968
+ const uploadPromises = Array.from({ length: partCount }, (_3, index) => uploadPart(data, PART_SIZE, multipartPrep.url, multipartPrep.key, multipartPrep.uploadId, index));
93969
+ const parts = await Promise.all(uploadPromises);
93970
+ await finishMultipartDownload(multipartPrep.key, multipartPrep.uploadId, multipartPrep.url, parts);
93971
+ return true;
93972
+ } catch (e2) {
93973
+ f2.error(`Could not upload via multipart ${formatError(e2)}`);
93974
+ return false;
93975
+ }
93976
+ }
93909
93977
  async function deletedFailedVersion(supabase, appId, name) {
93910
93978
  const data = {
93911
93979
  app_id: appId,
@@ -93920,6 +93988,23 @@ async function deletedFailedVersion(supabase, appId, name) {
93920
93988
  return Promise.reject(new Error("Cannot delete failed version"));
93921
93989
  }
93922
93990
  }
93991
+ async function uploadPart(data, partsize, url, key2, uploadId, index) {
93992
+ const dataToUpload = data.subarray(
93993
+ partsize * index,
93994
+ partsize * (index + 1)
93995
+ );
93996
+ const metadata = {
93997
+ action: "mpu-uploadpart",
93998
+ uploadId,
93999
+ partNumber: index + 1,
94000
+ key: key2
94001
+ };
94002
+ const response = await distribution_default.put(url, {
94003
+ body: dataToUpload,
94004
+ searchParams: new URLSearchParams({ body: btoa(JSON.stringify(metadata)) })
94005
+ });
94006
+ return await response.json();
94007
+ }
93923
94008
  async function updateOrCreateChannel(supabase, update) {
93924
94009
  if (!update.app_id || !update.name || !update.created_by) {
93925
94010
  f2.error("missing app_id, name, or created_by");
@@ -93986,6 +94071,19 @@ function getHumanDate(createdA) {
93986
94071
  const date = new Date(createdA || "");
93987
94072
  return date.toLocaleString();
93988
94073
  }
94074
+ var pmFetched = false;
94075
+ var pm = "npm";
94076
+ var pmCommand = "install";
94077
+ var pmRunner = "npx";
94078
+ function getPMAndCommand() {
94079
+ if (pmFetched)
94080
+ return { pm, command: pmCommand, installCommand: `${pm} ${pmCommand}`, runner: pmRunner };
94081
+ pm = findPackageManagerType(".", "npm");
94082
+ pmCommand = findInstallCommand(pm);
94083
+ pmFetched = true;
94084
+ pmRunner = findPackageManagerRunner();
94085
+ return { pm, command: pmCommand, installCommand: `${pm} ${pmCommand}`, runner: pmRunner };
94086
+ }
93989
94087
  async function getLocalDepenencies() {
93990
94088
  if (!(0, import_node_fs4.existsSync)("./package.json")) {
93991
94089
  f2.error("Missing package.json, you need to be in a capacitor project");
@@ -94011,9 +94109,9 @@ async function getLocalDepenencies() {
94011
94109
  }
94012
94110
  }
94013
94111
  if (!(0, import_node_fs4.existsSync)("./node_modules/")) {
94014
- const pm = findPackageManagerType(".", "npm");
94015
- const installCmd = findInstallCommand(pm);
94016
- f2.error(`Missing node_modules folder, please run ${pm} ${installCmd}`);
94112
+ const pm2 = findPackageManagerType(".", "npm");
94113
+ const installCmd = findInstallCommand(pm2);
94114
+ f2.error(`Missing node_modules folder, please run ${pm2} ${installCmd}`);
94017
94115
  program.error("");
94018
94116
  }
94019
94117
  let anyInvalid = false;
@@ -94021,9 +94119,9 @@ async function getLocalDepenencies() {
94021
94119
  const dependencyFolderExists = (0, import_node_fs4.existsSync)(`./node_modules/${key2}`);
94022
94120
  if (!dependencyFolderExists) {
94023
94121
  anyInvalid = true;
94024
- const pm = findPackageManagerType(".", "npm");
94025
- const installCmd = findInstallCommand(pm);
94026
- f2.error(`Missing dependency ${key2}, please run ${pm} ${installCmd}`);
94122
+ const pm2 = findPackageManagerType(".", "npm");
94123
+ const installCmd = findInstallCommand(pm2);
94124
+ f2.error(`Missing dependency ${key2}, please run ${pm2} ${installCmd}`);
94027
94125
  return { name: key2, version: value };
94028
94126
  }
94029
94127
  let hasNativeFiles = false;
@@ -94266,6 +94364,7 @@ async function checkAppExists(supabase, appid) {
94266
94364
  return !!app2;
94267
94365
  }
94268
94366
  async function checkAppExistsAndHasPermissionOrgErr(supabase, apikey, appid, requiredPermission) {
94367
+ const pm2 = getPMAndCommand();
94269
94368
  const permissions = await isAllowedAppOrg(supabase, apikey, appid);
94270
94369
  if (!permissions.okay) {
94271
94370
  switch (permissions.error) {
@@ -94275,7 +94374,7 @@ async function checkAppExistsAndHasPermissionOrgErr(supabase, apikey, appid, req
94275
94374
  break;
94276
94375
  }
94277
94376
  case "NO_APP": {
94278
- f2.error(`App ${appid} does not exist`);
94377
+ f2.error(`App ${appid} does not exist, run first \`${pm2.runner} @capgo/cli app add ${appid}\` to create it`);
94279
94378
  program.error("");
94280
94379
  break;
94281
94380
  }
@@ -94778,6 +94877,7 @@ var import_client_s3 = __toESM(require_dist_cjs71());
94778
94877
  var alertMb2 = 20;
94779
94878
  var UPLOAD_TIMEOUT = 12e4;
94780
94879
  async function uploadBundle(appid, options, shouldExit = true) {
94880
+ const pm2 = getPMAndCommand();
94781
94881
  oe(`Uploading`);
94782
94882
  await checkLatest();
94783
94883
  let { bundle: bundle2, path: path3, channel: channel2 } = options;
@@ -94801,7 +94901,7 @@ async function uploadBundle(appid, options, shouldExit = true) {
94801
94901
  const snag = useLogSnag();
94802
94902
  channel2 = channel2 || "dev";
94803
94903
  const config = await getConfig();
94804
- const localS3 = (config.app.extConfig.plugins && config.app.extConfig.plugins.CapacitorUpdater && config.app.extConfig.plugins.CapacitorUpdater.localS3) === true;
94904
+ const localS3 = (config?.app?.extConfig?.plugins && config?.app?.extConfig?.plugins?.CapacitorUpdater && config?.app?.extConfig?.plugins?.CapacitorUpdater?.localS3) === true;
94805
94905
  const checkNotifyAppReady = options.codeCheck;
94806
94906
  appid = appid || config?.app?.appId;
94807
94907
  const uuid = (0, import_node_crypto3.randomUUID)().split("-")[0];
@@ -94815,8 +94915,8 @@ async function uploadBundle(appid, options, shouldExit = true) {
94815
94915
  f2.error(`Missing API key, you need to provide a API key to upload your bundle`);
94816
94916
  program.error("");
94817
94917
  }
94818
- if (!appid || !bundle2 || !path3) {
94819
- f2.error("Missing argument, you need to provide a appid and a bundle and a path, or be in a capacitor project");
94918
+ if (!appid || !path3) {
94919
+ f2.error("Missing argument, you need to provide a appid and a path (--path), or be in a capacitor project");
94820
94920
  program.error("");
94821
94921
  }
94822
94922
  if (s3BucketName || s3Region || s3Apikey || s3Apisecret) {
@@ -94863,7 +94963,7 @@ async function uploadBundle(appid, options, shouldExit = true) {
94863
94963
  localDependencies = localDependenciesWithChannel;
94864
94964
  if (finalCompatibility.find((x3) => x3.localVersion !== x3.remoteVersion)) {
94865
94965
  f2.error(`Your bundle is not compatible with the channel ${channel2}`);
94866
- f2.warn(`You can check compatibility with "npx @capgo/cli bundle compatibility"`);
94966
+ f2.warn(`You can check compatibility with "${pm2.runner} @capgo/cli bundle compatibility"`);
94867
94967
  if (autoMinUpdateVersion) {
94868
94968
  minUpdateVersion = bundle2;
94869
94969
  f2.info(`Auto set min-update-version to ${minUpdateVersion}`);
@@ -95030,23 +95130,27 @@ The app size is ${mbSize} Mb, this may take a while to download for users
95030
95130
  if (!external && zipped) {
95031
95131
  const spinner = de();
95032
95132
  spinner.start(`Uploading Bundle`);
95033
- const url = await uploadUrl(supabase, appid, bundle2);
95034
- if (!url) {
95035
- f2.error(`Cannot get upload url`);
95036
- program.error("");
95037
- }
95038
95133
  const startTime = import_node_perf_hooks.performance.now();
95039
95134
  try {
95040
- await distribution_default.put(url, {
95041
- timeout: options.timeout || UPLOAD_TIMEOUT,
95042
- retry: 5,
95043
- body: zipped,
95044
- headers: !localS3 ? {
95045
- "Content-Type": "application/octet-stream",
95046
- "Cache-Control": "public, max-age=456789, immutable",
95047
- "x-amz-meta-crc32": checksum
95048
- } : void 0
95049
- });
95135
+ if (options.multipart !== void 0 && options.multipart) {
95136
+ await uploadMultipart(supabase, appid, bundle2, zipped);
95137
+ } else {
95138
+ const url = await uploadUrl(supabase, appid, bundle2);
95139
+ if (!url) {
95140
+ f2.error(`Cannot get upload url`);
95141
+ program.error("");
95142
+ }
95143
+ await distribution_default.put(url, {
95144
+ timeout: options.timeout || UPLOAD_TIMEOUT,
95145
+ retry: 5,
95146
+ body: zipped,
95147
+ headers: !localS3 ? {
95148
+ "Content-Type": "application/octet-stream",
95149
+ "Cache-Control": "public, max-age=456789, immutable",
95150
+ "x-amz-meta-crc32": checksum
95151
+ } : void 0
95152
+ });
95153
+ }
95050
95154
  } catch (errorUpload) {
95051
95155
  const endTime2 = import_node_perf_hooks.performance.now();
95052
95156
  const uploadTime2 = ((endTime2 - startTime) / 1e3).toFixed(2);
@@ -95143,7 +95247,8 @@ async function uploadCommand(apikey, options) {
95143
95247
  }
95144
95248
  }
95145
95249
  async function uploadDeprecatedCommand(apikey, options) {
95146
- f2.warn('\u26A0\uFE0F This command is deprecated, use "npx @capgo/cli bundle upload" instead \u26A0\uFE0F');
95250
+ const pm2 = getPMAndCommand();
95251
+ f2.warn(`\u26A0\uFE0F This command is deprecated, use "${pm2.runner} @capgo/cli bundle upload" instead \u26A0\uFE0F`);
95147
95252
  try {
95148
95253
  await uploadBundle(apikey, options, true);
95149
95254
  } catch (error) {
@@ -95156,6 +95261,10 @@ async function uploadDeprecatedCommand(apikey, options) {
95156
95261
  var import_node_fs9 = require("node:fs");
95157
95262
  var import_node_os3 = require("node:os");
95158
95263
  var import_node_process14 = __toESM(require("node:process"));
95264
+ async function doLoginExists() {
95265
+ const userHomeDir = (0, import_node_os3.homedir)();
95266
+ return (0, import_node_fs9.existsSync)(`${userHomeDir}/.capgo`) || (0, import_node_fs9.existsSync)(".capgo");
95267
+ }
95159
95268
  async function login(apikey, options, shouldExit = true) {
95160
95269
  if (shouldExit)
95161
95270
  oe(`Login to Capgo`);
@@ -95343,7 +95452,7 @@ async function addApp(appId, options, throwErr = true) {
95343
95452
  f2.error(`App ${appId} already exist`);
95344
95453
  program.error("");
95345
95454
  } else if (appExist) {
95346
- return true;
95455
+ return false;
95347
95456
  }
95348
95457
  const { error: orgError, data: allOrganizations } = await supabase.rpc("get_orgs_v5");
95349
95458
  if (orgError) {
@@ -95467,27 +95576,29 @@ async function markStep(userId, snag, step) {
95467
95576
  return markSnag("onboarding-v2", userId, snag, `onboarding-step-${step}`);
95468
95577
  }
95469
95578
  async function step2(userId, snag, appId, options) {
95579
+ const pm2 = getPMAndCommand();
95470
95580
  const doAdd = await se({ message: `Add ${appId} in Capgo?` });
95471
95581
  await cancelCommand2(doAdd, userId, snag);
95472
95582
  if (doAdd) {
95473
95583
  const s = de();
95474
- s.start(`Running: npx @capgo/cli@latest app add ${appId}`);
95584
+ s.start(`Running: ${pm2.runner} @capgo/cli@latest app add ${appId}`);
95475
95585
  const addRes = await addApp(appId, options, false);
95476
95586
  if (!addRes)
95477
95587
  s.stop(`App already add \u2705`);
95478
95588
  else
95479
95589
  s.stop(`App add Done \u2705`);
95480
95590
  } else {
95481
- f2.info(`Run yourself "npx @capgo/cli@latest app add ${appId}"`);
95591
+ f2.info(`Run yourself "${pm2.runner} @capgo/cli@latest app add ${appId}"`);
95482
95592
  }
95483
95593
  await markStep(userId, snag, 2);
95484
95594
  }
95485
95595
  async function step3(userId, snag, apikey, appId) {
95596
+ const pm2 = getPMAndCommand();
95486
95597
  const doChannel = await se({ message: `Create default channel ${defaultChannel} for ${appId} in Capgo?` });
95487
95598
  await cancelCommand2(doChannel, userId, snag);
95488
95599
  if (doChannel) {
95489
95600
  const s = de();
95490
- s.start(`Running: npx @capgo/cli@latest channel add ${defaultChannel} ${appId} --default`);
95601
+ s.start(`Running: ${pm2.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default`);
95491
95602
  const addChannelRes = await addChannel(defaultChannel, appId, {
95492
95603
  default: true,
95493
95604
  apikey
@@ -95497,13 +95608,14 @@ async function step3(userId, snag, apikey, appId) {
95497
95608
  else
95498
95609
  s.stop(`Channel add Done \u2705`);
95499
95610
  } else {
95500
- f2.info(`Run yourself "npx @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`);
95611
+ f2.info(`Run yourself "${pm2.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`);
95501
95612
  }
95502
95613
  await markStep(userId, snag, 3);
95503
95614
  }
95504
95615
  var urlMigrateV6 = "https://capacitorjs.com/docs/updating/6-0";
95505
95616
  var urlMigrateV5 = "https://capacitorjs.com/docs/updating/5-0";
95506
95617
  async function step4(userId, snag, apikey, appId) {
95618
+ const pm2 = getPMAndCommand();
95507
95619
  const doInstall = await se({ message: `Automatic Install "@capgo/capacitor-updater" dependency in ${appId}?` });
95508
95620
  await cancelCommand2(doInstall, userId, snag);
95509
95621
  if (doInstall) {
@@ -95527,22 +95639,20 @@ async function step4(userId, snag, apikey, appId) {
95527
95639
  s.stop(`@capacitor/core version is ${coreVersion}, please update to Capacitor v6: ${urlMigrateV6} to access the best features of Capgo`);
95528
95640
  versionToInstall = "^5.0.0";
95529
95641
  }
95530
- const pm = findPackageManagerType();
95531
- if (pm === "unknown") {
95642
+ if (pm2.pm === "unknown") {
95532
95643
  s.stop("Error");
95533
- f2.warn(`Cannot reconize package manager, please run \`capgo init\` in a capacitor project with npm, pnpm or yarn`);
95644
+ f2.warn(`Cannot reconize package manager, please run \`capgo init\` in a capacitor project with npm, pnpm, bun or yarn`);
95534
95645
  $e(`Bye \u{1F44B}`);
95535
95646
  import_node_process16.default.exit();
95536
95647
  }
95537
- const installCmd = findInstallCommand(pm);
95538
95648
  if (pack.dependencies["@capgo/capacitor-updater"]) {
95539
95649
  s.stop(`Capgo already installed \u2705`);
95540
95650
  } else {
95541
- await (0, import_node_child_process6.execSync)(`${pm} ${installCmd} @capgo/capacitor-updater@${versionToInstall}`, execOption);
95651
+ await (0, import_node_child_process6.execSync)(`${pm2.installCommand} @capgo/capacitor-updater@${versionToInstall}`, execOption);
95542
95652
  s.stop(`Install Done \u2705`);
95543
95653
  }
95544
95654
  } else {
95545
- f2.info(`Run yourself "npm i @capgo/capacitor-updater@latest"`);
95655
+ f2.info(`Run yourself "${pm2.installCommand} @capgo/capacitor-updater@latest"`);
95546
95656
  }
95547
95657
  await markStep(userId, snag, 4);
95548
95658
  }
@@ -95596,11 +95706,12 @@ ${codeInject};
95596
95706
  }
95597
95707
  }
95598
95708
  async function step6(userId, snag, apikey, appId) {
95709
+ const pm2 = getPMAndCommand();
95599
95710
  const doEncrypt = await se({ message: `Automatic configure end-to-end encryption in ${appId} updates?` });
95600
95711
  await cancelCommand2(doEncrypt, userId, snag);
95601
95712
  if (doEncrypt) {
95602
95713
  const s = de();
95603
- s.start(`Running: npx @capgo/cli@latest key create`);
95714
+ s.start(`Running: ${pm2.runner} @capgo/cli@latest key create`);
95604
95715
  const keyRes = await createKey({ force: true }, false);
95605
95716
  if (!keyRes) {
95606
95717
  s.stop("Error");
@@ -95615,13 +95726,14 @@ async function step6(userId, snag, apikey, appId) {
95615
95726
  await markStep(userId, snag, 6);
95616
95727
  }
95617
95728
  async function step7(userId, snag, apikey, appId) {
95618
- const doBuild = await se({ message: `Automatic build ${appId} with "npm run build" ?` });
95729
+ const pm2 = getPMAndCommand();
95730
+ const doBuild = await se({ message: `Automatic build ${appId} with "${pm2.pm} run build" ?` });
95619
95731
  await cancelCommand2(doBuild, userId, snag);
95620
95732
  if (doBuild) {
95621
95733
  const s = de();
95622
95734
  const projectType = await findProjectType();
95623
95735
  const buildCommand = await findBuildCommandForProjectType(projectType);
95624
- s.start(`Running: npm run ${buildCommand} && npx cap sync`);
95736
+ s.start(`Running: ${pm2.pm} run ${buildCommand} && ${pm2.runner} cap sync`);
95625
95737
  const pack = JSON.parse((0, import_node_fs11.readFileSync)("package.json").toString());
95626
95738
  if (!pack.scripts[buildCommand]) {
95627
95739
  s.stop("Error");
@@ -95629,19 +95741,20 @@ async function step7(userId, snag, apikey, appId) {
95629
95741
  $e(`Bye \u{1F44B}`);
95630
95742
  import_node_process16.default.exit();
95631
95743
  }
95632
- (0, import_node_child_process6.execSync)(`npm run ${buildCommand} && npx cap sync`, execOption);
95744
+ (0, import_node_child_process6.execSync)(`${pm2.pm} run ${buildCommand} && ${pm2.runner} cap sync`, execOption);
95633
95745
  s.stop(`Build & Sync Done \u2705`);
95634
95746
  } else {
95635
- f2.info(`Build yourself with command: npm run build && npx cap sync`);
95747
+ f2.info(`Build yourself with command: ${pm2.pm} run build && ${pm2.runner} cap sync`);
95636
95748
  }
95637
95749
  await markStep(userId, snag, 7);
95638
95750
  }
95639
95751
  async function step8(userId, snag, apikey, appId) {
95752
+ const pm2 = getPMAndCommand();
95640
95753
  const doBundle = await se({ message: `Automatic upload ${appId} bundle to Capgo?` });
95641
95754
  await cancelCommand2(doBundle, userId, snag);
95642
95755
  if (doBundle) {
95643
95756
  const s = de();
95644
- s.start(`Running: npx @capgo/cli@latest bundle upload`);
95757
+ s.start(`Running: ${pm2.runner} @capgo/cli@latest bundle upload`);
95645
95758
  const uploadRes = await uploadBundle(appId, {
95646
95759
  channel: defaultChannel,
95647
95760
  apikey
@@ -95655,11 +95768,12 @@ async function step8(userId, snag, apikey, appId) {
95655
95768
  s.stop(`Upload Done \u2705`);
95656
95769
  }
95657
95770
  } else {
95658
- f2.info(`Upload yourself with command: npx @capgo/cli@latest bundle upload`);
95771
+ f2.info(`Upload yourself with command: ${pm2.runner} @capgo/cli@latest bundle upload`);
95659
95772
  }
95660
95773
  await markStep(userId, snag, 8);
95661
95774
  }
95662
95775
  async function step9(userId, snag) {
95776
+ const pm2 = getPMAndCommand();
95663
95777
  const doRun = await se({ message: `Run in device now ?` });
95664
95778
  await cancelCommand2(doRun, userId, snag);
95665
95779
  if (doRun) {
@@ -95676,11 +95790,11 @@ async function step9(userId, snag) {
95676
95790
  }
95677
95791
  const platform2 = plaformType;
95678
95792
  const s = de();
95679
- s.start(`Running: npx cap run ${platform2}`);
95680
- await (0, import_node_child_process6.spawnSync)("npx", ["cap", "run", platform2], { stdio: "inherit" });
95793
+ s.start(`Running: ${pm2.runner} cap run ${platform2}`);
95794
+ await (0, import_node_child_process6.spawnSync)(pm2.runner, ["cap", "run", platform2], { stdio: "inherit" });
95681
95795
  s.stop(`Started Done \u2705`);
95682
95796
  } else {
95683
- f2.info(`Run yourself with command: npx cap run <ios|android>`);
95797
+ f2.info(`Run yourself with command: ${pm2.runner} cap run <ios|android>`);
95684
95798
  }
95685
95799
  await markStep(userId, snag, 9);
95686
95800
  }
@@ -95697,20 +95811,20 @@ async function step10(userId, snag, supabase, appId) {
95697
95811
  }
95698
95812
  await markStep(userId, snag, 10);
95699
95813
  }
95700
- async function initApp(apikey, appId, options) {
95814
+ async function initApp(apikeyCommand, appId, options) {
95815
+ const pm2 = getPMAndCommand();
95701
95816
  oe(`Capgo onboarding \u{1F6EB}`);
95702
95817
  await checkLatest();
95703
95818
  const snag = useLogSnag();
95704
95819
  const config = await getConfig();
95705
95820
  appId = appId || config?.app?.appId;
95706
- apikey = apikey || findSavedKey();
95821
+ const apikey = apikeyCommand || findSavedKey();
95707
95822
  const log = de();
95708
- log.start("Running: npx @capgo/cli@latest login ***");
95709
- const loginRes = await login(apikey, options, false);
95710
- if (!loginRes)
95711
- log.stop("Login already done \u2705");
95712
- else
95823
+ if (!doLoginExists() || apikeyCommand) {
95824
+ log.start(`Running: ${pm2.runner} @capgo/cli@latest login ***`);
95825
+ await login(apikey, options, false);
95713
95826
  log.stop("Login Done \u2705");
95827
+ }
95714
95828
  const supabase = await createSupabaseClient(apikey);
95715
95829
  const userId = await verifyUser(supabase, apikey, ["upload", "all", "read", "write"]);
95716
95830
  await markStep(userId, snag, 1);
@@ -95726,7 +95840,7 @@ async function initApp(apikey, appId, options) {
95726
95840
  await markStep(userId, snag, 0);
95727
95841
  f2.info(`Welcome onboard \u2708\uFE0F!`);
95728
95842
  f2.info(`Your Capgo update system is setup`);
95729
- f2.info(`Next time use \`npx @capgo/cli@latest bundle upload\` to only upload your bundle`);
95843
+ f2.info(`Next time use \`${pm2.runner} @capgo/cli@latest bundle upload\` to only upload your bundle`);
95730
95844
  $e(`Bye \u{1F44B}`);
95731
95845
  import_node_process16.default.exit();
95732
95846
  }
@@ -96595,7 +96709,7 @@ var bundle = program.command("bundle").description("Manage bundle");
96595
96709
  bundle.command("upload [appId]").alias("u").description("Upload a new bundle in Capgo Cloud").action(uploadCommand).option("-a, --apikey <apikey>", "apikey to link to your account").option("-p, --path <path>", "path of the folder to upload").option("-c, --channel <channel>", "channel to link to").option("-e, --external <url>", "link to external url intead of upload to Capgo Cloud").option("--iv-session-key <key>", "Set the iv and session key for bundle url external").option("--s3-region <region>", "Region for your AWS S3 bucket").option("--s3-apikey <apikey>", "apikey for your AWS S3 account").option("--s3-apisecret <apisecret>", "api secret for your AWS S3 account").option("--s3-bucket-name <bucketName>", "Name for your AWS S3 bucket").option("--key <key>", "custom path for public signing key").option("--key-data <keyData>", "base64 public signing key").option("--bundle-url", "prints bundle url into stdout").option("--no-key", "ignore signing key and send clear update").option("--no-code-check", "Ignore checking if notifyAppReady() is called in soure code and index present in root folder").option("--display-iv-session", "Show in the console the iv and session key used to encrypt the update").option("-b, --bundle <bundle>", "bundle version number of the bundle to upload").option(
96596
96710
  "--min-update-version <minUpdateVersion>",
96597
96711
  "Minimal version required to update to this version. Used only if the disable auto update is set to metadata in channel"
96598
- ).option("--auto-min-update-version", "Set the min update version based on native packages").option("--ignore-metadata-check", "Ignores the metadata (node_modules) check when uploading").option("--timeout <timeout>", "Timeout for the upload process in seconds");
96712
+ ).option("--auto-min-update-version", "Set the min update version based on native packages").option("--ignore-metadata-check", "Ignores the metadata (node_modules) check when uploading").option("--timeout <timeout>", "Timeout for the upload process in seconds").option("--multipart", "Uses multipart protocol to upload data to S3");
96599
96713
  bundle.command("compatibility [appId]").action(checkCompatibilityCommand).option("-a, --apikey <apikey>", "apikey to link to your account").option("-c, --channel <channel>", "channel to check the compatibility with").option("--text", "output text instead of emojis");
96600
96714
  bundle.command("delete [bundleId] [appId]").alias("d").description("Delete a bundle in Capgo Cloud").action(deleteBundle).option("-a, --apikey <apikey>", "apikey to link to your account");
96601
96715
  bundle.command("list [appId]").alias("l").description("List bundle in Capgo Cloud").action(listBundle).option("-a, --apikey <apikey>", "apikey to link to your account");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
- "version": "4.7.0",
3
+ "version": "4.8.1",
4
4
  "description": "A CLI to upload to capgo servers",
5
5
  "author": "github.com/riderx",
6
6
  "license": "Apache 2.0",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@aws-sdk/client-s3": "^3.563.0",
47
47
  "@capacitor/cli": "6.0.0",
48
- "@capgo/find-package-manager": "^0.0.13",
48
+ "@capgo/find-package-manager": "^0.0.16",
49
49
  "@clack/prompts": "^0.7.0",
50
50
  "@supabase/supabase-js": "^2.42.7",
51
51
  "@tomasklaen/checksum": "^1.1.0",
package/src/api/app.ts CHANGED
@@ -3,7 +3,7 @@ import * as p from '@clack/prompts'
3
3
  import { program } from 'commander'
4
4
  import type { Database } from '../types/supabase.types'
5
5
  import type { OptionsBase } from '../utils'
6
- import { OrganizationPerm, isAllowedApp, isAllowedAppOrg } from '../utils'
6
+ import { OrganizationPerm, getPMAndCommand, isAllowedAppOrg } from '../utils'
7
7
 
8
8
  export async function checkAppExists(supabase: SupabaseClient<Database>, appid: string) {
9
9
  const { data: app } = await supabase
@@ -12,28 +12,8 @@ export async function checkAppExists(supabase: SupabaseClient<Database>, appid:
12
12
  return !!app
13
13
  }
14
14
 
15
- export async function checkAppExistsAndHasPermissionErr(supabase: SupabaseClient<Database>, apikey: string, appid: string, shouldExist = true) {
16
- const appExist = await checkAppExists(supabase, appid)
17
- const perm = await isAllowedApp(supabase, apikey, appid)
18
-
19
- if (appExist && !shouldExist) {
20
- p.log.error(`App ${appid} already exist`)
21
- program.error('')
22
- }
23
- if (!appExist && shouldExist) {
24
- p.log.error(`App ${appid} does not exist`)
25
- program.error('')
26
- }
27
- if (appExist && !perm) {
28
- p.log.error(`App ${appid} exist and you don't have permission to access it`)
29
- if (appid === 'io.ionic.starter')
30
- p.log.info('Modify your appid in your capacitor.config.json file to something unique, this is a default appid for ionic starter app')
31
-
32
- program.error('')
33
- }
34
- }
35
-
36
15
  export async function checkAppExistsAndHasPermissionOrgErr(supabase: SupabaseClient<Database>, apikey: string, appid: string, requiredPermission: OrganizationPerm) {
16
+ const pm = getPMAndCommand()
37
17
  const permissions = await isAllowedAppOrg(supabase, apikey, appid)
38
18
  if (!permissions.okay) {
39
19
  switch (permissions.error) {
@@ -43,7 +23,7 @@ export async function checkAppExistsAndHasPermissionOrgErr(supabase: SupabaseCli
43
23
  break
44
24
  }
45
25
  case 'NO_APP': {
46
- p.log.error(`App ${appid} does not exist`)
26
+ p.log.error(`App ${appid} does not exist, run first \`${pm.runner} @capgo/cli app add ${appid}\` to create it`)
47
27
  program.error('')
48
28
  break
49
29
  }
package/src/app/add.ts CHANGED
@@ -52,7 +52,7 @@ export async function addApp(appId: string, options: Options, throwErr = true) {
52
52
  program.error('')
53
53
  }
54
54
  else if (appExist) {
55
- return true
55
+ return false
56
56
  }
57
57
 
58
58
  const { error: orgError, data: allOrganizations } = await supabase
@@ -33,11 +33,13 @@ import {
33
33
  getLocalConfig,
34
34
  getLocalDepenencies,
35
35
  getOrganizationId,
36
+ getPMAndCommand,
36
37
  hasOrganizationPerm,
37
38
  regexSemver,
38
39
  requireUpdateMetadata,
39
40
  updateOrCreateChannel,
40
41
  updateOrCreateVersion,
42
+ uploadMultipart,
41
43
  uploadUrl,
42
44
  useLogSnag,
43
45
  verifyUser,
@@ -65,11 +67,13 @@ interface Options extends OptionsBase {
65
67
  autoMinUpdateVersion?: boolean
66
68
  ignoreMetadataCheck?: boolean
67
69
  timeout?: number
70
+ multipart?: boolean
68
71
  }
69
72
 
70
73
  const UPLOAD_TIMEOUT = 120000
71
74
 
72
75
  export async function uploadBundle(appid: string, options: Options, shouldExit = true) {
76
+ const pm = getPMAndCommand()
73
77
  p.intro(`Uploading`)
74
78
  await checkLatest()
75
79
  let { bundle, path, channel } = options
@@ -96,8 +100,8 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
96
100
  channel = channel || 'dev'
97
101
 
98
102
  const config = await getConfig()
99
- const localS3: boolean = (config.app.extConfig.plugins && config.app.extConfig.plugins.CapacitorUpdater
100
- && config.app.extConfig.plugins.CapacitorUpdater.localS3) === true
103
+ const localS3: boolean = (config?.app?.extConfig?.plugins && config?.app?.extConfig?.plugins?.CapacitorUpdater
104
+ && config?.app?.extConfig?.plugins?.CapacitorUpdater?.localS3) === true
101
105
 
102
106
  const checkNotifyAppReady = options.codeCheck
103
107
  appid = appid || config?.app?.appId
@@ -114,8 +118,8 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
114
118
  p.log.error(`Missing API key, you need to provide a API key to upload your bundle`)
115
119
  program.error('')
116
120
  }
117
- if (!appid || !bundle || !path) {
118
- p.log.error('Missing argument, you need to provide a appid and a bundle and a path, or be in a capacitor project')
121
+ if (!appid || !path) {
122
+ p.log.error('Missing argument, you need to provide a appid and a path (--path), or be in a capacitor project')
119
123
  program.error('')
120
124
  }
121
125
  // if one S3 variable is set, check that all are set
@@ -150,8 +154,6 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
150
154
  const supabase = await createSupabaseClient(options.apikey)
151
155
  const userId = await verifyUser(supabase, options.apikey, ['write', 'all', 'upload'])
152
156
  // Check we have app access to this appId
153
- // await checkAppExistsAndHasPermissionErr(supabase, options.apikey, appid);
154
-
155
157
  const permissions = await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appid, OrganizationPerm.upload)
156
158
 
157
159
  // Now if it does exist we will fetch the org id
@@ -186,7 +188,7 @@ export async function uploadBundle(appid: string, options: Options, shouldExit =
186
188
 
187
189
  if (finalCompatibility.find(x => x.localVersion !== x.remoteVersion)) {
188
190
  p.log.error(`Your bundle is not compatible with the channel ${channel}`)
189
- p.log.warn(`You can check compatibility with "npx @capgo/cli bundle compatibility"`)
191
+ p.log.warn(`You can check compatibility with "${pm.runner} @capgo/cli bundle compatibility"`)
190
192
 
191
193
  if (autoMinUpdateVersion) {
192
194
  minUpdateVersion = bundle
@@ -381,26 +383,32 @@ It will be also visible in your dashboard\n`)
381
383
  if (!external && zipped) {
382
384
  const spinner = p.spinner()
383
385
  spinner.start(`Uploading Bundle`)
384
-
385
- const url = await uploadUrl(supabase, appid, bundle)
386
- if (!url) {
387
- p.log.error(`Cannot get upload url`)
388
- program.error('')
389
- }
390
386
  const startTime = performance.now()
387
+
391
388
  try {
392
- await ky.put(url, {
393
- timeout: options.timeout || UPLOAD_TIMEOUT,
394
- retry: 5,
395
- body: zipped,
396
- headers: (!localS3
397
- ? {
398
- 'Content-Type': 'application/octet-stream',
399
- 'Cache-Control': 'public, max-age=456789, immutable',
400
- 'x-amz-meta-crc32': checksum,
401
- }
402
- : undefined),
403
- })
389
+ if (options.multipart !== undefined && options.multipart) {
390
+ await uploadMultipart(supabase, appid, bundle, zipped)
391
+ }
392
+ else {
393
+ const url = await uploadUrl(supabase, appid, bundle)
394
+ if (!url) {
395
+ p.log.error(`Cannot get upload url`)
396
+ program.error('')
397
+ }
398
+
399
+ await ky.put(url, {
400
+ timeout: options.timeout || UPLOAD_TIMEOUT,
401
+ retry: 5,
402
+ body: zipped,
403
+ headers: (!localS3
404
+ ? {
405
+ 'Content-Type': 'application/octet-stream',
406
+ 'Cache-Control': 'public, max-age=456789, immutable',
407
+ 'x-amz-meta-crc32': checksum,
408
+ }
409
+ : undefined),
410
+ })
411
+ }
404
412
  }
405
413
  catch (errorUpload) {
406
414
  const endTime = performance.now()
@@ -411,6 +419,7 @@ It will be also visible in your dashboard\n`)
411
419
  await deletedFailedVersion(supabase, appid, bundle)
412
420
  program.error('')
413
421
  }
422
+
414
423
  versionData.storage_provider = 'r2'
415
424
  const { error: dbError2 } = await updateOrCreateVersion(supabase, versionData)
416
425
  if (dbError2) {
@@ -511,7 +520,8 @@ export async function uploadCommand(apikey: string, options: Options) {
511
520
  }
512
521
 
513
522
  export async function uploadDeprecatedCommand(apikey: string, options: Options) {
514
- p.log.warn('⚠️ This command is deprecated, use "npx @capgo/cli bundle upload" instead ⚠️')
523
+ const pm = getPMAndCommand()
524
+ p.log.warn(`⚠️ This command is deprecated, use "${pm.runner} @capgo/cli bundle upload" instead ⚠️`)
515
525
  try {
516
526
  await uploadBundle(apikey, options, true)
517
527
  }
package/src/index.ts CHANGED
@@ -131,6 +131,7 @@ bundle
131
131
  .option('--auto-min-update-version', 'Set the min update version based on native packages')
132
132
  .option('--ignore-metadata-check', 'Ignores the metadata (node_modules) check when uploading')
133
133
  .option('--timeout <timeout>', 'Timeout for the upload process in seconds')
134
+ .option('--multipart', 'Uses multipart protocol to upload data to S3')
134
135
 
135
136
  bundle
136
137
  .command('compatibility [appId]')
package/src/init.ts CHANGED
@@ -2,7 +2,6 @@ import { readFileSync, writeFileSync } from 'node:fs'
2
2
  import type { ExecSyncOptions } from 'node:child_process'
3
3
  import { execSync, spawnSync } from 'node:child_process'
4
4
  import process from 'node:process'
5
- import { findInstallCommand, findPackageManagerType } from '@capgo/find-package-manager'
6
5
  import * as p from '@clack/prompts'
7
6
  import type { SupabaseClient } from '@supabase/supabase-js'
8
7
  import type LogSnag from 'logsnag'
@@ -12,11 +11,11 @@ import { markSnag, waitLog } from './app/debug'
12
11
  import { createKey } from './key'
13
12
  import { addChannel } from './channel/add'
14
13
  import { uploadBundle } from './bundle/upload'
15
- import { login } from './login'
14
+ import { doLoginExists, login } from './login'
16
15
  import { addApp } from './app/add'
17
16
  import { checkLatest } from './api/update'
18
17
  import type { Options } from './api/app'
19
- import { convertAppName, createSupabaseClient, findBuildCommandForProjectType, findMainFile, findMainFileForProjectType, findProjectType, findSavedKey, getConfig, useLogSnag, verifyUser } from './utils'
18
+ import { convertAppName, createSupabaseClient, findBuildCommandForProjectType, findMainFile, findMainFileForProjectType, findProjectType, findSavedKey, getConfig, getPMAndCommand, useLogSnag, verifyUser } from './utils'
20
19
 
21
20
  interface SuperOptions extends Options {
22
21
  local: boolean
@@ -40,11 +39,12 @@ async function markStep(userId: string, snag: LogSnag, step: number | string) {
40
39
  }
41
40
 
42
41
  async function step2(userId: string, snag: LogSnag, appId: string, options: SuperOptions) {
42
+ const pm = getPMAndCommand()
43
43
  const doAdd = await p.confirm({ message: `Add ${appId} in Capgo?` })
44
44
  await cancelCommand(doAdd, userId, snag)
45
45
  if (doAdd) {
46
46
  const s = p.spinner()
47
- s.start(`Running: npx @capgo/cli@latest app add ${appId}`)
47
+ s.start(`Running: ${pm.runner} @capgo/cli@latest app add ${appId}`)
48
48
  const addRes = await addApp(appId, options, false)
49
49
  if (!addRes)
50
50
  s.stop(`App already add ✅`)
@@ -52,18 +52,19 @@ async function step2(userId: string, snag: LogSnag, appId: string, options: Supe
52
52
  s.stop(`App add Done ✅`)
53
53
  }
54
54
  else {
55
- p.log.info(`Run yourself "npx @capgo/cli@latest app add ${appId}"`)
55
+ p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest app add ${appId}"`)
56
56
  }
57
57
  await markStep(userId, snag, 2)
58
58
  }
59
59
 
60
60
  async function step3(userId: string, snag: LogSnag, apikey: string, appId: string) {
61
+ const pm = getPMAndCommand()
61
62
  const doChannel = await p.confirm({ message: `Create default channel ${defaultChannel} for ${appId} in Capgo?` })
62
63
  await cancelCommand(doChannel, userId, snag)
63
64
  if (doChannel) {
64
65
  const s = p.spinner()
65
66
  // create production channel public
66
- s.start(`Running: npx @capgo/cli@latest channel add ${defaultChannel} ${appId} --default`)
67
+ s.start(`Running: ${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default`)
67
68
  const addChannelRes = await addChannel(defaultChannel, appId, {
68
69
  default: true,
69
70
  apikey,
@@ -74,7 +75,7 @@ async function step3(userId: string, snag: LogSnag, apikey: string, appId: strin
74
75
  s.stop(`Channel add Done ✅`)
75
76
  }
76
77
  else {
77
- p.log.info(`Run yourself "npx @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`)
78
+ p.log.info(`Run yourself "${pm.runner} @capgo/cli@latest channel add ${defaultChannel} ${appId} --default"`)
78
79
  }
79
80
  await markStep(userId, snag, 3)
80
81
  }
@@ -82,6 +83,7 @@ async function step3(userId: string, snag: LogSnag, apikey: string, appId: strin
82
83
  const urlMigrateV6 = 'https://capacitorjs.com/docs/updating/6-0'
83
84
  const urlMigrateV5 = 'https://capacitorjs.com/docs/updating/5-0'
84
85
  async function step4(userId: string, snag: LogSnag, apikey: string, appId: string) {
86
+ const pm = getPMAndCommand()
85
87
  const doInstall = await p.confirm({ message: `Automatic Install "@capgo/capacitor-updater" dependency in ${appId}?` })
86
88
  await cancelCommand(doInstall, userId, snag)
87
89
  if (doInstall) {
@@ -107,27 +109,25 @@ async function step4(userId: string, snag: LogSnag, apikey: string, appId: strin
107
109
  s.stop(`@capacitor/core version is ${coreVersion}, please update to Capacitor v6: ${urlMigrateV6} to access the best features of Capgo`)
108
110
  versionToInstall = '^5.0.0'
109
111
  }
110
- const pm = findPackageManagerType()
111
- if (pm === 'unknown') {
112
+ if (pm.pm === 'unknown') {
112
113
  s.stop('Error')
113
- p.log.warn(`Cannot reconize package manager, please run \`capgo init\` in a capacitor project with npm, pnpm or yarn`)
114
+ p.log.warn(`Cannot reconize package manager, please run \`capgo init\` in a capacitor project with npm, pnpm, bun or yarn`)
114
115
  p.outro(`Bye 👋`)
115
116
  process.exit()
116
117
  }
117
118
  // // use pm to install capgo
118
119
  // // run command pm install @capgo/capacitor-updater@latest
119
- const installCmd = findInstallCommand(pm)
120
120
  // check if capgo is already installed in package.json
121
121
  if (pack.dependencies['@capgo/capacitor-updater']) {
122
122
  s.stop(`Capgo already installed ✅`)
123
123
  }
124
124
  else {
125
- await execSync(`${pm} ${installCmd} @capgo/capacitor-updater@${versionToInstall}`, execOption as ExecSyncOptions)
125
+ await execSync(`${pm.installCommand} @capgo/capacitor-updater@${versionToInstall}`, execOption as ExecSyncOptions)
126
126
  s.stop(`Install Done ✅`)
127
127
  }
128
128
  }
129
129
  else {
130
- p.log.info(`Run yourself "npm i @capgo/capacitor-updater@latest"`)
130
+ p.log.info(`Run yourself "${pm.installCommand} @capgo/capacitor-updater@latest"`)
131
131
  }
132
132
  await markStep(userId, snag, 4)
133
133
  }
@@ -180,11 +180,12 @@ async function step5(userId: string, snag: LogSnag, apikey: string, appId: strin
180
180
  }
181
181
 
182
182
  async function step6(userId: string, snag: LogSnag, apikey: string, appId: string) {
183
+ const pm = getPMAndCommand()
183
184
  const doEncrypt = await p.confirm({ message: `Automatic configure end-to-end encryption in ${appId} updates?` })
184
185
  await cancelCommand(doEncrypt, userId, snag)
185
186
  if (doEncrypt) {
186
187
  const s = p.spinner()
187
- s.start(`Running: npx @capgo/cli@latest key create`)
188
+ s.start(`Running: ${pm.runner} @capgo/cli@latest key create`)
188
189
  const keyRes = await createKey({ force: true }, false)
189
190
  if (!keyRes) {
190
191
  s.stop('Error')
@@ -201,13 +202,14 @@ async function step6(userId: string, snag: LogSnag, apikey: string, appId: strin
201
202
  }
202
203
 
203
204
  async function step7(userId: string, snag: LogSnag, apikey: string, appId: string) {
204
- const doBuild = await p.confirm({ message: `Automatic build ${appId} with "npm run build" ?` })
205
+ const pm = getPMAndCommand()
206
+ const doBuild = await p.confirm({ message: `Automatic build ${appId} with "${pm.pm} run build" ?` })
205
207
  await cancelCommand(doBuild, userId, snag)
206
208
  if (doBuild) {
207
209
  const s = p.spinner()
208
210
  const projectType = await findProjectType()
209
211
  const buildCommand = await findBuildCommandForProjectType(projectType)
210
- s.start(`Running: npm run ${buildCommand} && npx cap sync`)
212
+ s.start(`Running: ${pm.pm} run ${buildCommand} && ${pm.runner} cap sync`)
211
213
  const pack = JSON.parse(readFileSync('package.json').toString())
212
214
  // check in script build exist
213
215
  if (!pack.scripts[buildCommand]) {
@@ -216,21 +218,22 @@ async function step7(userId: string, snag: LogSnag, apikey: string, appId: strin
216
218
  p.outro(`Bye 👋`)
217
219
  process.exit()
218
220
  }
219
- execSync(`npm run ${buildCommand} && npx cap sync`, execOption as ExecSyncOptions)
221
+ execSync(`${pm.pm} run ${buildCommand} && ${pm.runner} cap sync`, execOption as ExecSyncOptions)
220
222
  s.stop(`Build & Sync Done ✅`)
221
223
  }
222
224
  else {
223
- p.log.info(`Build yourself with command: npm run build && npx cap sync`)
225
+ p.log.info(`Build yourself with command: ${pm.pm} run build && ${pm.runner} cap sync`)
224
226
  }
225
227
  await markStep(userId, snag, 7)
226
228
  }
227
229
 
228
230
  async function step8(userId: string, snag: LogSnag, apikey: string, appId: string) {
231
+ const pm = getPMAndCommand()
229
232
  const doBundle = await p.confirm({ message: `Automatic upload ${appId} bundle to Capgo?` })
230
233
  await cancelCommand(doBundle, userId, snag)
231
234
  if (doBundle) {
232
235
  const s = p.spinner()
233
- s.start(`Running: npx @capgo/cli@latest bundle upload`)
236
+ s.start(`Running: ${pm.runner} @capgo/cli@latest bundle upload`)
234
237
  const uploadRes = await uploadBundle(appId, {
235
238
  channel: defaultChannel,
236
239
  apikey,
@@ -246,12 +249,13 @@ async function step8(userId: string, snag: LogSnag, apikey: string, appId: strin
246
249
  }
247
250
  }
248
251
  else {
249
- p.log.info(`Upload yourself with command: npx @capgo/cli@latest bundle upload`)
252
+ p.log.info(`Upload yourself with command: ${pm.runner} @capgo/cli@latest bundle upload`)
250
253
  }
251
254
  await markStep(userId, snag, 8)
252
255
  }
253
256
 
254
257
  async function step9(userId: string, snag: LogSnag) {
258
+ const pm = getPMAndCommand()
255
259
  const doRun = await p.confirm({ message: `Run in device now ?` })
256
260
  await cancelCommand(doRun, userId, snag)
257
261
  if (doRun) {
@@ -269,12 +273,12 @@ async function step9(userId: string, snag: LogSnag) {
269
273
 
270
274
  const platform = plaformType as 'ios' | 'android'
271
275
  const s = p.spinner()
272
- s.start(`Running: npx cap run ${platform}`)
273
- await spawnSync('npx', ['cap', 'run', platform], { stdio: 'inherit' })
276
+ s.start(`Running: ${pm.runner} cap run ${platform}`)
277
+ await spawnSync(pm.runner, ['cap', 'run', platform], { stdio: 'inherit' })
274
278
  s.stop(`Started Done ✅`)
275
279
  }
276
280
  else {
277
- p.log.info(`Run yourself with command: npx cap run <ios|android>`)
281
+ p.log.info(`Run yourself with command: ${pm.runner} cap run <ios|android>`)
278
282
  }
279
283
  await markStep(userId, snag, 9)
280
284
  }
@@ -294,21 +298,21 @@ async function step10(userId: string, snag: LogSnag, supabase: SupabaseClient<Da
294
298
  await markStep(userId, snag, 10)
295
299
  }
296
300
 
297
- export async function initApp(apikey: string, appId: string, options: SuperOptions) {
301
+ export async function initApp(apikeyCommand: string, appId: string, options: SuperOptions) {
302
+ const pm = getPMAndCommand()
298
303
  p.intro(`Capgo onboarding 🛫`)
299
304
  await checkLatest()
300
305
  const snag = useLogSnag()
301
306
  const config = await getConfig()
302
307
  appId = appId || config?.app?.appId
303
- apikey = apikey || findSavedKey()
308
+ const apikey = apikeyCommand || findSavedKey()
304
309
 
305
310
  const log = p.spinner()
306
- log.start('Running: npx @capgo/cli@latest login ***')
307
- const loginRes = await login(apikey, options, false)
308
- if (!loginRes)
309
- log.stop('Login already done ✅')
310
- else
311
+ if (!doLoginExists() || apikeyCommand) {
312
+ log.start(`Running: ${pm.runner} @capgo/cli@latest login ***`)
313
+ await login(apikey, options, false)
311
314
  log.stop('Login Done ✅')
315
+ }
312
316
 
313
317
  const supabase = await createSupabaseClient(apikey)
314
318
  const userId = await verifyUser(supabase, apikey, ['upload', 'all', 'read', 'write'])
@@ -327,7 +331,7 @@ export async function initApp(apikey: string, appId: string, options: SuperOptio
327
331
  await markStep(userId, snag, 0)
328
332
  p.log.info(`Welcome onboard ✈️!`)
329
333
  p.log.info(`Your Capgo update system is setup`)
330
- p.log.info(`Next time use \`npx @capgo/cli@latest bundle upload\` to only upload your bundle`)
334
+ p.log.info(`Next time use \`${pm.runner} @capgo/cli@latest bundle upload\` to only upload your bundle`)
331
335
  p.outro(`Bye 👋`)
332
336
  process.exit()
333
337
  }
package/src/login.ts CHANGED
@@ -9,6 +9,10 @@ import { checkLatest } from './api/update'
9
9
  interface Options {
10
10
  local: boolean
11
11
  }
12
+ export async function doLoginExists() {
13
+ const userHomeDir = homedir()
14
+ return existsSync(`${userHomeDir}/.capgo`) || existsSync('.capgo')
15
+ }
12
16
 
13
17
  export async function login(apikey: string, options: Options, shouldExit = true) {
14
18
  if (shouldExit)
package/src/utils.ts CHANGED
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import { resolve } from 'node:path'
4
4
  import process from 'node:process'
5
+ import type { Buffer } from 'node:buffer'
5
6
  import { loadConfig } from '@capacitor/cli/dist/config'
6
7
  import { program } from 'commander'
7
8
  import type { SupabaseClient } from '@supabase/supabase-js'
@@ -11,7 +12,8 @@ import { LogSnag } from 'logsnag'
11
12
  import * as p from '@clack/prompts'
12
13
  import ky from 'ky'
13
14
  import { promiseFiles } from 'node-dir'
14
- import { findInstallCommand, findPackageManagerType } from '@capgo/find-package-manager'
15
+ import type { InstallCommand, PackageManagerRunner, PackageManagerType } from '@capgo/find-package-manager'
16
+ import { findInstallCommand, findPackageManagerRunner, findPackageManagerType } from '@capgo/find-package-manager'
15
17
  import type { Database } from './types/supabase.types'
16
18
 
17
19
  export const baseKey = '.capgo_key'
@@ -39,7 +41,7 @@ export async function getConfig() {
39
41
  config = await loadConfig()
40
42
  }
41
43
  catch (err) {
42
- p.log.error('No capacitor config file found, run `cap init` first')
44
+ p.log.error(`No capacitor config file found, run \`cap init\` first ${formatError(err)}`)
43
45
  program.error('')
44
46
  }
45
47
  return config
@@ -306,7 +308,7 @@ export function findSavedKey(quiet = false) {
306
308
  key = readFileSync(keyPath, 'utf8').trim()
307
309
  }
308
310
  if (!key) {
309
- p.log.error(`Cannot find API key in local folder or global, please login first with npx @capacitor/cli login`)
311
+ p.log.error(`Cannot find API key in local folder or global, please login first with ${getPMAndCommand().runner} @capacitor/cli login`)
310
312
  program.error('')
311
313
  }
312
314
  return key
@@ -482,6 +484,7 @@ export async function uploadUrl(supabase: SupabaseClient<Database>, appId: strin
482
484
  const data = {
483
485
  app_id: appId,
484
486
  name,
487
+ version: 0,
485
488
  }
486
489
  try {
487
490
  const pathUploadLink = 'private/upload_link'
@@ -494,6 +497,67 @@ export async function uploadUrl(supabase: SupabaseClient<Database>, appId: strin
494
497
  return ''
495
498
  }
496
499
 
500
+ async function prepareMultipart(supabase: SupabaseClient<Database>, appId: string, name: string): Promise<{ key: string, uploadId: string, url: string } | null> {
501
+ const data = {
502
+ app_id: appId,
503
+ name,
504
+ version: 1,
505
+ }
506
+ try {
507
+ const pathUploadLink = 'private/upload_link'
508
+ const res = await supabase.functions.invoke(pathUploadLink, { body: JSON.stringify(data) })
509
+ return res.data as any
510
+ }
511
+ catch (error) {
512
+ p.log.error(`Cannot get upload url ${formatError(error)}`)
513
+ return null
514
+ }
515
+ }
516
+
517
+ async function finishMultipartDownload(key: string, uploadId: string, url: string, parts: any[]) {
518
+ const metadata = {
519
+ action: 'mpu-complete',
520
+ uploadId,
521
+ key,
522
+ }
523
+
524
+ await ky.post(url, {
525
+ json: {
526
+ parts,
527
+ },
528
+ searchParams: new URLSearchParams({ body: btoa(JSON.stringify(metadata)) }),
529
+ })
530
+
531
+ // console.log(await response.json())
532
+ }
533
+
534
+ const PART_SIZE = 10 * 1024 * 1024
535
+ export async function uploadMultipart(supabase: SupabaseClient<Database>, appId: string, name: string, data: Buffer): Promise<boolean> {
536
+ try {
537
+ const multipartPrep = await prepareMultipart(supabase, appId, name)
538
+ if (!multipartPrep) {
539
+ // Just pass the error
540
+ return false
541
+ }
542
+
543
+ const fileSize = data.length
544
+ const partCount = Math.ceil(fileSize / PART_SIZE)
545
+
546
+ const uploadPromises = Array.from({ length: partCount }, (_, index) =>
547
+ uploadPart(data, PART_SIZE, multipartPrep.url, multipartPrep.key, multipartPrep.uploadId, index))
548
+
549
+ const parts = await Promise.all(uploadPromises)
550
+
551
+ await finishMultipartDownload(multipartPrep.key, multipartPrep.uploadId, multipartPrep.url, parts)
552
+
553
+ return true
554
+ }
555
+ catch (e) {
556
+ p.log.error(`Could not upload via multipart ${formatError(e)}`)
557
+ return false
558
+ }
559
+ }
560
+
497
561
  export async function deletedFailedVersion(supabase: SupabaseClient<Database>, appId: string, name: string): Promise<void> {
498
562
  const data = {
499
563
  app_id: appId,
@@ -510,6 +574,34 @@ export async function deletedFailedVersion(supabase: SupabaseClient<Database>, a
510
574
  }
511
575
  }
512
576
 
577
+ async function uploadPart(
578
+ data: Buffer,
579
+ partsize: number,
580
+ url: string,
581
+ key: string,
582
+ uploadId: string,
583
+ index: number,
584
+ ) {
585
+ const dataToUpload = data.subarray(
586
+ partsize * index,
587
+ partsize * (index + 1),
588
+ )
589
+
590
+ const metadata = {
591
+ action: 'mpu-uploadpart',
592
+ uploadId,
593
+ partNumber: index + 1,
594
+ key,
595
+ }
596
+
597
+ const response = await ky.put(url, {
598
+ body: dataToUpload,
599
+ searchParams: new URLSearchParams({ body: btoa(JSON.stringify(metadata)) }),
600
+ })
601
+
602
+ return await response.json()
603
+ }
604
+
513
605
  export async function updateOrCreateChannel(supabase: SupabaseClient<Database>, update: Database['public']['Tables']['channels']['Insert']) {
514
606
  // console.log('updateOrCreateChannel', update)
515
607
  if (!update.app_id || !update.name || !update.created_by) {
@@ -625,6 +717,20 @@ export function getHumanDate(createdA: string | null) {
625
717
  return date.toLocaleString()
626
718
  }
627
719
 
720
+ let pmFetched = false
721
+ let pm: PackageManagerType = 'npm'
722
+ let pmCommand: InstallCommand = 'install'
723
+ let pmRunner: PackageManagerRunner = 'npx'
724
+ export function getPMAndCommand() {
725
+ if (pmFetched)
726
+ return { pm, command: pmCommand, installCommand: `${pm} ${pmCommand}`, runner: pmRunner }
727
+ pm = findPackageManagerType('.', 'npm')
728
+ pmCommand = findInstallCommand(pm)
729
+ pmFetched = true
730
+ pmRunner = findPackageManagerRunner()
731
+ return { pm, command: pmCommand, installCommand: `${pm} ${pmCommand}`, runner: pmRunner }
732
+ }
733
+
628
734
  export async function getLocalDepenencies() {
629
735
  if (!existsSync('./package.json')) {
630
736
  p.log.error('Missing package.json, you need to be in a capacitor project')