Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/rt-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mattstack/rt-client",
"version": "0.30.0",
"version": "0.31.0",
"type": "module",
"exports": {
".": {
Expand Down
13 changes: 13 additions & 0 deletions packages/rt-client/src/settings/__tests__/write.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,19 @@ describe("settings/unset", () => {
expect(() => unsetSetting("rt.doesNotExist", "user")).toThrow(/unknown setting/);
});

test("removes a retired key a store still carries, from any scope", () => {
mkdirSync(dirname(machineSettingsPath()), { recursive: true });
writeFileSync(machineSettingsPath(), `{\n "mattstack.mode": "dev",\n "mattstack.appPath": "/Applications/mattstack-dev.app"\n}\n`);
expect(unsetSetting("mattstack.mode", "machine")).toBe(true);
const after = readFileSync(machineSettingsPath(), "utf8");
expect(after).not.toContain("mattstack.mode");
expect(after).toContain("mattstack.appPath");
});

test("refuses a retired key with a repo identity", () => {
expect(() => unsetSetting("mattstack.mode", "user", { repoIdentity: IDENTITY })).toThrow(/not repo-scoped/);
});

test("refuses a scope the def does not allow", () => {
expect(() => unsetSetting("rt.repoIdentityOverrides", "user")).toThrow(/cannot be unset in the user store/);
});
Expand Down
14 changes: 12 additions & 2 deletions packages/rt-client/src/settings/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ import { applyEdits, modify, parseTree, type JSONPath, type Node, type ParseErro
import { randomBytes } from "crypto";
import { dirname } from "path";
import { machineSettingsPath, teamSettingsPath, userSettingsPath } from "./paths.ts";
import { getDef, isMigrated, validateValue, type SettingDef, type SettingScope } from "./registry-machinery.ts";
import { getDef, isMigrated, isRetiredKey, validateValue, type SettingDef, type SettingScope } from "./registry-machinery.ts";
import { listTeams } from "./stores.ts";
import { isJoinedTeam } from "./team-local-read.ts";

Expand Down Expand Up @@ -171,9 +171,15 @@ export function setSetting(key: string, value: unknown, scope: SettingScope, opt
* with no local store included — nothing to remove is success, not an error),
* and a key not present in the store is a no-op. Returns whether anything was
* actually removed; the local-only reminder prints only on a real removal.
* A retired key is not in the registry but may linger in a store, so it can
* still be removed from any scope.
*/
export function unsetSetting(key: string, scope: SettingScope, opts: SetSettingOpts = {}): boolean {
const def = getDef(key);
if (!def && isRetiredKey(key)) {
if (opts.repoIdentity !== undefined) refuse(`"${key}" is not repo-scoped; omit the repo identity`);
return removeKeyFromScope(key, scope, opts, [key]);
}
if (!def) {
refuse(`unknown setting "${key}" — not in the settings registry (see \`rt settings list\`)`);
}
Expand All @@ -190,10 +196,14 @@ export function unsetSetting(key: string, scope: SettingScope, opts: SetSettingO
refuse(`"${key}" is not repo-scoped — omit the repo identity`);
}

const jsonPath: JSONPath = opts.repoIdentity !== undefined ? ["repos", opts.repoIdentity, key] : [key];
return removeKeyFromScope(key, scope, opts, jsonPath);
}

function removeKeyFromScope(key: string, scope: SettingScope, opts: SetSettingOpts, jsonPath: JSONPath): boolean {
const storePath = resolveStorePathForUnset(scope, opts);
if (storePath === null || !existsSync(storePath)) return false;

const jsonPath: JSONPath = opts.repoIdentity !== undefined ? ["repos", opts.repoIdentity, key] : [key];
const removed = removeFromStore(storePath, jsonPath);

if (removed) {
Expand Down
Loading