primitive-admin 1.2.0-alpha.7 → 1.2.0-alpha.9
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/README.md +8 -2
- package/assets/skill/skills/primitive-platform/SKILL.md +22 -8
- package/dist/src/commands/apps-children.d.ts +1 -1
- package/dist/src/commands/apps-children.js +55 -145
- package/dist/src/commands/apps-children.js.map +1 -1
- package/dist/src/commands/auth.js +107 -33
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/database-types.js +13 -313
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.js +13 -1
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +70 -11
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/llm.js +9 -6
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/settings.d.ts +12 -9
- package/dist/src/commands/settings.js +31 -79
- package/dist/src/commands/settings.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +12 -2
- package/dist/src/commands/sync-app-settings.js +35 -10
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +1 -8
- package/dist/src/commands/sync.js +47 -45
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +46 -6
- package/dist/src/lib/api-client.js +156 -64
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +38 -0
- package/dist/src/lib/app-settings-descriptor.js +96 -4
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/child-apps-local.d.ts +0 -46
- package/dist/src/lib/child-apps-local.js +2 -72
- package/dist/src/lib/child-apps-local.js.map +1 -1
- package/dist/src/lib/credentials-home.d.ts +51 -0
- package/dist/src/lib/credentials-home.js +169 -0
- package/dist/src/lib/credentials-home.js.map +1 -0
- package/dist/src/lib/credentials-lock.d.ts +80 -0
- package/dist/src/lib/credentials-lock.js +222 -0
- package/dist/src/lib/credentials-lock.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +56 -4
- package/dist/src/lib/credentials-store.js +236 -78
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +1 -1
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/function-bundle.d.ts +38 -18
- package/dist/src/lib/function-bundle.js +60 -35
- package/dist/src/lib/function-bundle.js.map +1 -1
- package/dist/src/lib/function-document-types.d.ts +5 -3
- package/dist/src/lib/function-document-types.js +10 -14
- package/dist/src/lib/function-document-types.js.map +1 -1
- package/dist/src/lib/function-source-roots.d.ts +67 -0
- package/dist/src/lib/function-source-roots.js +148 -0
- package/dist/src/lib/function-source-roots.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +27 -0
- package/dist/src/lib/function-sync.js +16 -3
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-triggers.d.ts +15 -3
- package/dist/src/lib/function-triggers.js +72 -10
- package/dist/src/lib/function-triggers.js.map +1 -1
- package/dist/src/lib/function-typecheck.d.ts +37 -2
- package/dist/src/lib/function-typecheck.js +210 -28
- package/dist/src/lib/function-typecheck.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +48 -11
- package/dist/src/lib/generated-config-surfaces.js +178 -49
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
- package/dist/src/lib/generated-workflow-model-fields.js +53 -0
- package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
- package/dist/src/lib/login-scope-lines.d.ts +1 -1
- package/dist/src/lib/login-scope-lines.js +1 -1
- package/dist/src/lib/login-scope-lines.js.map +1 -1
- package/dist/src/lib/project-config.d.ts +7 -0
- package/dist/src/lib/project-config.js +22 -1
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
- package/dist/src/lib/prompt-schema-codegen.js +52 -14
- package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
- package/dist/src/lib/root-import-decision.d.ts +9 -4
- package/dist/src/lib/root-import-decision.js +24 -3
- package/dist/src/lib/root-import-decision.js.map +1 -1
- package/dist/src/lib/scope-request.d.ts +8 -5
- package/dist/src/lib/scope-request.js +17 -8
- package/dist/src/lib/scope-request.js.map +1 -1
- package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
- package/dist/src/lib/swift-codegen/agentGenerator.js +56 -4
- package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
- package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
- package/dist/src/lib/workflow-field-descriptor.js +153 -0
- package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cross-process lock and atomic replace every project-mode credentials
|
|
3
|
+
* write goes through (#4492).
|
|
4
|
+
*
|
|
5
|
+
* Several CLI processes write one credentials file — one agent per git
|
|
6
|
+
* worktree, all sharing the main worktree's login — so a write is a locked
|
|
7
|
+
* read-modify-write, and the file is replaced by a rename, never rewritten in
|
|
8
|
+
* place: a reader never sees a partial file.
|
|
9
|
+
*
|
|
10
|
+
* The lock is `credentials.json.lock`, created exclusively and holding its
|
|
11
|
+
* owner's token (`<pid>:<random>`). A lock older than {@link LOCK_STALE_MS}
|
|
12
|
+
* was left by a crashed process; a contender reclaims it by renaming it to a
|
|
13
|
+
* name of its own, which exactly one contender can do, and removes it only if
|
|
14
|
+
* it still holds the token it read — otherwise a live lock was moved, and it
|
|
15
|
+
* is put back unless another writer took the lock meanwhile. A holder proves,
|
|
16
|
+
* immediately before it commits, that it still owns the lock and that the file
|
|
17
|
+
* is still the version it read — a holder whose lock was moved away and put
|
|
18
|
+
* back may have missed another writer's commit in between — and releases only
|
|
19
|
+
* its own lock.
|
|
20
|
+
*
|
|
21
|
+
* Node has no advisory file lock, so one window remains: between a holder's
|
|
22
|
+
* checks and its rename, a contender that just misreclaimed that lock could
|
|
23
|
+
* let a third writer in. Every other interleaving ends in a failed write,
|
|
24
|
+
* never a lost update.
|
|
25
|
+
*/
|
|
26
|
+
/** A lock untouched for this long was left by a crashed process. */
|
|
27
|
+
export declare const LOCK_STALE_MS = 10000;
|
|
28
|
+
/** How long a writer waits for a held lock before it fails. */
|
|
29
|
+
export declare const LOCK_WAIT_MS = 5000;
|
|
30
|
+
/** The lock on a credentials directory, held by this process. */
|
|
31
|
+
export interface CredentialsLock {
|
|
32
|
+
/** The lock file. */
|
|
33
|
+
readonly path: string;
|
|
34
|
+
/** This holder's token, the lock file's content while it holds it. */
|
|
35
|
+
readonly token: string;
|
|
36
|
+
/** Whether the lock file still holds this holder's token. */
|
|
37
|
+
ownsLock(): boolean;
|
|
38
|
+
/** Remove the lock if, and only if, this holder still owns it. Never throws. */
|
|
39
|
+
release(): void;
|
|
40
|
+
}
|
|
41
|
+
/** A write that could not take, or lost, the lock on a credentials file. */
|
|
42
|
+
export declare class CredentialsLockedError extends Error {
|
|
43
|
+
constructor(lockPath: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Reclaim the stale lock at `lockPath`, whose content was `seen` when it was
|
|
47
|
+
* judged stale: rename it to a name of `token`'s own, then remove it if it
|
|
48
|
+
* still holds `seen`, or put it back if it does not (a live writer took the
|
|
49
|
+
* lock in between). Gone already is not an error: another contender won.
|
|
50
|
+
*/
|
|
51
|
+
export declare function reclaimStaleLock(lockPath: string, token: string, seen: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Put a live lock that a reclaimer moved to `moved` back at `lockPath` — but
|
|
54
|
+
* only if no other writer has taken the lock since, which a rename would
|
|
55
|
+
* silently replace. When one has, the displaced holder has lost the lock and
|
|
56
|
+
* fails its ownership check. The caller removes `moved` either way.
|
|
57
|
+
*/
|
|
58
|
+
export declare function restoreDisplacedLock(moved: string, lockPath: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* The version of the file at `path` as a writer read it: what an atomic
|
|
61
|
+
* replace changes (the inode) and what an in-place write changes (size and
|
|
62
|
+
* times). `"absent"` when there is no file.
|
|
63
|
+
*/
|
|
64
|
+
export declare function fileVersion(path: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Take the lock on the credentials file in `dir`, waiting up to
|
|
67
|
+
* {@link LOCK_WAIT_MS} for another writer and reclaiming a stale lock. Throws
|
|
68
|
+
* {@link CredentialsLockedError} naming the lock when it stays held.
|
|
69
|
+
*/
|
|
70
|
+
export declare function acquireCredentialsLock(dir: string): CredentialsLock;
|
|
71
|
+
/**
|
|
72
|
+
* Replace `path` with `content` while `lock` is held: write a private
|
|
73
|
+
* temporary file beside it, check the lock is still this holder's and, when
|
|
74
|
+
* `readVersion` is given, that `path` is still the {@link fileVersion} the
|
|
75
|
+
* holder read under the lock, then rename the temporary file over `path`. A
|
|
76
|
+
* holder that lost the lock, or whose read another writer's commit has
|
|
77
|
+
* overtaken, removes its temporary file and throws
|
|
78
|
+
* {@link CredentialsLockedError}, leaving `path` as it was.
|
|
79
|
+
*/
|
|
80
|
+
export declare function replaceFileUnderLock(lock: CredentialsLock, path: string, content: string, readVersion?: string): void;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cross-process lock and atomic replace every project-mode credentials
|
|
3
|
+
* write goes through (#4492).
|
|
4
|
+
*
|
|
5
|
+
* Several CLI processes write one credentials file — one agent per git
|
|
6
|
+
* worktree, all sharing the main worktree's login — so a write is a locked
|
|
7
|
+
* read-modify-write, and the file is replaced by a rename, never rewritten in
|
|
8
|
+
* place: a reader never sees a partial file.
|
|
9
|
+
*
|
|
10
|
+
* The lock is `credentials.json.lock`, created exclusively and holding its
|
|
11
|
+
* owner's token (`<pid>:<random>`). A lock older than {@link LOCK_STALE_MS}
|
|
12
|
+
* was left by a crashed process; a contender reclaims it by renaming it to a
|
|
13
|
+
* name of its own, which exactly one contender can do, and removes it only if
|
|
14
|
+
* it still holds the token it read — otherwise a live lock was moved, and it
|
|
15
|
+
* is put back unless another writer took the lock meanwhile. A holder proves,
|
|
16
|
+
* immediately before it commits, that it still owns the lock and that the file
|
|
17
|
+
* is still the version it read — a holder whose lock was moved away and put
|
|
18
|
+
* back may have missed another writer's commit in between — and releases only
|
|
19
|
+
* its own lock.
|
|
20
|
+
*
|
|
21
|
+
* Node has no advisory file lock, so one window remains: between a holder's
|
|
22
|
+
* checks and its rename, a contender that just misreclaimed that lock could
|
|
23
|
+
* let a third writer in. Every other interleaving ends in a failed write,
|
|
24
|
+
* never a lost update.
|
|
25
|
+
*/
|
|
26
|
+
import { randomBytes } from "crypto";
|
|
27
|
+
import { chmodSync, linkSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "fs";
|
|
28
|
+
import { basename, dirname, join } from "path";
|
|
29
|
+
/** A lock untouched for this long was left by a crashed process. */
|
|
30
|
+
export const LOCK_STALE_MS = 10_000;
|
|
31
|
+
/** How long a writer waits for a held lock before it fails. */
|
|
32
|
+
export const LOCK_WAIT_MS = 5_000;
|
|
33
|
+
const RETRY_MS = 25;
|
|
34
|
+
const LOCKED_FILE = "credentials.json";
|
|
35
|
+
/** A write that could not take, or lost, the lock on a credentials file. */
|
|
36
|
+
export class CredentialsLockedError extends Error {
|
|
37
|
+
constructor(lockPath) {
|
|
38
|
+
super(`credentials file is locked: ${lockPath}`);
|
|
39
|
+
this.name = "CredentialsLockedError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function sleep(ms) {
|
|
43
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
44
|
+
}
|
|
45
|
+
const isMissing = (err) => err?.code === "ENOENT";
|
|
46
|
+
/**
|
|
47
|
+
* Reclaim the stale lock at `lockPath`, whose content was `seen` when it was
|
|
48
|
+
* judged stale: rename it to a name of `token`'s own, then remove it if it
|
|
49
|
+
* still holds `seen`, or put it back if it does not (a live writer took the
|
|
50
|
+
* lock in between). Gone already is not an error: another contender won.
|
|
51
|
+
*/
|
|
52
|
+
export function reclaimStaleLock(lockPath, token, seen) {
|
|
53
|
+
const moved = `${lockPath}.stale.${token}`;
|
|
54
|
+
try {
|
|
55
|
+
renameSync(lockPath, moved);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (isMissing(err))
|
|
59
|
+
return;
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
let content;
|
|
63
|
+
try {
|
|
64
|
+
content = readFileSync(moved, "utf-8");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (content !== seen)
|
|
70
|
+
restoreDisplacedLock(moved, lockPath);
|
|
71
|
+
try {
|
|
72
|
+
unlinkSync(moved);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Swept by the next release.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Put a live lock that a reclaimer moved to `moved` back at `lockPath` — but
|
|
80
|
+
* only if no other writer has taken the lock since, which a rename would
|
|
81
|
+
* silently replace. When one has, the displaced holder has lost the lock and
|
|
82
|
+
* fails its ownership check. The caller removes `moved` either way.
|
|
83
|
+
*/
|
|
84
|
+
export function restoreDisplacedLock(moved, lockPath) {
|
|
85
|
+
try {
|
|
86
|
+
linkSync(moved, lockPath);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Taken by another writer, or no hard links here: the displaced holder
|
|
90
|
+
// has lost the lock.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The version of the file at `path` as a writer read it: what an atomic
|
|
95
|
+
* replace changes (the inode) and what an in-place write changes (size and
|
|
96
|
+
* times). `"absent"` when there is no file.
|
|
97
|
+
*/
|
|
98
|
+
export function fileVersion(path) {
|
|
99
|
+
try {
|
|
100
|
+
const s = statSync(path);
|
|
101
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}:${s.ctimeMs}`;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (isMissing(err))
|
|
105
|
+
return "absent";
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Remove what crashed writers left in `dir`: old reclaimed locks and temp files. */
|
|
110
|
+
function sweep(dir) {
|
|
111
|
+
try {
|
|
112
|
+
for (const name of readdirSync(dir)) {
|
|
113
|
+
if (!name.startsWith(`${LOCKED_FILE}.lock.stale.`) && !(name.startsWith(`${LOCKED_FILE}.`) && name.endsWith(".tmp"))) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const path = join(dir, name);
|
|
117
|
+
if (Date.now() - statSync(path).mtimeMs > LOCK_STALE_MS)
|
|
118
|
+
unlinkSync(path);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Best effort.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Take the lock on the credentials file in `dir`, waiting up to
|
|
127
|
+
* {@link LOCK_WAIT_MS} for another writer and reclaiming a stale lock. Throws
|
|
128
|
+
* {@link CredentialsLockedError} naming the lock when it stays held.
|
|
129
|
+
*/
|
|
130
|
+
export function acquireCredentialsLock(dir) {
|
|
131
|
+
const path = join(dir, `${LOCKED_FILE}.lock`);
|
|
132
|
+
const token = `${process.pid}:${randomBytes(8).toString("hex")}`;
|
|
133
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
134
|
+
for (;;) {
|
|
135
|
+
try {
|
|
136
|
+
writeFileSync(path, token, { flag: "wx", mode: 0o600 });
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (err?.code !== "EEXIST")
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
let held;
|
|
144
|
+
let seen;
|
|
145
|
+
try {
|
|
146
|
+
held = statSync(path);
|
|
147
|
+
seen = readFileSync(path, "utf-8");
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
if (isMissing(err))
|
|
151
|
+
continue;
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
if (Date.now() - held.mtimeMs > LOCK_STALE_MS) {
|
|
155
|
+
reclaimStaleLock(path, token, seen);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (Date.now() >= deadline)
|
|
159
|
+
throw new CredentialsLockedError(path);
|
|
160
|
+
sleep(RETRY_MS);
|
|
161
|
+
}
|
|
162
|
+
const ownsLock = () => {
|
|
163
|
+
try {
|
|
164
|
+
return readFileSync(path, "utf-8") === token;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
path,
|
|
172
|
+
token,
|
|
173
|
+
ownsLock,
|
|
174
|
+
release() {
|
|
175
|
+
try {
|
|
176
|
+
if (ownsLock())
|
|
177
|
+
unlinkSync(path);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Already gone.
|
|
181
|
+
}
|
|
182
|
+
sweep(dir);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Replace `path` with `content` while `lock` is held: write a private
|
|
188
|
+
* temporary file beside it, check the lock is still this holder's and, when
|
|
189
|
+
* `readVersion` is given, that `path` is still the {@link fileVersion} the
|
|
190
|
+
* holder read under the lock, then rename the temporary file over `path`. A
|
|
191
|
+
* holder that lost the lock, or whose read another writer's commit has
|
|
192
|
+
* overtaken, removes its temporary file and throws
|
|
193
|
+
* {@link CredentialsLockedError}, leaving `path` as it was.
|
|
194
|
+
*/
|
|
195
|
+
export function replaceFileUnderLock(lock, path, content, readVersion) {
|
|
196
|
+
const tmp = join(dirname(path), `${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
197
|
+
writeFileSync(tmp, content, { mode: 0o600 });
|
|
198
|
+
try {
|
|
199
|
+
if (!lock.ownsLock())
|
|
200
|
+
throw new CredentialsLockedError(lock.path);
|
|
201
|
+
if (readVersion !== undefined && fileVersion(path) !== readVersion) {
|
|
202
|
+
throw new CredentialsLockedError(lock.path);
|
|
203
|
+
}
|
|
204
|
+
renameSync(tmp, path);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
try {
|
|
208
|
+
unlinkSync(tmp);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Never written, or already renamed.
|
|
212
|
+
}
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
chmodSync(path, 0o600);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Best-effort on Windows / unusual filesystems.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=credentials-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-lock.js","sourceRoot":"","sources":["../../../src/lib/credentials-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,21 @@
|
|
|
42
42
|
* the committed environment whose tree it shares — has no slot of its own: it
|
|
43
43
|
* reads and writes its parent's, so one login and one logout cover the parent
|
|
44
44
|
* and all of its children.
|
|
45
|
+
*
|
|
46
|
+
* A slot records the server that issued it (#4492). A slot is keyed by
|
|
47
|
+
* environment name while the environment's `apiUrl` is committed, so a branch
|
|
48
|
+
* switch can point `dev` at another server than the one its stored tokens
|
|
49
|
+
* came from. A slot recording another server than the invoking environment's
|
|
50
|
+
* is never used (its tokens are not sent), and credentials from another server
|
|
51
|
+
* than the slot's environment's are never stored. A slot written before this
|
|
52
|
+
* records no server; it is read against the environment's `apiUrl`, and its
|
|
53
|
+
* next write records one.
|
|
54
|
+
*
|
|
55
|
+
* In a linked git worktree the project's file is the main worktree's
|
|
56
|
+
* (#4492, `credentials-home.ts`), so one login serves every worktree of the
|
|
57
|
+
* repository that holds the project. Every project-mode write is a locked
|
|
58
|
+
* read-modify-write with an atomic replace (`credentials-lock.ts`), because
|
|
59
|
+
* several processes — one per worktree — write that one file.
|
|
45
60
|
*/
|
|
46
61
|
import type { Credentials } from "../types/index.js";
|
|
47
62
|
export declare const PROJECT_LOCAL_DIR = ".primitive";
|
|
@@ -53,6 +68,12 @@ export declare function isProjectMode(): boolean;
|
|
|
53
68
|
* whether we're in project mode or legacy mode.
|
|
54
69
|
*/
|
|
55
70
|
export declare function getCredentialsFilePath(): string;
|
|
71
|
+
/**
|
|
72
|
+
* The credentials file of the project at `projectRoot`: its own
|
|
73
|
+
* `.primitive/credentials.json`, or in a linked git worktree the main
|
|
74
|
+
* worktree's for the same project (#4492).
|
|
75
|
+
*/
|
|
76
|
+
export declare function credentialsFileForProject(projectRoot: string): string;
|
|
56
77
|
/**
|
|
57
78
|
* Declare that THIS command may fall back to `~/.primitive/credentials.json`
|
|
58
79
|
* when no project is in scope. Called at the top of `login`, `logout`, `init`
|
|
@@ -65,6 +86,12 @@ export declare function allowGlobalCredentials(): void;
|
|
|
65
86
|
* boundary in one process, the way `resetEnvResolver()` does for the resolver.
|
|
66
87
|
*/
|
|
67
88
|
export declare function resetGlobalCredentialsAllowance(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Whether two server URLs name the same server: scheme and host compare
|
|
91
|
+
* case-insensitively and the path with trailing slashes removed. A URL that
|
|
92
|
+
* does not parse compares as its trailing-slash-trimmed string.
|
|
93
|
+
*/
|
|
94
|
+
export declare function sameServer(a: string, b: string): boolean;
|
|
68
95
|
/** The variable a CI job puts its admin token in (#3890). */
|
|
69
96
|
export declare const PRIMITIVE_TOKEN_VAR = "PRIMITIVE_TOKEN";
|
|
70
97
|
/** The token in `PRIMITIVE_TOKEN`, or null when it is unset or blank. */
|
|
@@ -84,6 +111,13 @@ export declare function environmentToken(): string | null;
|
|
|
84
111
|
* refusal: the environment is resolved first, exactly as for a stored login.
|
|
85
112
|
*/
|
|
86
113
|
export declare function loadCredentialsStore(): Credentials | null;
|
|
114
|
+
/**
|
|
115
|
+
* The current environment's stored login when its slot records another server
|
|
116
|
+
* than the environment's, blended with the server it RECORDS; null otherwise.
|
|
117
|
+
* For `logout` alone, which revokes such a login at the server that issued it
|
|
118
|
+
* and so never sends its tokens anywhere else.
|
|
119
|
+
*/
|
|
120
|
+
export declare function mismatchedLogin(): Credentials | null;
|
|
87
121
|
/**
|
|
88
122
|
* The environments that share the current environment's login (#4511): the
|
|
89
123
|
* one whose slot holds it, and its child environments on this machine. Null
|
|
@@ -93,6 +127,21 @@ export declare function loginEnvironments(): {
|
|
|
93
127
|
parent: string;
|
|
94
128
|
children: string[];
|
|
95
129
|
} | null;
|
|
130
|
+
/**
|
|
131
|
+
* What a login, logout or not-logged-in refusal adds about a linked git
|
|
132
|
+
* worktree (#4492). Each is null when it has nothing to say: outside a
|
|
133
|
+
* project, and where the project's login is its own.
|
|
134
|
+
*/
|
|
135
|
+
export declare const worktreeLoginNotes: {
|
|
136
|
+
/** After a login stored in another worktree's file: where it went. */
|
|
137
|
+
storedElsewhere(): string | null;
|
|
138
|
+
/** After a logout of `slot`'s login, when more than one worktree uses it. */
|
|
139
|
+
sharedLogout(slot: string): string | null;
|
|
140
|
+
/** A linked worktree's own credentials file, which is never read. */
|
|
141
|
+
ignoredFile(): string | null;
|
|
142
|
+
/** After "not logged in", when git is why a linked worktree has no login. */
|
|
143
|
+
notLoggedIn(): string | null;
|
|
144
|
+
};
|
|
96
145
|
/**
|
|
97
146
|
* The logins still held in the slots of the current login's child
|
|
98
147
|
* environments: sessions signed in to a child before children used their
|
|
@@ -105,9 +154,10 @@ export declare function leftoverChildLogins(): Credentials[];
|
|
|
105
154
|
* Saves credentials. In project mode, writes to the per-env slot in the
|
|
106
155
|
* project credentials file. In legacy mode, writes the global file.
|
|
107
156
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
157
|
+
* In project mode the slot records the incoming `serverUrl`, which must be
|
|
158
|
+
* the same server as the slot's environment's `apiUrl`: credentials from
|
|
159
|
+
* another server are refused with `server-mismatch` and nothing is written
|
|
160
|
+
* (#4492). In a child environment the parent's slot is written (#4511).
|
|
111
161
|
*/
|
|
112
162
|
export declare function saveCredentialsStore(credentials: Credentials): void;
|
|
113
163
|
/**
|
|
@@ -144,7 +194,9 @@ export declare function removeCredentialsSlotInProject(projectRoot: string, envN
|
|
|
144
194
|
* Writes the credentials slot for the named environment in a specific
|
|
145
195
|
* project's credentials file. Used by `primitive init` to carry the session
|
|
146
196
|
* it just authenticated into the freshly scaffolded project, so the first
|
|
147
|
-
* in-project CLI command works without a second `primitive login`.
|
|
197
|
+
* in-project CLI command works without a second `primitive login`. The slot
|
|
198
|
+
* records the credentials' server, which must be the named environment's
|
|
199
|
+
* `apiUrl` in the project's config (#4492).
|
|
148
200
|
*/
|
|
149
201
|
export declare function seedCredentialsForEnvInProject(projectRoot: string, envName: string, credentials: Credentials): void;
|
|
150
202
|
/**
|