@stelfield/cli 0.12.2 → 0.12.3
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/package.json +1 -1
- package/src/api.js +1 -1
- package/src/credentials.js +2 -25
- package/src/index.js +2 -2
- package/src/profile.js +20 -14
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -240,7 +240,7 @@ async function authedRequestWithRefresh(baseUrl, credOpts, httpOpts, makeRequest
|
|
|
240
240
|
|
|
241
241
|
// ── 命中 401:触发 refresh ────────────────────────────────
|
|
242
242
|
// 用凭证目录作 key 实现 profile 隔离(不同目录各自独立 refresh)
|
|
243
|
-
const credDir = credOpts.dir ||
|
|
243
|
+
const credDir = credOpts.dir || (await import('./profile.js')).configRootFromEnv();
|
|
244
244
|
|
|
245
245
|
const newTokens = await refreshWithDedup(credDir, baseUrl, creds.refresh_token, fetchImpl);
|
|
246
246
|
|
package/src/credentials.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { atomicWriteFile } from './atomic-write.js';
|
|
21
|
-
import { resolveCredDir
|
|
21
|
+
import { resolveCredDir } from './profile.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* 凭证目录解析(三级优先级)。
|
|
@@ -60,9 +60,7 @@ export function loadCredentials(opts = {}) {
|
|
|
60
60
|
try {
|
|
61
61
|
raw = fs.readFileSync(file, 'utf8');
|
|
62
62
|
} catch {
|
|
63
|
-
|
|
64
|
-
// old folder. Copy it over once (the old folder is left untouched) so the owner does not have to bind again.
|
|
65
|
-
return migrateLegacyCredentials(opts);
|
|
63
|
+
return null; // 文件不存在 / 读不了 → 未登录
|
|
66
64
|
}
|
|
67
65
|
try {
|
|
68
66
|
return JSON.parse(raw);
|
|
@@ -71,24 +69,3 @@ export function loadCredentials(opts = {}) {
|
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
/**
|
|
75
|
-
* One-time carry-over of a pre-rename login. Skipped when the caller pins an explicit directory (tests) so unit
|
|
76
|
-
* tests never touch the real home folder. Returns the credentials, or null when there is nothing to carry over.
|
|
77
|
-
*/
|
|
78
|
-
function migrateLegacyCredentials(opts = {}) {
|
|
79
|
-
if (opts.dir) return null;
|
|
80
|
-
try {
|
|
81
|
-
const legacyFile = path.join(resolveLegacyCredDir({ profile: opts.profile }), 'credentials');
|
|
82
|
-
const creds = JSON.parse(fs.readFileSync(legacyFile, 'utf8'));
|
|
83
|
-
if (!creds || !creds.refresh_token) return null;
|
|
84
|
-
saveCredentials(creds, opts);
|
|
85
|
-
// The poll cursor goes along, so the first poll does not replay events the Agent already handled.
|
|
86
|
-
try {
|
|
87
|
-
const cur = fs.readFileSync(path.join(path.dirname(legacyFile), 'poll-cursor'), 'utf8');
|
|
88
|
-
atomicWriteFile(path.join(path.dirname(credentialsPath(opts)), 'poll-cursor'), cur, 0o600);
|
|
89
|
-
} catch { /* no cursor yet */ }
|
|
90
|
-
return creds;
|
|
91
|
-
} catch {
|
|
92
|
-
return null; // nothing to carry over, or unreadable: behave as not logged in
|
|
93
|
-
}
|
|
94
|
-
}
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { parseArgs } from './cli-parse.js';
|
|
|
12
12
|
import { buildAskQuestions, buildAnswers } from './collab-input.js';
|
|
13
13
|
import { RETIRED_COMMANDS } from './retired-commands.js';
|
|
14
14
|
import { resolveBaseUrl } from './config.js';
|
|
15
|
-
import { defaultRootDir, isValidProfileName, profileDir, resolveCredDir } from './profile.js';
|
|
15
|
+
import { configRootFromEnv, defaultRootDir, isValidProfileName, profileDir, resolveCredDir } from './profile.js';
|
|
16
16
|
import { loadCredentials } from './credentials.js';
|
|
17
17
|
import { runAuthLogin } from './oauth.js';
|
|
18
18
|
import { runConnect } from './connect.js';
|
|
@@ -656,7 +656,7 @@ export async function run(argv, deps = {}) {
|
|
|
656
656
|
);
|
|
657
657
|
return 2;
|
|
658
658
|
}
|
|
659
|
-
profileDirPath = profileDir(flags.profile, { rootDir: env
|
|
659
|
+
profileDirPath = profileDir(flags.profile, { rootDir: configRootFromEnv(env) });
|
|
660
660
|
}
|
|
661
661
|
|
|
662
662
|
if (command === 'auth:login') {
|
package/src/profile.js
CHANGED
|
@@ -13,11 +13,28 @@
|
|
|
13
13
|
// profile 名限 [A-Za-z0-9._-]{1,32},防止拼出目录穿越路径。
|
|
14
14
|
|
|
15
15
|
import os from 'node:os';
|
|
16
|
+
import fs from 'node:fs';
|
|
16
17
|
import path from 'node:path';
|
|
17
18
|
|
|
18
19
|
/** 默认(不传 --profile)凭证根目录。 */
|
|
19
20
|
export function defaultRootDir() {
|
|
20
|
-
|
|
21
|
+
// An Agent that logged in under the old `linger` command keeps its login in ~/.linger. Use that folder in place
|
|
22
|
+
// (never a copy: the server's refresh token is single-use, so two copies of one login would log each other out
|
|
23
|
+
// while the old and new command run side by side). Fresh installs use ~/.stelfield.
|
|
24
|
+
const fresh = path.join(os.homedir(), '.stelfield');
|
|
25
|
+
const legacy = path.join(os.homedir(), '.linger');
|
|
26
|
+
if (hasLogin(fresh)) return fresh;
|
|
27
|
+
if (hasLogin(legacy)) return legacy;
|
|
28
|
+
return fresh;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasLogin(dir) {
|
|
32
|
+
return fs.existsSync(path.join(dir, 'credentials')) || fs.existsSync(path.join(dir, 'profiles'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Config root named by the environment: STELFIELD_CONFIG_DIR, else the old LINGER_CONFIG_DIR, else the default. */
|
|
36
|
+
export function configRootFromEnv(env = process.env) {
|
|
37
|
+
return env.STELFIELD_CONFIG_DIR || env.LINGER_CONFIG_DIR || defaultRootDir();
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
/** profile 名合法性(目录名安全子集 · 挡住 ../ 之类)。 */
|
|
@@ -51,22 +68,11 @@ export function profileDir(name, opts = {}) {
|
|
|
51
68
|
* 注:--profile 优先级高于 STELFIELD_CONFIG_DIR——显式开关应当压过环境变量,
|
|
52
69
|
* 否则用户设了环境变量后 --profile 就成了哑参数(排查起来很费解)。
|
|
53
70
|
*/
|
|
54
|
-
/**
|
|
55
|
-
* Where the same Agent kept its login before the rename (command `linger`, folder ~/.linger, env LINGER_CONFIG_DIR).
|
|
56
|
-
* Used only to carry an existing login over once; nothing is ever written there.
|
|
57
|
-
*/
|
|
58
|
-
export function resolveLegacyCredDir({ profile, env = process.env } = {}) {
|
|
59
|
-
const root = env.LINGER_CONFIG_DIR || path.join(os.homedir(), '.linger');
|
|
60
|
-
if (profile != null && profile !== '') return profileDir(profile, { rootDir: root });
|
|
61
|
-
return root;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
71
|
export function resolveCredDir({ dir, profile, env = process.env } = {}) {
|
|
65
72
|
if (dir) return dir;
|
|
66
73
|
if (profile != null && profile !== '') {
|
|
67
|
-
const root = env
|
|
74
|
+
const root = configRootFromEnv(env);
|
|
68
75
|
return profileDir(profile, { rootDir: root });
|
|
69
76
|
}
|
|
70
|
-
|
|
71
|
-
return defaultRootDir();
|
|
77
|
+
return configRootFromEnv(env);
|
|
72
78
|
}
|