@scalebun/cli 1.0.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/lib/api/client.d.ts +8 -0
  2. package/lib/api/client.d.ts.map +1 -1
  3. package/lib/api/client.js +70 -2
  4. package/lib/api/client.js.map +1 -1
  5. package/lib/commands/ci-init.d.ts +0 -6
  6. package/lib/commands/ci-init.d.ts.map +1 -1
  7. package/lib/commands/ci-init.js +172 -28
  8. package/lib/commands/ci-init.js.map +1 -1
  9. package/lib/commands/doctor.d.ts +8 -0
  10. package/lib/commands/doctor.d.ts.map +1 -0
  11. package/lib/commands/doctor.js +252 -0
  12. package/lib/commands/doctor.js.map +1 -0
  13. package/lib/commands/login.d.ts +28 -4
  14. package/lib/commands/login.d.ts.map +1 -1
  15. package/lib/commands/login.js +132 -51
  16. package/lib/commands/login.js.map +1 -1
  17. package/lib/commands/ota-bundle.d.ts.map +1 -1
  18. package/lib/commands/ota-bundle.js +2 -0
  19. package/lib/commands/ota-bundle.js.map +1 -1
  20. package/lib/commands/ota-delete-bundle.d.ts +8 -0
  21. package/lib/commands/ota-delete-bundle.d.ts.map +1 -1
  22. package/lib/commands/ota-delete-bundle.js +60 -0
  23. package/lib/commands/ota-delete-bundle.js.map +1 -1
  24. package/lib/commands/ota-keys.d.ts +14 -0
  25. package/lib/commands/ota-keys.d.ts.map +1 -0
  26. package/lib/commands/ota-keys.js +200 -0
  27. package/lib/commands/ota-keys.js.map +1 -0
  28. package/lib/commands/ota-publish.d.ts +10 -0
  29. package/lib/commands/ota-publish.d.ts.map +1 -1
  30. package/lib/commands/ota-publish.js +251 -34
  31. package/lib/commands/ota-publish.js.map +1 -1
  32. package/lib/commands/ota-rollout.d.ts +3 -0
  33. package/lib/commands/ota-rollout.d.ts.map +1 -0
  34. package/lib/commands/ota-rollout.js +235 -0
  35. package/lib/commands/ota-rollout.js.map +1 -0
  36. package/lib/commands/quickstart.d.ts +4 -0
  37. package/lib/commands/quickstart.d.ts.map +1 -0
  38. package/lib/commands/quickstart.js +274 -0
  39. package/lib/commands/quickstart.js.map +1 -0
  40. package/lib/index.js +20 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/utils/bundle.d.ts +86 -0
  43. package/lib/utils/bundle.d.ts.map +1 -1
  44. package/lib/utils/bundle.js +244 -14
  45. package/lib/utils/bundle.js.map +1 -1
  46. package/lib/utils/credentials.d.ts +12 -1
  47. package/lib/utils/credentials.d.ts.map +1 -1
  48. package/lib/utils/credentials.js +40 -1
  49. package/lib/utils/credentials.js.map +1 -1
  50. package/lib/utils/transportSecurity.d.ts +36 -0
  51. package/lib/utils/transportSecurity.d.ts.map +1 -0
  52. package/lib/utils/transportSecurity.js +74 -0
  53. package/lib/utils/transportSecurity.js.map +1 -0
  54. package/lib/utils/ui.d.ts.map +1 -1
  55. package/lib/utils/ui.js +2 -0
  56. package/lib/utils/ui.js.map +1 -1
  57. package/package.json +1 -1
@@ -1,8 +1,50 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.registerOtaDeleteBundleCommand = registerOtaDeleteBundleCommand;
37
+ const readline = __importStar(require("readline"));
4
38
  const credentials_1 = require("../utils/credentials");
5
39
  const client_1 = require("../api/client");
