@tinycloud/cli 1.1.0-beta.7 → 1.1.0-beta.8
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 +42 -13
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +42 -13
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +2 -2
- package/skills/tc-cli/AUTH.md +1 -1
- package/skills/tc-cli/REFERENCE.md +15 -0
package/dist/legacy-entry.js
CHANGED
|
@@ -260,7 +260,7 @@ var init_formatter = __esm({
|
|
|
260
260
|
// src/output/errors.ts
|
|
261
261
|
import { readFileSync, readdirSync } from "fs";
|
|
262
262
|
import { join as join2 } from "path";
|
|
263
|
-
import { ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
263
|
+
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
264
264
|
function setActiveProfileName(name) {
|
|
265
265
|
activeProfileName = name;
|
|
266
266
|
}
|
|
@@ -286,6 +286,9 @@ function wrapError(error) {
|
|
|
286
286
|
{ hint: "Wait for the other command to finish and retry. A crashed process's lock is reclaimed automatically after 30 s." }
|
|
287
287
|
);
|
|
288
288
|
}
|
|
289
|
+
if (error instanceof ProfileDeletedError) {
|
|
290
|
+
return new CLIError("PROFILE_NOT_FOUND", message);
|
|
291
|
+
}
|
|
289
292
|
if (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired")) {
|
|
290
293
|
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
291
294
|
}
|
|
@@ -644,6 +647,8 @@ import { join as join3 } from "path";
|
|
|
644
647
|
import {
|
|
645
648
|
profilePath,
|
|
646
649
|
readSession,
|
|
650
|
+
recordProfileDeletion,
|
|
651
|
+
refuseWriteToDeletedProfile,
|
|
647
652
|
removeSession,
|
|
648
653
|
withProfileLock,
|
|
649
654
|
writeSession
|
|
@@ -758,10 +763,12 @@ var init_profiles = __esm({
|
|
|
758
763
|
* removed while holding the profile lock, so another writer's critical
|
|
759
764
|
* section never sees them vanish midway: session and key first, settings
|
|
760
765
|
* last, so a crash midway never leaves a session or key without its
|
|
761
|
-
* profile.
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
766
|
+
* profile. The deletion is then recorded, so a store write that waited for
|
|
767
|
+
* the lock meanwhile refuses rather than recreating a profile with only a
|
|
768
|
+
* session in it. `.lock` itself is left to the lock's release; the
|
|
769
|
+
* then-empty directory is removed afterwards unless another writer took
|
|
770
|
+
* the lock (or wrote) meanwhile. A profile directory that is a symlink is
|
|
771
|
+
* unlinked, its target left alone.
|
|
765
772
|
* Throws if the name is not one path segment or names the default profile.
|
|
766
773
|
*/
|
|
767
774
|
static async deleteProfile(name) {
|
|
@@ -782,11 +789,12 @@ var init_profiles = __esm({
|
|
|
782
789
|
`Cannot delete the default profile "${name}". Change the default first with \`tc profile default <other>\`.`
|
|
783
790
|
);
|
|
784
791
|
}
|
|
785
|
-
const
|
|
786
|
-
if (error.code === "ENOENT") return
|
|
792
|
+
const kind = await lstat(profileDir).then((stats) => stats.isSymbolicLink() ? "link" : "present", (error) => {
|
|
793
|
+
if (error.code === "ENOENT") return "missing";
|
|
787
794
|
throw error;
|
|
788
795
|
});
|
|
789
|
-
if (
|
|
796
|
+
if (kind === "missing") return;
|
|
797
|
+
if (kind === "link") {
|
|
790
798
|
await rm2(profileDir, { force: true });
|
|
791
799
|
return;
|
|
792
800
|
}
|
|
@@ -796,6 +804,7 @@ var init_profiles = __esm({
|
|
|
796
804
|
if (entry !== ".lock" && entry !== "profile.json") await rm2(join3(profileDir, entry), { recursive: true, force: true });
|
|
797
805
|
}
|
|
798
806
|
await rm2(join3(profileDir, "profile.json"), { force: true });
|
|
807
|
+
await recordProfileDeletion(name);
|
|
799
808
|
});
|
|
800
809
|
await rmdir(profileDir).catch((error) => {
|
|
801
810
|
if (error.code !== "ENOENT" && error.code !== "ENOTEMPTY" && error.code !== "EEXIST") throw error;
|
|
@@ -808,9 +817,14 @@ var init_profiles = __esm({
|
|
|
808
817
|
static async getKey(name) {
|
|
809
818
|
return readJson(join3(PROFILES_DIR, name, "key.json"));
|
|
810
819
|
}
|
|
811
|
-
/**
|
|
820
|
+
/**
|
|
821
|
+
* Saves a JWK key under the profile lock (0600, in an owner-only profile
|
|
822
|
+
* directory). Refused (PROFILE_NOT_FOUND) if the profile was deleted while
|
|
823
|
+
* this waited for the lock, rather than leaving a key-only profile.
|
|
824
|
+
*/
|
|
812
825
|
static async setKey(name, jwk) {
|
|
813
826
|
await _ProfileManager.withLock(name, async () => {
|
|
827
|
+
await refuseWriteToDeletedProfile(name);
|
|
814
828
|
await writeJson(join3(await _ProfileManager.ensureProfileDir(name), "key.json"), jwk);
|
|
815
829
|
});
|
|
816
830
|
}
|
|
@@ -1779,6 +1793,7 @@ import {
|
|
|
1779
1793
|
profileStoreMetadataPath,
|
|
1780
1794
|
readAdditionalDelegations,
|
|
1781
1795
|
readAuthRequests,
|
|
1796
|
+
refuseWriteToDeletedProfile as refuseWriteToDeletedProfile2,
|
|
1782
1797
|
updateProfileStore,
|
|
1783
1798
|
withProfileLock as withProfileLock2,
|
|
1784
1799
|
writeJsonAtomic
|
|
@@ -13146,6 +13161,7 @@ async function appendPermissionRequestArtifact(profile, artifact) {
|
|
|
13146
13161
|
});
|
|
13147
13162
|
}
|
|
13148
13163
|
async function writeSharedRecords(profile, store, entries) {
|
|
13164
|
+
await refuseWriteToDeletedProfile2(profile);
|
|
13149
13165
|
const path = store === "additional-delegations" ? additionalDelegationsPath(profile) : permissionRequestsPath(profile);
|
|
13150
13166
|
await writeJsonAtomic(path, entries);
|
|
13151
13167
|
await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });
|
|
@@ -13556,6 +13572,7 @@ async function createSDKInstance(ctx, options) {
|
|
|
13556
13572
|
});
|
|
13557
13573
|
return node;
|
|
13558
13574
|
}
|
|
13575
|
+
var BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
|
|
13559
13576
|
async function bootstrapDelegatedSession(ctx, delegation) {
|
|
13560
13577
|
const written = await ProfileManager.withLock(ctx.profile, async () => {
|
|
13561
13578
|
const previousProfile = await ProfileManager.getProfile(ctx.profile);
|
|
@@ -13605,13 +13622,13 @@ async function bootstrapDelegatedSession(ctx, delegation) {
|
|
|
13605
13622
|
throw error;
|
|
13606
13623
|
});
|
|
13607
13624
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
13608
|
-
if (
|
|
13625
|
+
if (profile === null || BOOTSTRAP_PROFILE_FIELDS.some((field) => profile[field] !== written.profile[field]) || JSON.stringify(session) !== JSON.stringify(written.session)) {
|
|
13609
13626
|
return `Profile "${ctx.profile}" changed while the import was pending (another login, logout or profile update), so its newer state was kept and the provisional session was not rolled back.`;
|
|
13610
13627
|
}
|
|
13611
13628
|
return restoreBeforeBootstrap(ctx.profile, written.previousProfile);
|
|
13612
13629
|
}, { timeoutMs: PROFILE_COMMIT_LOCK_TIMEOUT_MS }).catch((error) => (
|
|
13613
13630
|
// The lock or a read failed: the import's error stays the one reported.
|
|
13614
|
-
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${
|
|
13631
|
+
`Rolling back the provisional session of profile "${ctx.profile}" could not run (${failureName(error)}); check \`tc --profile ${ctx.profile} context\`.`
|
|
13615
13632
|
));
|
|
13616
13633
|
throw annotate(cause, note);
|
|
13617
13634
|
};
|
|
@@ -13627,15 +13644,27 @@ async function restoreBeforeBootstrap(profileName, previousProfile) {
|
|
|
13627
13644
|
const failures = [];
|
|
13628
13645
|
for (const write of [
|
|
13629
13646
|
() => ProfileManager.clearSession(profileName),
|
|
13630
|
-
() => ProfileManager.
|
|
13647
|
+
() => ProfileManager.updateProfile(profileName, (current) => {
|
|
13648
|
+
const restored = { ...current };
|
|
13649
|
+
for (const field of BOOTSTRAP_PROFILE_FIELDS) {
|
|
13650
|
+
if (previousProfile[field] === void 0) delete restored[field];
|
|
13651
|
+
else restored[field] = previousProfile[field];
|
|
13652
|
+
}
|
|
13653
|
+
return restored;
|
|
13654
|
+
})
|
|
13631
13655
|
]) {
|
|
13632
13656
|
await write().catch((error) => {
|
|
13633
|
-
failures.push(
|
|
13657
|
+
failures.push(failureName(error));
|
|
13634
13658
|
});
|
|
13635
13659
|
}
|
|
13636
13660
|
if (failures.length === 0) return void 0;
|
|
13637
13661
|
return `Rolling back the provisional session of profile "${profileName}" failed too (${failures.join("; ")}); check \`tc --profile ${profileName} context\`.`;
|
|
13638
13662
|
}
|
|
13663
|
+
function failureName(error) {
|
|
13664
|
+
if (error instanceof SyntaxError) return "a profile file is not valid JSON";
|
|
13665
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") return error.code;
|
|
13666
|
+
return error instanceof Error ? error.name : "unknown error";
|
|
13667
|
+
}
|
|
13639
13668
|
function annotate(error, note) {
|
|
13640
13669
|
if (note === void 0) return error;
|
|
13641
13670
|
const cause = wrapError(error);
|