@tinycloud/cli 0.7.0-beta.3 → 0.7.0-beta.5
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 +222 -83
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -322,7 +322,8 @@ function wrapError(error) {
|
|
|
322
322
|
}
|
|
323
323
|
function handleError(error) {
|
|
324
324
|
const cliError = wrapError(error);
|
|
325
|
-
const
|
|
325
|
+
const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
|
|
326
|
+
const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
|
|
326
327
|
outputError(cliError.code, cliError.message, hint);
|
|
327
328
|
process.exit(cliError.exitCode);
|
|
328
329
|
}
|
|
@@ -1053,7 +1054,7 @@ import { spawn } from "child_process";
|
|
|
1053
1054
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1054
1055
|
import { dirname as dirname2 } from "path";
|
|
1055
1056
|
import { createInterface as createInterface2 } from "readline";
|
|
1056
|
-
import { principalDidEquals } from "@tinycloud/node-sdk";
|
|
1057
|
+
import { grantAuthRequest, principalDidEquals } from "@tinycloud/node-sdk";
|
|
1057
1058
|
|
|
1058
1059
|
// src/config/types.ts
|
|
1059
1060
|
var CLI_PROFILE_POSTURES = [
|
|
@@ -5740,6 +5741,31 @@ var KVService = class extends BaseService {
|
|
|
5740
5741
|
}
|
|
5741
5742
|
return void 0;
|
|
5742
5743
|
}
|
|
5744
|
+
/**
|
|
5745
|
+
* Classify a KV 404 by reading the response body once.
|
|
5746
|
+
*
|
|
5747
|
+
* The server returns 404 both for a genuinely missing key AND for an
|
|
5748
|
+
* un-hosted space (body "Space not found"). Previously get/head/delete
|
|
5749
|
+
* collapsed every 404 to KV_NOT_FOUND before reading the body, so an
|
|
5750
|
+
* un-hosted-space read was indistinguishable from a missing key. We now
|
|
5751
|
+
* preserve status + the "Space not found" body for the un-hosted case (so the
|
|
5752
|
+
* CLI/SDK can normalize it to SPACE_NOT_HOSTED, matching put/list/sql), and
|
|
5753
|
+
* fall through to KV_NOT_FOUND for a real missing key.
|
|
5754
|
+
*/
|
|
5755
|
+
async classifyNotFound(response, key) {
|
|
5756
|
+
const errorText = await response.text();
|
|
5757
|
+
if (/space not found/i.test(errorText)) {
|
|
5758
|
+
return err(
|
|
5759
|
+
serviceError(
|
|
5760
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
5761
|
+
`KV ${response.status} - ${errorText}`,
|
|
5762
|
+
"kv",
|
|
5763
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
5764
|
+
)
|
|
5765
|
+
);
|
|
5766
|
+
}
|
|
5767
|
+
return err(serviceError(ErrorCodes.KV_NOT_FOUND, `Key not found: ${key}`, "kv"));
|
|
5768
|
+
}
|
|
5743
5769
|
/**
|
|
5744
5770
|
* Get the full path with optional prefix.
|
|
5745
5771
|
*
|
|
@@ -5986,13 +6012,7 @@ var KVService = class extends BaseService {
|
|
|
5986
6012
|
}));
|
|
5987
6013
|
}
|
|
5988
6014
|
if (response.status === 404) {
|
|
5989
|
-
return
|
|
5990
|
-
serviceError(
|
|
5991
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
5992
|
-
`Key not found: ${key}`,
|
|
5993
|
-
"kv"
|
|
5994
|
-
)
|
|
5995
|
-
);
|
|
6015
|
+
return this.classifyNotFound(response, key);
|
|
5996
6016
|
}
|
|
5997
6017
|
const errorText = await response.text();
|
|
5998
6018
|
return err(
|
|
@@ -6253,13 +6273,7 @@ var KVService = class extends BaseService {
|
|
|
6253
6273
|
}));
|
|
6254
6274
|
}
|
|
6255
6275
|
if (response.status === 404) {
|
|
6256
|
-
return
|
|
6257
|
-
serviceError(
|
|
6258
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6259
|
-
`Key not found: ${key}`,
|
|
6260
|
-
"kv"
|
|
6261
|
-
)
|
|
6262
|
-
);
|
|
6276
|
+
return this.classifyNotFound(response, key);
|
|
6263
6277
|
}
|
|
6264
6278
|
const errorText = await response.text();
|
|
6265
6279
|
return err(
|
|
@@ -6304,13 +6318,7 @@ var KVService = class extends BaseService {
|
|
|
6304
6318
|
}));
|
|
6305
6319
|
}
|
|
6306
6320
|
if (response.status === 404) {
|
|
6307
|
-
return
|
|
6308
|
-
serviceError(
|
|
6309
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6310
|
-
`Key not found: ${key}`,
|
|
6311
|
-
"kv"
|
|
6312
|
-
)
|
|
6313
|
-
);
|
|
6321
|
+
return this.classifyNotFound(response, key);
|
|
6314
6322
|
}
|
|
6315
6323
|
const errorText = await response.text();
|
|
6316
6324
|
return err(
|
|
@@ -11488,21 +11496,8 @@ function registerAuthCommand(program2) {
|
|
|
11488
11496
|
expiryOption: parsed.requestedExpiry,
|
|
11489
11497
|
yes: options.yes === true
|
|
11490
11498
|
});
|
|
11491
|
-
const
|
|
11492
|
-
|
|
11493
|
-
parsed.requested,
|
|
11494
|
-
parsed.requestedExpiry !== void 0 ? { expiry: parsed.requestedExpiry } : void 0
|
|
11495
|
-
);
|
|
11496
|
-
outputJson({
|
|
11497
|
-
kind: "tinycloud.auth.delegation",
|
|
11498
|
-
version: 1,
|
|
11499
|
-
requestId: parsed.requestId,
|
|
11500
|
-
delegationCid: result.delegation.cid,
|
|
11501
|
-
delegation: result.delegation,
|
|
11502
|
-
permissions: parsed.requested,
|
|
11503
|
-
expiry: result.delegation.expiry.toISOString(),
|
|
11504
|
-
prompted: result.prompted
|
|
11505
|
-
});
|
|
11499
|
+
const grant = await grantAuthRequest(node, parsed);
|
|
11500
|
+
outputJson(grant);
|
|
11506
11501
|
} catch (error) {
|
|
11507
11502
|
handleError(error);
|
|
11508
11503
|
}
|
|
@@ -12178,6 +12173,79 @@ async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
|
12178
12173
|
// src/commands/kv.ts
|
|
12179
12174
|
import { readFile as readFile4 } from "fs/promises";
|
|
12180
12175
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
12176
|
+
|
|
12177
|
+
// src/lib/host.ts
|
|
12178
|
+
function canonicalizeAddress2(address) {
|
|
12179
|
+
const trimmed = address.trim();
|
|
12180
|
+
return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
|
|
12181
|
+
}
|
|
12182
|
+
async function resolveLocalAddress(profile, profileName) {
|
|
12183
|
+
const session = await ProfileManager.getSession(profileName);
|
|
12184
|
+
const sessAddr = session?.address;
|
|
12185
|
+
if (typeof sessAddr === "string" && sessAddr.length > 0) {
|
|
12186
|
+
return canonicalizeAddress2(sessAddr);
|
|
12187
|
+
}
|
|
12188
|
+
if (profile.address) return canonicalizeAddress2(profile.address);
|
|
12189
|
+
if (profile.ownerDid) {
|
|
12190
|
+
const match = profile.ownerDid.match(/^did:pkh:eip155:\d+:(0x[a-fA-F0-9]{40})$/);
|
|
12191
|
+
if (match) return canonicalizeAddress2(match[1]);
|
|
12192
|
+
}
|
|
12193
|
+
return null;
|
|
12194
|
+
}
|
|
12195
|
+
function ownerAddressFromSpaceUri(spaceUri) {
|
|
12196
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:\d+:(0x[a-fA-F0-9]{40}):/);
|
|
12197
|
+
return match ? canonicalizeAddress2(match[1]) : null;
|
|
12198
|
+
}
|
|
12199
|
+
function ownerDidFromSpaceUri(spaceUri) {
|
|
12200
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40}):/);
|
|
12201
|
+
if (!match) return null;
|
|
12202
|
+
return `did:pkh:eip155:${match[1]}:${canonicalizeAddress2(match[2])}`;
|
|
12203
|
+
}
|
|
12204
|
+
async function isRootAuthority(spaceUri, profileName) {
|
|
12205
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
12206
|
+
if (resolveProfilePosture(profile) === "delegate-session") return false;
|
|
12207
|
+
const ownerAddr = ownerAddressFromSpaceUri(spaceUri);
|
|
12208
|
+
if (!ownerAddr) return false;
|
|
12209
|
+
const selfAddr = await resolveLocalAddress(profile, profileName);
|
|
12210
|
+
return selfAddr !== null && selfAddr === ownerAddr;
|
|
12211
|
+
}
|
|
12212
|
+
function spaceNameFromUri(spaceUri) {
|
|
12213
|
+
return spaceUri.slice(spaceUri.lastIndexOf(":") + 1);
|
|
12214
|
+
}
|
|
12215
|
+
async function unhostedSpaceError(error, spaceUri, profileName) {
|
|
12216
|
+
if (!spaceUri) return null;
|
|
12217
|
+
const status = error.meta?.status;
|
|
12218
|
+
const isUnhosted = status === 404 && /space not found/i.test(error.message);
|
|
12219
|
+
if (!isUnhosted) return null;
|
|
12220
|
+
const spaceName = spaceNameFromUri(spaceUri);
|
|
12221
|
+
const owner = await isRootAuthority(spaceUri, profileName);
|
|
12222
|
+
const hint = owner ? [
|
|
12223
|
+
"You are the owner. Host it once:",
|
|
12224
|
+
` tc space host ${spaceName}`,
|
|
12225
|
+
"Then retry."
|
|
12226
|
+
].join("\n") : [
|
|
12227
|
+
"You are a delegate and CANNOT host this space \u2014 only its owner can.",
|
|
12228
|
+
"Emit a host request:",
|
|
12229
|
+
` tc space host-request ${spaceName} --emit ./host-request.json`,
|
|
12230
|
+
"Send it to the owner; they run `tc space host` and confirm. Then retry."
|
|
12231
|
+
].join("\n");
|
|
12232
|
+
const message = owner ? `Space '${spaceName}' (${spaceUri}) is not hosted.` : `Space '${spaceName}' (owner ${ownerDidFromSpaceUri(spaceUri) ?? spaceUri}) is not hosted.`;
|
|
12233
|
+
return new CLIError("SPACE_NOT_HOSTED", message, ExitCode.ERROR, { hint });
|
|
12234
|
+
}
|
|
12235
|
+
async function resolveHostSpace(name, profileName) {
|
|
12236
|
+
const resolved = await resolveSpaceUri(name, profileName);
|
|
12237
|
+
if (!resolved) {
|
|
12238
|
+
throw new Error(`Could not resolve a space for "${name}".`);
|
|
12239
|
+
}
|
|
12240
|
+
return resolved;
|
|
12241
|
+
}
|
|
12242
|
+
|
|
12243
|
+
// src/commands/kv.ts
|
|
12244
|
+
async function throwKvError(error, spaceUri, profileName) {
|
|
12245
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
12246
|
+
if (hosted) throw hosted;
|
|
12247
|
+
throw new CLIError(error.code, error.message, ExitCode.ERROR);
|
|
12248
|
+
}
|
|
12181
12249
|
async function readStdin2() {
|
|
12182
12250
|
const chunks = [];
|
|
12183
12251
|
for await (const chunk of process.stdin) {
|
|
@@ -12187,7 +12255,8 @@ async function readStdin2() {
|
|
|
12187
12255
|
}
|
|
12188
12256
|
async function kvHandle(node, spaceInput, profileName) {
|
|
12189
12257
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
12190
|
-
|
|
12258
|
+
const kv = spaceUri ? node.kvForSpace(spaceUri) : node.kv;
|
|
12259
|
+
return { kv, spaceUri };
|
|
12191
12260
|
}
|
|
12192
12261
|
function registerKvCommand(program2) {
|
|
12193
12262
|
const kv = program2.command("kv").description("Key-value store operations");
|
|
@@ -12196,17 +12265,19 @@ function registerKvCommand(program2) {
|
|
|
12196
12265
|
const globalOpts = cmd.optsWithGlobals();
|
|
12197
12266
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12198
12267
|
const node = await ensureAuthenticated(ctx);
|
|
12199
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12268
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12200
12269
|
const wantBytes = !!options.output || !!options.raw;
|
|
12201
12270
|
const result = await withSpinner(
|
|
12202
12271
|
`Getting ${key}...`,
|
|
12203
12272
|
() => kv2.get(key, wantBytes ? { binary: true } : void 0)
|
|
12204
12273
|
);
|
|
12205
12274
|
if (!result.ok) {
|
|
12275
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12276
|
+
if (hosted) throw hosted;
|
|
12206
12277
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12207
12278
|
throw new CLIError("NOT_FOUND", `Key "${key}" not found`, ExitCode.NOT_FOUND);
|
|
12208
12279
|
}
|
|
12209
|
-
|
|
12280
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12210
12281
|
}
|
|
12211
12282
|
const data = result.data.data;
|
|
12212
12283
|
const metadata = result.data.headers ?? {};
|
|
@@ -12257,10 +12328,10 @@ function registerKvCommand(program2) {
|
|
|
12257
12328
|
putValue = value;
|
|
12258
12329
|
}
|
|
12259
12330
|
}
|
|
12260
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12331
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12261
12332
|
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
|
|
12262
12333
|
if (!result.ok) {
|
|
12263
|
-
|
|
12334
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12264
12335
|
}
|
|
12265
12336
|
outputJson({ key, written: true });
|
|
12266
12337
|
} catch (error) {
|
|
@@ -12272,10 +12343,10 @@ function registerKvCommand(program2) {
|
|
|
12272
12343
|
const globalOpts = cmd.optsWithGlobals();
|
|
12273
12344
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12274
12345
|
const node = await ensureAuthenticated(ctx);
|
|
12275
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12346
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12276
12347
|
const result = await withSpinner(`Deleting ${key}...`, () => kv2.delete(key));
|
|
12277
12348
|
if (!result.ok) {
|
|
12278
|
-
|
|
12349
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12279
12350
|
}
|
|
12280
12351
|
outputJson({ key, deleted: true });
|
|
12281
12352
|
} catch (error) {
|
|
@@ -12287,11 +12358,11 @@ function registerKvCommand(program2) {
|
|
|
12287
12358
|
const globalOpts = cmd.optsWithGlobals();
|
|
12288
12359
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12289
12360
|
const node = await ensureAuthenticated(ctx);
|
|
12290
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12361
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12291
12362
|
const listOptions = options.prefix ? { prefix: options.prefix } : void 0;
|
|
12292
12363
|
const result = await withSpinner("Listing keys...", () => kv2.list(listOptions));
|
|
12293
12364
|
if (!result.ok) {
|
|
12294
|
-
|
|
12365
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12295
12366
|
}
|
|
12296
12367
|
const rawData = result.data.data ?? result.data;
|
|
12297
12368
|
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
@@ -12322,14 +12393,16 @@ function registerKvCommand(program2) {
|
|
|
12322
12393
|
const globalOpts = cmd.optsWithGlobals();
|
|
12323
12394
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12324
12395
|
const node = await ensureAuthenticated(ctx);
|
|
12325
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12396
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12326
12397
|
const result = await withSpinner(`Checking ${key}...`, () => kv2.head(key));
|
|
12327
12398
|
if (!result.ok) {
|
|
12399
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12400
|
+
if (hosted) throw hosted;
|
|
12328
12401
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12329
12402
|
outputJson({ key, exists: false, metadata: {} });
|
|
12330
12403
|
return;
|
|
12331
12404
|
}
|
|
12332
|
-
|
|
12405
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12333
12406
|
}
|
|
12334
12407
|
outputJson({
|
|
12335
12408
|
key,
|
|
@@ -12343,6 +12416,13 @@ function registerKvCommand(program2) {
|
|
|
12343
12416
|
}
|
|
12344
12417
|
|
|
12345
12418
|
// src/commands/space.ts
|
|
12419
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
12420
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
12421
|
+
import { dirname as dirname3 } from "path";
|
|
12422
|
+
function didWithoutFragment2(did) {
|
|
12423
|
+
const fragment = did.indexOf("#");
|
|
12424
|
+
return fragment === -1 ? did : did.slice(0, fragment);
|
|
12425
|
+
}
|
|
12346
12426
|
function registerSpaceCommand(program2) {
|
|
12347
12427
|
const space = program2.command("space").description("Space management");
|
|
12348
12428
|
space.command("list").description("List spaces").action(async (_options, cmd) => {
|
|
@@ -12383,6 +12463,53 @@ function registerSpaceCommand(program2) {
|
|
|
12383
12463
|
handleError(error);
|
|
12384
12464
|
}
|
|
12385
12465
|
});
|
|
12466
|
+
space.command("host-request <name>").description("Emit a request asking the space owner to host it (delegate-only)").option("--emit [file]", "Write the request artifact to file (or stdout when no path)").addHelpText("after", `
|
|
12467
|
+
|
|
12468
|
+
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
12469
|
+
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
12470
|
+
can hand it to the owner; they run \`tc space host <name>\` and confirm.
|
|
12471
|
+
|
|
12472
|
+
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
12473
|
+
it directly with \`tc space host <name>\` (no request needed).
|
|
12474
|
+
`).action(async (name, options, cmd) => {
|
|
12475
|
+
try {
|
|
12476
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
12477
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12478
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
12479
|
+
const spaceId = await resolveHostSpace(name, ctx.profile);
|
|
12480
|
+
const spaceName = spaceNameFromUri(spaceId);
|
|
12481
|
+
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
12482
|
+
throw new CLIError(
|
|
12483
|
+
"ALREADY_ROOT_AUTHORITY",
|
|
12484
|
+
`You are the owner of ${spaceId}. Host it directly: tc space host ${spaceName}`,
|
|
12485
|
+
ExitCode.USAGE_ERROR
|
|
12486
|
+
);
|
|
12487
|
+
}
|
|
12488
|
+
const requesterDid = didWithoutFragment2(profile.sessionDid ?? profile.did);
|
|
12489
|
+
const ownerDid = ownerDidFromSpaceUri(spaceId);
|
|
12490
|
+
if (!ownerDid) {
|
|
12491
|
+
throw new CLIError(
|
|
12492
|
+
"UNRESOLVABLE_OWNER",
|
|
12493
|
+
`Cannot determine the owner of ${spaceId}; host-request needs a pkh space URI.`,
|
|
12494
|
+
ExitCode.USAGE_ERROR
|
|
12495
|
+
);
|
|
12496
|
+
}
|
|
12497
|
+
const artifact = {
|
|
12498
|
+
kind: "tinycloud.host.request",
|
|
12499
|
+
version: 1,
|
|
12500
|
+
requestId: `hostreq_${Date.now().toString(36)}_${randomBytes3(4).toString("hex")}`,
|
|
12501
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12502
|
+
spaceName,
|
|
12503
|
+
spaceId,
|
|
12504
|
+
ownerDid,
|
|
12505
|
+
requesterDid,
|
|
12506
|
+
host: ctx.host
|
|
12507
|
+
};
|
|
12508
|
+
await emitHostRequestArtifact(artifact, options.emit);
|
|
12509
|
+
} catch (error) {
|
|
12510
|
+
handleError(error);
|
|
12511
|
+
}
|
|
12512
|
+
});
|
|
12386
12513
|
space.command("info [space-id]").description("Get space info").action(async (spaceId, _options, cmd) => {
|
|
12387
12514
|
try {
|
|
12388
12515
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -12415,6 +12542,22 @@ function registerSpaceCommand(program2) {
|
|
|
12415
12542
|
}
|
|
12416
12543
|
});
|
|
12417
12544
|
}
|
|
12545
|
+
async function emitHostRequestArtifact(artifact, emitOption) {
|
|
12546
|
+
if (typeof emitOption === "string" && emitOption.length > 0) {
|
|
12547
|
+
await mkdir3(dirname3(emitOption), { recursive: true });
|
|
12548
|
+
await writeFile4(emitOption, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
12549
|
+
outputJson({
|
|
12550
|
+
emitted: true,
|
|
12551
|
+
path: emitOption,
|
|
12552
|
+
requestId: artifact.requestId,
|
|
12553
|
+
spaceName: artifact.spaceName,
|
|
12554
|
+
spaceId: artifact.spaceId,
|
|
12555
|
+
ownerDid: artifact.ownerDid
|
|
12556
|
+
});
|
|
12557
|
+
return;
|
|
12558
|
+
}
|
|
12559
|
+
outputJson(artifact);
|
|
12560
|
+
}
|
|
12418
12561
|
|
|
12419
12562
|
// src/lib/duration.ts
|
|
12420
12563
|
function parseDuration(input) {
|
|
@@ -13062,7 +13205,7 @@ complete -c tc -l quiet -s q -d "Suppress non-essential output"
|
|
|
13062
13205
|
|
|
13063
13206
|
// src/commands/vault.ts
|
|
13064
13207
|
import { readFile as readFile5 } from "fs/promises";
|
|
13065
|
-
import { writeFile as
|
|
13208
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
13066
13209
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
13067
13210
|
async function readStdin3() {
|
|
13068
13211
|
const chunks = [];
|
|
@@ -13151,7 +13294,7 @@ function registerVaultCommand(program2) {
|
|
|
13151
13294
|
const data = result.data.data ?? result.data;
|
|
13152
13295
|
if (options.output) {
|
|
13153
13296
|
const content = data instanceof Uint8Array ? Buffer.from(data) : typeof data === "string" ? data : JSON.stringify(data);
|
|
13154
|
-
await
|
|
13297
|
+
await writeFile5(options.output, content);
|
|
13155
13298
|
outputJson({ key, written: options.output });
|
|
13156
13299
|
return;
|
|
13157
13300
|
}
|
|
@@ -13235,7 +13378,7 @@ function registerVaultCommand(program2) {
|
|
|
13235
13378
|
|
|
13236
13379
|
// src/commands/secrets.ts
|
|
13237
13380
|
import { readFile as readFile6 } from "fs/promises";
|
|
13238
|
-
import { writeFile as
|
|
13381
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
13239
13382
|
import { join as join5 } from "path";
|
|
13240
13383
|
import { homedir as homedir2 } from "os";
|
|
13241
13384
|
var SECRETS_SPACE2 = "secrets";
|
|
@@ -13826,7 +13969,7 @@ function registerSecretsCommand(program2) {
|
|
|
13826
13969
|
})
|
|
13827
13970
|
);
|
|
13828
13971
|
if (options.output) {
|
|
13829
|
-
await
|
|
13972
|
+
await writeFile6(options.output, value2);
|
|
13830
13973
|
outputJson({ name, written: options.output });
|
|
13831
13974
|
return;
|
|
13832
13975
|
}
|
|
@@ -13855,7 +13998,7 @@ function registerSecretsCommand(program2) {
|
|
|
13855
13998
|
}
|
|
13856
13999
|
const value = String(result.data);
|
|
13857
14000
|
if (options.output) {
|
|
13858
|
-
await
|
|
14001
|
+
await writeFile6(options.output, value);
|
|
13859
14002
|
outputJson({ name, written: options.output });
|
|
13860
14003
|
return;
|
|
13861
14004
|
}
|
|
@@ -13973,7 +14116,7 @@ function registerSecretsCommand(program2) {
|
|
|
13973
14116
|
|
|
13974
14117
|
// src/commands/vars.ts
|
|
13975
14118
|
import { readFile as readFile7 } from "fs/promises";
|
|
13976
|
-
import { writeFile as
|
|
14119
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
13977
14120
|
var VARIABLES_PREFIX = "variables/";
|
|
13978
14121
|
async function readStdin5() {
|
|
13979
14122
|
const chunks = [];
|
|
@@ -14045,7 +14188,7 @@ function registerVarsCommand(program2) {
|
|
|
14045
14188
|
value = typeof data === "string" ? data : JSON.stringify(data);
|
|
14046
14189
|
}
|
|
14047
14190
|
if (options.output) {
|
|
14048
|
-
await
|
|
14191
|
+
await writeFile7(options.output, value);
|
|
14049
14192
|
outputJson({ name, written: options.output });
|
|
14050
14193
|
return;
|
|
14051
14194
|
}
|
|
@@ -14219,12 +14362,18 @@ function registerDoctorCommand(program2) {
|
|
|
14219
14362
|
}
|
|
14220
14363
|
|
|
14221
14364
|
// src/commands/sql.ts
|
|
14222
|
-
import { writeFile as
|
|
14365
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
14223
14366
|
import { resolve } from "path";
|
|
14224
14367
|
async function dbHandle(node, dbName, spaceInput, profileName) {
|
|
14225
14368
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
14226
14369
|
const sql = spaceUri ? node.sqlForSpace(spaceUri) : node.sql;
|
|
14227
|
-
return sql.db(dbName);
|
|
14370
|
+
return { handle: sql.db(dbName), spaceUri };
|
|
14371
|
+
}
|
|
14372
|
+
async function throwSqlError(error, spaceUri, profileName, prefix) {
|
|
14373
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
14374
|
+
if (hosted) throw hosted;
|
|
14375
|
+
const message = prefix ? `${prefix}${error.message}` : error.message;
|
|
14376
|
+
throw new CLIError(error.code, message, ExitCode.ERROR, error.meta);
|
|
14228
14377
|
}
|
|
14229
14378
|
function registerSqlCommand(program2) {
|
|
14230
14379
|
const sql = program2.command("sql").description("SQLite database operations for your TinyCloud space").addHelpText("after", `
|
|
@@ -14270,13 +14419,13 @@ Output:
|
|
|
14270
14419
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14271
14420
|
const node = await ensureAuthenticated(ctx);
|
|
14272
14421
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14273
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14422
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14274
14423
|
const result = await withSpinner(
|
|
14275
14424
|
"Running query...",
|
|
14276
14425
|
() => handle.query(sqlStr, params)
|
|
14277
14426
|
);
|
|
14278
14427
|
if (!result.ok) {
|
|
14279
|
-
|
|
14428
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14280
14429
|
}
|
|
14281
14430
|
const { columns, rows, rowCount } = result.data;
|
|
14282
14431
|
if (shouldOutputJson()) {
|
|
@@ -14314,13 +14463,13 @@ Output:
|
|
|
14314
14463
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14315
14464
|
const node = await ensureAuthenticated(ctx);
|
|
14316
14465
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14317
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14466
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14318
14467
|
const result = await withSpinner(
|
|
14319
14468
|
"Executing statement...",
|
|
14320
14469
|
() => handle.execute(sqlStr, params)
|
|
14321
14470
|
);
|
|
14322
14471
|
if (!result.ok) {
|
|
14323
|
-
|
|
14472
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14324
14473
|
}
|
|
14325
14474
|
outputJson({
|
|
14326
14475
|
changes: result.data.changes,
|
|
@@ -14344,18 +14493,18 @@ Output:
|
|
|
14344
14493
|
const globalOpts = cmd.optsWithGlobals();
|
|
14345
14494
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14346
14495
|
const node = await ensureAuthenticated(ctx);
|
|
14347
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14496
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14348
14497
|
const result = await withSpinner(
|
|
14349
14498
|
"Exporting database...",
|
|
14350
14499
|
() => handle.export()
|
|
14351
14500
|
);
|
|
14352
14501
|
if (!result.ok) {
|
|
14353
|
-
|
|
14502
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14354
14503
|
}
|
|
14355
14504
|
const blob = result.data;
|
|
14356
14505
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14357
14506
|
const outputPath = resolve(options.output);
|
|
14358
|
-
await
|
|
14507
|
+
await writeFile8(outputPath, buffer);
|
|
14359
14508
|
outputJson({
|
|
14360
14509
|
file: outputPath,
|
|
14361
14510
|
size: blob.size,
|
|
@@ -14399,8 +14548,8 @@ Notes:
|
|
|
14399
14548
|
ExitCode.USAGE_ERROR
|
|
14400
14549
|
);
|
|
14401
14550
|
}
|
|
14402
|
-
const fromHandle = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14403
|
-
const toHandle = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14551
|
+
const { handle: fromHandle, spaceUri: fromSpaceUriResolved } = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14552
|
+
const { handle: toHandle, spaceUri: toSpaceUriResolved } = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14404
14553
|
let tables;
|
|
14405
14554
|
if (options.table && options.table.length > 0) {
|
|
14406
14555
|
tables = options.table.flatMap((t) => t.split(",").map((s) => s.trim()).filter(Boolean));
|
|
@@ -14409,7 +14558,7 @@ Notes:
|
|
|
14409
14558
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
|
14410
14559
|
);
|
|
14411
14560
|
if (!listing.ok) {
|
|
14412
|
-
|
|
14561
|
+
await throwSqlError(listing.error, fromSpaceUriResolved, ctx.profile, "Cannot list source tables: ");
|
|
14413
14562
|
}
|
|
14414
14563
|
tables = listing.data.rows.map((r) => String(r[0]));
|
|
14415
14564
|
}
|
|
@@ -14425,12 +14574,7 @@ Notes:
|
|
|
14425
14574
|
const safe = quoteIdent(table);
|
|
14426
14575
|
const countResult = await fromHandle.query(`SELECT count(*) AS n FROM ${safe}`);
|
|
14427
14576
|
if (!countResult.ok) {
|
|
14428
|
-
|
|
14429
|
-
countResult.error.code,
|
|
14430
|
-
`Cannot count rows in source table "${table}": ${countResult.error.message}`,
|
|
14431
|
-
ExitCode.ERROR,
|
|
14432
|
-
countResult.error.meta
|
|
14433
|
-
);
|
|
14577
|
+
await throwSqlError(countResult.error, fromSpaceUriResolved, ctx.profile, `Cannot count rows in source table "${table}": `);
|
|
14434
14578
|
}
|
|
14435
14579
|
const rows = Number(countResult.data.rows[0]?.[0] ?? 0);
|
|
14436
14580
|
plan.push({ table, rows, copied: 0, skipped: 0 });
|
|
@@ -14448,7 +14592,7 @@ Notes:
|
|
|
14448
14592
|
const safe = quoteIdent(entry.table);
|
|
14449
14593
|
const fetched = await fromHandle.query(`SELECT * FROM ${safe}`);
|
|
14450
14594
|
if (!fetched.ok) {
|
|
14451
|
-
|
|
14595
|
+
await throwSqlError(fetched.error, fromSpaceUriResolved, ctx.profile, `Failed to read "${entry.table}": `);
|
|
14452
14596
|
}
|
|
14453
14597
|
const columns = fetched.data.columns;
|
|
14454
14598
|
const rows = fetched.data.rows;
|
|
@@ -14459,12 +14603,7 @@ Notes:
|
|
|
14459
14603
|
for (const row of rows) {
|
|
14460
14604
|
const writeResult = await toHandle.execute(insertSql, row);
|
|
14461
14605
|
if (!writeResult.ok) {
|
|
14462
|
-
|
|
14463
|
-
writeResult.error.code,
|
|
14464
|
-
`Insert into "${entry.table}" failed after ${entry.copied} row(s): ${writeResult.error.message}`,
|
|
14465
|
-
ExitCode.ERROR,
|
|
14466
|
-
writeResult.error.meta
|
|
14467
|
-
);
|
|
14606
|
+
await throwSqlError(writeResult.error, toSpaceUriResolved, ctx.profile, `Insert into "${entry.table}" failed after ${entry.copied} row(s): `);
|
|
14468
14607
|
}
|
|
14469
14608
|
entry.copied += writeResult.data.changes ?? 1;
|
|
14470
14609
|
}
|
|
@@ -14484,7 +14623,7 @@ function quoteIdent(name) {
|
|
|
14484
14623
|
}
|
|
14485
14624
|
|
|
14486
14625
|
// src/commands/duckdb.ts
|
|
14487
|
-
import { readFile as readFile8, writeFile as
|
|
14626
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
14488
14627
|
import { resolve as resolve2 } from "path";
|
|
14489
14628
|
function registerDuckdbCommand(program2) {
|
|
14490
14629
|
const duckdb = program2.command("duckdb").description("DuckDB database operations");
|
|
@@ -14598,7 +14737,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
14598
14737
|
const blob = result.data;
|
|
14599
14738
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14600
14739
|
const outputPath = resolve2(options.output);
|
|
14601
|
-
await
|
|
14740
|
+
await writeFile9(outputPath, buffer);
|
|
14602
14741
|
outputJson({
|
|
14603
14742
|
file: outputPath,
|
|
14604
14743
|
size: blob.size,
|