40
+ /**
41
+ * `scalebun ota delete-bundle` — permanent, irreversible artifact deletion.
42
+ *
43
+ * It asks first. This used to delete on sight: one mis-pasted --bundle-id and
44
+ * the artifact was gone, with devices possibly still running it. `ota rollback`
45
+ * already had this shape (prompt on a TTY, explicit --yes off-TTY); a command
46
+ * whose own description says "permanently" needs it at least as much.
47
+ */
6
48
  function registerOtaDeleteBundleCommand(ota) {
7
49
  ota
8
50
  .command('delete-bundle')
@@ -10,12 +52,30 @@ function registerOtaDeleteBundleCommand(ota) {
10
52
  .description('Delete an OTA bundle permanently')
11
53
  .requiredOption('--app-id <id>', 'Scalebun app ID')
12
54
  .requiredOption('--bundle-id <id>', 'OTA bundle ID to delete')
55
+ .option('--yes', 'Skip the confirmation prompt (required in CI / non-interactive shells)')
13
56
  .action(async (opts) => {
14
57
  const creds = (0, credentials_1.readCredentials)();
15
58
  if (!creds) {
16
59
  console.error('❌ Not logged in. Run `scalebun login` first.');
17
60
  process.exit(1);
18
61
  }
62
+ if (!opts.yes) {
63
+ if (!process.stdin.isTTY) {
64
+ console.error('❌ Refusing to delete without confirmation in a non-interactive shell.\n' +
65
+ ' Re-run with --yes if this is intentional.');
66
+ process.exit(1);
67
+ }
68
+ console.log(`\n⚠️ This permanently deletes bundle ${opts.bundleId} from app ${opts.appId}.\n` +
69
+ ' Devices still running it keep their local copy, but it can never be\n' +
70
+ ' re-served, and any release pointing at it becomes undeliverable.\n');
71
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
72
+ const answer = await new Promise((resolve) => rl.question(' Type the bundle id to confirm: ', resolve));
73
+ rl.close();
74
+ if (answer.trim() !== opts.bundleId) {
75
+ console.log('Aborted — nothing was deleted.');
76
+ return;
77
+ }
78
+ }
19
79
  const client = new client_1.ApiClient(creds);
20
80
  try {
21
81
  console.log(`🗑️ Deleting bundle ${opts.bundleId} from App ${opts.appId}…`);
@@ -1 +1 @@
1
- {"version":3,"file":"ota-delete-bundle.js","sourceRoot":"","sources":["../../src/commands/ota-delete-bundle.ts"],"names":[],"mappings":";;AAIA,wEAwBC;AA3BD,sDAAuD;AACvD,0CAA0C;AAE1C,SAAgB,8BAA8B,CAAC,GAAY;IACzD,GAAG;SACA,OAAO,CAAC,eAAe,CAAC;SACxB,KAAK,CAAC,QAAQ,CAAC;SACf,WAAW,CAAC,kCAAkC,CAAC;SAC/C,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAClD,cAAc,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;SAC7D,MAAM,CAAC,KAAK,EAAE,IAAyC,EAAE,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YAC5E,MAAM,MAAM,CAAC,MAAM,CAAM,cAAc,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClF,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,wBAAwB,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"ota-delete-bundle.js","sourceRoot":"","sources":["../../src/commands/ota-delete-bundle.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,wEAiDC;AA9DD,mDAAqC;AAErC,sDAAuD;AACvD,0CAA0C;AAE1C;;;;;;;GAOG;AACH,SAAgB,8BAA8B,CAAC,GAAY;IACzD,GAAG;SACA,OAAO,CAAC,eAAe,CAAC;SACxB,KAAK,CAAC,QAAQ,CAAC;SACf,WAAW,CAAC,kCAAkC,CAAC;SAC/C,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAClD,cAAc,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;SAC7D,MAAM,CAAC,OAAO,EAAE,wEAAwE,CAAC;SACzF,MAAM,CAAC,KAAK,EAAE,IAAwD,EAAE,EAAE;QACzE,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CACX,yEAAyE;oBACvE,8CAA8C,CACjD,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,CAAC,GAAG,CACT,yCAAyC,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,KAAK,KAAK;gBAChF,0EAA0E;gBAC1E,uEAAuE,CAC1E,CAAC;YACF,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtF,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,CACnD,EAAE,CAAC,QAAQ,CAAC,oCAAoC,EAAE,OAAO,CAAC,CAC3D,CAAC;YACF,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YAC5E,MAAM,MAAM,CAAC,MAAM,CAAM,cAAc,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClF,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,wBAAwB,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Command } from 'commander';
2
+ import * as crypto from 'crypto';
3
+ /** Hex-encoded raw 32-byte ed25519 public key from a Node KeyObject. */
4
+ export declare function rawPublicKeyHex(publicKey: crypto.KeyObject): string;
5
+ export declare function registerOtaKeysCommand(ota: Command): void;
6
+ /**
7
+ * Sign a bundle hash with the locally-held private key.
8
+ *
9
+ * Returns null when no key is present — publishing without signing stays valid
10
+ * for apps that have not adopted it, and `ota publish` reports which happened
11
+ * rather than failing.
12
+ */
13
+ export declare function signBundleHash(sha256Hex: string, keyDir?: string): string | null;
14
+ //# sourceMappingURL=ota-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ota-keys.d.ts","sourceRoot":"","sources":["../../src/commands/ota-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AA+BjC,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,GAAG,MAAM,CAInE;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA0JzD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAcf"}
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.rawPublicKeyHex = rawPublicKeyHex;
37
+ exports.registerOtaKeysCommand = registerOtaKeysCommand;
38
+ exports.signBundleHash = signBundleHash;
39
+ const crypto = __importStar(require("crypto"));
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const credentials_1 = require("../utils/credentials");
43
+ const client_1 = require("../api/client");
44
+ /**
45
+ * Bundle signing keys (OTA-03).
46
+ *
47
+ * The security model, which is the reason this command exists at all: the
48
+ * private key is generated on the developer's machine and NEVER leaves it. Only
49
+ * the public half is uploaded. A server that cannot sign cannot forge a bundle,
50
+ * so a compromise of ScaleBun's infrastructure still cannot push code to a
51
+ * customer's users.
52
+ *
53
+ * `scalebun ota generate-key-pair` was referenced in the product documentation
54
+ * before it existed — the docs described verification the SDK never performed
55
+ * and a command the CLI never registered. This implements it.
56
+ */
57
+ const KEY_DIR = '.scalebun';
58
+ const PRIVATE_KEY_FILE = 'ota-signing-key.pem';
59
+ /** Hex-encoded raw 32-byte ed25519 public key from a Node KeyObject. */
60
+ function rawPublicKeyHex(publicKey) {
61
+ // SPKI for ed25519 is a fixed 12-byte header followed by the 32-byte key.
62
+ const spki = publicKey.export({ type: 'spki', format: 'der' });
63
+ return spki.subarray(spki.length - 32).toString('hex');
64
+ }
65
+ function registerOtaKeysCommand(ota) {
66
+ ota
67
+ .command('generate-key-pair')
68
+ .description('Generate an ed25519 signing keypair locally and register the public half')
69
+ .option('--app-id <id>', 'Register the public key against this app')
70
+ .option('--name <name>', 'Label for the key in the dashboard')
71
+ .option('--out <dir>', `Directory for the private key (default: ./${KEY_DIR})`)
72
+ .option('--force', 'Overwrite an existing private key')
73
+ .action(async (opts) => {
74
+ const outDir = path.resolve(opts.out ?? KEY_DIR);
75
+ const privatePath = path.join(outDir, PRIVATE_KEY_FILE);
76
+ if (fs.existsSync(privatePath) && !opts.force) {
77
+ console.error(`❌ A signing key already exists at ${privatePath}.\n` +
78
+ ` Overwriting it means every bundle signed with the old key can no longer be\n` +
79
+ ` verified by apps carrying the old public key. Pass --force if that is intended.`);
80
+ process.exit(1);
81
+ }
82
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
83
+ fs.mkdirSync(outDir, { recursive: true });
84
+ fs.writeFileSync(privatePath, privateKey.export({ type: 'pkcs8', format: 'pem' }),
85
+ // Owner read/write only. Irrelevant on Windows, correct everywhere else.
86
+ { mode: 0o600 });
87
+ const publicHex = rawPublicKeyHex(publicKey);
88
+ console.log(`\n🔑 ed25519 signing keypair generated.\n`);
89
+ console.log(` Private key : ${privatePath}`);
90
+ console.log(` NEVER commit this. Add ${KEY_DIR}/ to .gitignore.`);
91
+ console.log(` Public key : ${publicHex}\n`);
92
+ if (opts.appId) {
93
+ const creds = (0, credentials_1.readCredentials)();
94
+ if (!creds) {
95
+ console.error('❌ Not logged in. Run `scalebun login` first.');
96
+ console.error(' The keypair was still written; register it later with:');
97
+ console.error(` scalebun ota register-key --app-id <id> --public-key ${publicHex}`);
98
+ process.exit(1);
99
+ }
100
+ const client = new client_1.ApiClient(creds);
101
+ try {
102
+ await client.post(`/saas/apps/${opts.appId}/ota/signing-keys`, {
103
+ publicKey: publicHex,
104
+ name: opts.name ?? `CLI ${new Date().toISOString().slice(0, 10)}`,
105
+ });
106
+ console.log('✅ Public key registered with ScaleBun.\n');
107
+ }
108
+ catch (err) {
109
+ console.error(`❌ Failed to register public key: ${err.message}`);
110
+ console.error(` Retry with: scalebun ota register-key --app-id ${opts.appId} --public-key ${publicHex}\n`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+ else {
115
+ console.log('ℹ️ Pass --app-id to register the public key automatically.\n');
116
+ }
117
+ console.log('Next: embed the PUBLIC key in your app and enforce it at init:\n');
118
+ console.log(' ScaleBun.init({');
119
+ console.log(' // …');
120
+ console.log(' ota: {');
121
+ console.log(` publicSigningKey: '${publicHex}',`);
122
+ console.log(' verifySignature: myEd25519Verifier,');
123
+ console.log(' },');
124
+ console.log(' })\n');
125
+ });
126
+ ota
127
+ .command('register-key')
128
+ .description('Register an existing ed25519 public key with an app')
129
+ .requiredOption('--app-id <id>', 'Scalebun app ID')
130
+ .requiredOption('--public-key <hex>', 'ed25519 public key (hex or base64)')
131
+ .option('--name <name>', 'Label for the key in the dashboard')
132
+ .action(async (opts) => {
133
+ const creds = (0, credentials_1.readCredentials)();
134
+ if (!creds) {
135
+ console.error('❌ Not logged in. Run `scalebun login` first.');
136
+ process.exit(1);
137
+ }
138
+ const client = new client_1.ApiClient(creds);
139
+ try {
140
+ await client.post(`/saas/apps/${opts.appId}/ota/signing-keys`, {
141
+ publicKey: opts.publicKey,
142
+ name: opts.name ?? 'CLI',
143
+ });
144
+ console.log('✅ Public key registered.\n');
145
+ }
146
+ catch (err) {
147
+ console.error(`❌ Failed to register public key: ${err.message}`);
148
+ process.exit(1);
149
+ }
150
+ });
151
+ ota
152
+ .command('keys')
153
+ .description('List signing keys registered for an app')
154
+ .requiredOption('--app-id <id>', 'Scalebun app ID')
155
+ .action(async (opts) => {
156
+ const creds = (0, credentials_1.readCredentials)();
157
+ if (!creds) {
158
+ console.error('❌ Not logged in. Run `scalebun login` first.');
159
+ process.exit(1);
160
+ }
161
+ const client = new client_1.ApiClient(creds);
162
+ try {
163
+ const keys = await client.get(`/saas/apps/${opts.appId}/ota/signing-keys`);
164
+ console.log(`\n🔑 Signing keys for app ${opts.appId}:\n`);
165
+ if (!keys?.length) {
166
+ console.log(' (none — run `scalebun ota generate-key-pair --app-id <id>`)\n');
167
+ return;
168
+ }
169
+ for (const k of keys) {
170
+ const state = k.revokedAt ? 'REVOKED' : 'active';
171
+ console.log(` • ${(k.name ?? 'unnamed').padEnd(20)} ${state.padEnd(8)} ${k.publicKey.slice(0, 16)}…`);
172
+ }
173
+ console.log('');
174
+ }
175
+ catch (err) {
176
+ console.error(`❌ Failed to list keys: ${err.message}`);
177
+ process.exit(1);
178
+ }
179
+ });
180
+ }
181
+ /**
182
+ * Sign a bundle hash with the locally-held private key.
183
+ *
184
+ * Returns null when no key is present — publishing without signing stays valid
185
+ * for apps that have not adopted it, and `ota publish` reports which happened
186
+ * rather than failing.
187
+ */
188
+ function signBundleHash(sha256Hex, keyDir) {
189
+ const privatePath = path.join(path.resolve(keyDir ?? KEY_DIR), PRIVATE_KEY_FILE);
190
+ if (!fs.existsSync(privatePath))
191
+ return null;
192
+ const privateKey = crypto.createPrivateKey(fs.readFileSync(privatePath));
193
+ // The signature covers the bundle's SHA-256, which the device already has from
194
+ // the check response and re-computes from the downloaded bytes. Signing the
195
+ // hash rather than the bundle keeps the CLI from re-reading 20 MB, and gives
196
+ // the device one value to verify that transitively covers the whole artifact.
197
+ const signature = crypto.sign(null, Buffer.from(sha256Hex, 'hex'), privateKey);
198
+ return signature.toString('hex');
199
+ }
200
+ //# sourceMappingURL=ota-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ota-keys.js","sourceRoot":"","sources":["../../src/commands/ota-keys.ts"],"names":[],"mappings":"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"}
@@ -12,5 +12,15 @@ export type InstallMode = 'IMMEDIATE' | 'ON_NEXT_RESTART' | 'ON_NEXT_RESUME';
12
12
  * 6. POST /saas/apps/:appId/ota/bundles/:id/promote → configure installMode & rollout
13
13
  * 7. Print success
14
14
  */
15
+ /**
16
+ * Parse `--rollout`, refusing anything that isn't 0-100.
17
+ *
18
+ * This was `parseInt(x, 10) || 100`, which turned BOTH `--rollout 0` and any
19
+ * typo into a 100% rollout — the opposite of what the operator asked for. A
20
+ * dark release (0%) is a documented, legitimate way to promote a bundle without
21
+ * exposing it, so silently inverting it exposes every production device to an
22
+ * untested bundle. Exits rather than guessing.
23
+ */
24
+ export declare function parseRolloutPercent(raw: string | undefined): number;
15
25
  export declare function registerOtaPublishCommand(ota: Command): void;
16
26
  //# sourceMappingURL=ota-publish.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ota-publish.d.ts","sourceRoot":"","sources":["../../src/commands/ota-publish.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAwN5D"}
1
+ {"version":3,"file":"ota-publish.d.ts","sourceRoot":"","sources":["../../src/commands/ota-publish.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAWnE;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAuc5D"}