@ecomweb/cli 0.7.0 → 0.8.0
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 +56 -32
- package/dist/base-command.d.ts +8 -0
- package/dist/base-command.js +43 -4
- package/dist/base-command.js.map +1 -1
- package/dist/commands/auth/login.d.ts +6 -4
- package/dist/commands/auth/login.js +117 -89
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +28 -9
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/profiles/delete.js +11 -0
- package/dist/commands/auth/profiles/delete.js.map +1 -1
- package/dist/commands/auth/profiles/list.js +2 -2
- package/dist/commands/auth/profiles/list.js.map +1 -1
- package/dist/commands/auth/status.js +17 -9
- package/dist/commands/auth/status.js.map +1 -1
- package/dist/commands/{auth/register.d.ts → stores/create.d.ts} +4 -3
- package/dist/commands/stores/create.js +68 -0
- package/dist/commands/stores/create.js.map +1 -0
- package/dist/commands/stores/current.js +1 -1
- package/dist/commands/stores/current.js.map +1 -1
- package/dist/commands/stores/list.js +3 -3
- package/dist/commands/stores/list.js.map +1 -1
- package/dist/commands/stores/switch.js +14 -6
- package/dist/commands/stores/switch.js.map +1 -1
- package/dist/lib/api-client.d.ts +5 -4
- package/dist/lib/api-client.js +32 -16
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/codex-skill.d.ts +1 -1
- package/dist/lib/codex-skill.js +12 -8
- package/dist/lib/codex-skill.js.map +1 -1
- package/dist/lib/config.d.ts +6 -18
- package/dist/lib/config.js +35 -64
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/credential-store.d.ts +44 -0
- package/dist/lib/credential-store.js +137 -0
- package/dist/lib/credential-store.js.map +1 -0
- package/dist/lib/device-login.d.ts +22 -0
- package/dist/lib/device-login.js +66 -0
- package/dist/lib/device-login.js.map +1 -0
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +6 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/file-lock.d.ts +6 -0
- package/dist/lib/file-lock.js +66 -0
- package/dist/lib/file-lock.js.map +1 -0
- package/dist/lib/open-browser.d.ts +13 -0
- package/dist/lib/open-browser.js +38 -0
- package/dist/lib/open-browser.js.map +1 -0
- package/dist/lib/pending-login.d.ts +5 -0
- package/dist/lib/pending-login.js +39 -0
- package/dist/lib/pending-login.js.map +1 -0
- package/dist/lib/secure-file.d.ts +1 -0
- package/dist/lib/secure-file.js +15 -0
- package/dist/lib/secure-file.js.map +1 -0
- package/dist/lib/session.d.ts +40 -0
- package/dist/lib/session.js +80 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/store-selection.d.ts +19 -0
- package/dist/lib/store-selection.js +23 -0
- package/dist/lib/store-selection.js.map +1 -0
- package/oclif.manifest.json +227 -578
- package/package.json +2 -1
- package/dist/commands/auth/register.js +0 -35
- package/dist/commands/auth/register.js.map +0 -1
- package/dist/commands/auth/verify.d.ts +0 -20
- package/dist/commands/auth/verify.js +0 -43
- package/dist/commands/auth/verify.js.map +0 -1
- package/dist/commands/init.d.ts +0 -22
- package/dist/commands/init.js +0 -84
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -19
- package/dist/commands/setup.js +0 -186
- package/dist/commands/setup.js.map +0 -1
- package/dist/lib/email-login.d.ts +0 -35
- package/dist/lib/email-login.js +0 -86
- package/dist/lib/email-login.js.map +0 -1
package/dist/lib/config.js
CHANGED
|
@@ -33,13 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ConfigManager = void 0;
|
|
36
|
+
exports.ConfigManager = exports.CREDENTIALS_DIR = void 0;
|
|
37
|
+
exports.assertValidProfileName = assertValidProfileName;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const os = __importStar(require("os"));
|
|
41
|
+
const errors_1 = require("./errors");
|
|
40
42
|
const CONFIG_DIR = path.join(os.homedir(), ".config", "ecomweb");
|
|
41
43
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
42
|
-
|
|
44
|
+
exports.CREDENTIALS_DIR = path.join(CONFIG_DIR, "credentials");
|
|
43
45
|
const DEFAULT_PROFILE = {
|
|
44
46
|
api_url: "https://apibase.ecomweb.app/api/v1",
|
|
45
47
|
default_output: "json",
|
|
@@ -50,9 +52,24 @@ const DEFAULT_CONFIG = {
|
|
|
50
52
|
default: { ...DEFAULT_PROFILE },
|
|
51
53
|
},
|
|
52
54
|
};
|
|
55
|
+
function assertValidProfileName(name, source) {
|
|
56
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name.includes("..")) {
|
|
57
|
+
const allowed = "Use only letters, digits, dot, underscore and hyphen (no \"..\").";
|
|
58
|
+
const from = source === "flag"
|
|
59
|
+
? " (from --profile)"
|
|
60
|
+
: source === "config"
|
|
61
|
+
? ` (from active_profile in ${CONFIG_FILE})`
|
|
62
|
+
: "";
|
|
63
|
+
throw new errors_1.CliError("INVALID_PROFILE_NAME", `Invalid profile name: ${JSON.stringify(name)}${from}`, errors_1.ExitCode.VALIDATION, {
|
|
64
|
+
hint: source === "config" ? `${allowed} Fix active_profile in ${CONFIG_FILE}, or pass --profile default.` : allowed,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
53
68
|
class ConfigManager {
|
|
54
|
-
constructor() {
|
|
69
|
+
constructor(selectedProfile) {
|
|
55
70
|
this.config = this.loadConfig();
|
|
71
|
+
this.selected = selectedProfile || this.config.active_profile;
|
|
72
|
+
assertValidProfileName(this.selected, selectedProfile ? "flag" : "config");
|
|
56
73
|
}
|
|
57
74
|
ensureDir(dir) {
|
|
58
75
|
if (!fs.existsSync(dir)) {
|
|
@@ -68,7 +85,7 @@ class ConfigManager {
|
|
|
68
85
|
}
|
|
69
86
|
catch {
|
|
70
87
|
}
|
|
71
|
-
return
|
|
88
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
72
89
|
}
|
|
73
90
|
save() {
|
|
74
91
|
this.ensureDir(CONFIG_DIR);
|
|
@@ -77,11 +94,15 @@ class ConfigManager {
|
|
|
77
94
|
getActiveProfileName() {
|
|
78
95
|
return this.config.active_profile;
|
|
79
96
|
}
|
|
97
|
+
getProfileName() {
|
|
98
|
+
return this.selected;
|
|
99
|
+
}
|
|
80
100
|
getProfile(name) {
|
|
81
|
-
const profileName = name || this.
|
|
101
|
+
const profileName = name || this.selected;
|
|
82
102
|
return this.config.profiles[profileName] || { ...DEFAULT_PROFILE };
|
|
83
103
|
}
|
|
84
104
|
setProfile(name, profile) {
|
|
105
|
+
assertValidProfileName(name);
|
|
85
106
|
this.config.profiles[name] = {
|
|
86
107
|
...DEFAULT_PROFILE,
|
|
87
108
|
...this.config.profiles[name],
|
|
@@ -90,11 +111,12 @@ class ConfigManager {
|
|
|
90
111
|
this.save();
|
|
91
112
|
}
|
|
92
113
|
setActiveProfile(name) {
|
|
114
|
+
assertValidProfileName(name);
|
|
93
115
|
this.config.active_profile = name;
|
|
94
116
|
this.save();
|
|
95
117
|
}
|
|
96
118
|
setStoreRef(storeRef, storeName, profileName) {
|
|
97
|
-
const name = profileName || this.
|
|
119
|
+
const name = profileName || this.selected;
|
|
98
120
|
const profile = this.getProfile(name);
|
|
99
121
|
profile.store_ref = storeRef;
|
|
100
122
|
if (storeName)
|
|
@@ -105,62 +127,6 @@ class ConfigManager {
|
|
|
105
127
|
getStoreRef() {
|
|
106
128
|
return this.getProfile().store_ref;
|
|
107
129
|
}
|
|
108
|
-
getApiKey(profileName) {
|
|
109
|
-
const name = profileName || this.config.active_profile;
|
|
110
|
-
const keyFile = path.join(CREDENTIALS_DIR, `${name}.key`);
|
|
111
|
-
try {
|
|
112
|
-
if (fs.existsSync(keyFile)) {
|
|
113
|
-
return fs.readFileSync(keyFile, "utf-8").trim();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
}
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
setApiKey(key, profileName) {
|
|
121
|
-
const name = profileName || this.config.active_profile;
|
|
122
|
-
this.ensureDir(CREDENTIALS_DIR);
|
|
123
|
-
const keyFile = path.join(CREDENTIALS_DIR, `${name}.key`);
|
|
124
|
-
fs.writeFileSync(keyFile, key, { mode: 0o600 });
|
|
125
|
-
}
|
|
126
|
-
removeApiKey(profileName) {
|
|
127
|
-
const name = profileName || this.config.active_profile;
|
|
128
|
-
const keyFile = path.join(CREDENTIALS_DIR, `${name}.key`);
|
|
129
|
-
if (fs.existsSync(keyFile)) {
|
|
130
|
-
fs.unlinkSync(keyFile);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
getJwtTokens(profileName) {
|
|
134
|
-
const name = profileName || this.config.active_profile;
|
|
135
|
-
const tokenFile = path.join(CREDENTIALS_DIR, `${name}.jwt`);
|
|
136
|
-
try {
|
|
137
|
-
if (fs.existsSync(tokenFile)) {
|
|
138
|
-
return JSON.parse(fs.readFileSync(tokenFile, "utf-8"));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
}
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
setJwtTokens(tokens, profileName) {
|
|
146
|
-
const name = profileName || this.config.active_profile;
|
|
147
|
-
this.ensureDir(CREDENTIALS_DIR);
|
|
148
|
-
const tokenFile = path.join(CREDENTIALS_DIR, `${name}.jwt`);
|
|
149
|
-
fs.writeFileSync(tokenFile, JSON.stringify(tokens), { mode: 0o600 });
|
|
150
|
-
}
|
|
151
|
-
removeJwtTokens(profileName) {
|
|
152
|
-
const name = profileName || this.config.active_profile;
|
|
153
|
-
const tokenFile = path.join(CREDENTIALS_DIR, `${name}.jwt`);
|
|
154
|
-
if (fs.existsSync(tokenFile)) {
|
|
155
|
-
fs.unlinkSync(tokenFile);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
isJwtExpired(profileName) {
|
|
159
|
-
const tokens = this.getJwtTokens(profileName);
|
|
160
|
-
if (!tokens)
|
|
161
|
-
return true;
|
|
162
|
-
return Date.now() / 1000 >= tokens.expires_at - 60;
|
|
163
|
-
}
|
|
164
130
|
getProfileNames() {
|
|
165
131
|
return Object.keys(this.config.profiles);
|
|
166
132
|
}
|
|
@@ -170,7 +136,7 @@ class ConfigManager {
|
|
|
170
136
|
this.config.active_profile = "default";
|
|
171
137
|
}
|
|
172
138
|
this.save();
|
|
173
|
-
this.
|
|
139
|
+
this.removeLegacyCredentials(name);
|
|
174
140
|
}
|
|
175
141
|
resolve(flag, envVar, profileValue, defaultValue) {
|
|
176
142
|
if (flag !== undefined)
|
|
@@ -186,7 +152,12 @@ class ConfigManager {
|
|
|
186
152
|
return this.resolve(flag, "ECOMWEB_API_URL", this.getProfile().api_url, "https://apibase.ecomweb.app/api/v1");
|
|
187
153
|
}
|
|
188
154
|
resolveApiKey(flag) {
|
|
189
|
-
return this.resolve(flag, "ECOMWEB_API_KEY",
|
|
155
|
+
return this.resolve(flag, "ECOMWEB_API_KEY", undefined, undefined);
|
|
156
|
+
}
|
|
157
|
+
removeLegacyCredentials(profileName) {
|
|
158
|
+
for (const ext of ["key", "jwt"]) {
|
|
159
|
+
fs.rmSync(path.join(exports.CREDENTIALS_DIR, `${profileName}.${ext}`), { force: true });
|
|
160
|
+
}
|
|
190
161
|
}
|
|
191
162
|
resolveStoreRef(flag) {
|
|
192
163
|
const envVal = process.env["ECOMWEB_STORE_REF"];
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface StoredSession {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
expires_at: number;
|
|
5
|
+
email: string | null;
|
|
6
|
+
user_id: string;
|
|
7
|
+
api_url?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CredentialStore {
|
|
10
|
+
readonly kind: "keychain" | "file";
|
|
11
|
+
get(profile: string): StoredSession | undefined;
|
|
12
|
+
set(profile: string, session: StoredSession): void;
|
|
13
|
+
delete(profile: string): void;
|
|
14
|
+
}
|
|
15
|
+
export declare const KEYCHAIN_SERVICE = "ecomweb-cli";
|
|
16
|
+
export interface KeyringEntry {
|
|
17
|
+
getPassword(): string | null;
|
|
18
|
+
setPassword(password: string): void;
|
|
19
|
+
deletePassword(): boolean;
|
|
20
|
+
}
|
|
21
|
+
export type EntryFactory = (service: string, account: string) => KeyringEntry;
|
|
22
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
23
|
+
private readonly dir;
|
|
24
|
+
readonly kind: "file";
|
|
25
|
+
constructor(dir: string);
|
|
26
|
+
private file;
|
|
27
|
+
get(profile: string): StoredSession | undefined;
|
|
28
|
+
set(profile: string, session: StoredSession): void;
|
|
29
|
+
delete(profile: string): void;
|
|
30
|
+
}
|
|
31
|
+
export declare class KeychainCredentialStore implements CredentialStore {
|
|
32
|
+
private readonly entry;
|
|
33
|
+
readonly kind: "keychain";
|
|
34
|
+
constructor(entry: EntryFactory);
|
|
35
|
+
get(profile: string): StoredSession | undefined;
|
|
36
|
+
set(profile: string, session: StoredSession): void;
|
|
37
|
+
delete(profile: string): void;
|
|
38
|
+
}
|
|
39
|
+
export declare function openCredentialStore(opts: {
|
|
40
|
+
dir: string;
|
|
41
|
+
env?: NodeJS.ProcessEnv;
|
|
42
|
+
loadEntry?: () => EntryFactory;
|
|
43
|
+
}): CredentialStore;
|
|
44
|
+
export declare function lazyCredentialStore(open: () => CredentialStore): CredentialStore;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KeychainCredentialStore = exports.FileCredentialStore = exports.KEYCHAIN_SERVICE = void 0;
|
|
7
|
+
exports.openCredentialStore = openCredentialStore;
|
|
8
|
+
exports.lazyCredentialStore = lazyCredentialStore;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const secure_file_1 = require("./secure-file");
|
|
13
|
+
exports.KEYCHAIN_SERVICE = "ecomweb-cli";
|
|
14
|
+
function parseSession(raw) {
|
|
15
|
+
try {
|
|
16
|
+
const s = JSON.parse(raw);
|
|
17
|
+
if (typeof s?.access_token === "string" &&
|
|
18
|
+
typeof s.refresh_token === "string" &&
|
|
19
|
+
typeof s.expires_at === "number" &&
|
|
20
|
+
typeof s.user_id === "string") {
|
|
21
|
+
return {
|
|
22
|
+
access_token: s.access_token,
|
|
23
|
+
refresh_token: s.refresh_token,
|
|
24
|
+
expires_at: s.expires_at,
|
|
25
|
+
email: s.email ?? null,
|
|
26
|
+
user_id: s.user_id,
|
|
27
|
+
...(typeof s.api_url === "string" && { api_url: s.api_url }),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
class FileCredentialStore {
|
|
36
|
+
constructor(dir) {
|
|
37
|
+
this.dir = dir;
|
|
38
|
+
this.kind = "file";
|
|
39
|
+
}
|
|
40
|
+
file(profile) {
|
|
41
|
+
return node_path_1.default.join(this.dir, `${profile}.session.json`);
|
|
42
|
+
}
|
|
43
|
+
get(profile) {
|
|
44
|
+
try {
|
|
45
|
+
return parseSession((0, node_fs_1.readFileSync)(this.file(profile), "utf8"));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
set(profile, session) {
|
|
52
|
+
(0, secure_file_1.writePrivateFile)(this.file(profile), JSON.stringify(session));
|
|
53
|
+
}
|
|
54
|
+
delete(profile) {
|
|
55
|
+
(0, node_fs_1.rmSync)(this.file(profile), { force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.FileCredentialStore = FileCredentialStore;
|
|
59
|
+
function keychainRefused(error) {
|
|
60
|
+
return new errors_1.CliError("CREDENTIAL_STORE_UNAVAILABLE", `The OS keychain refused access: ${error instanceof Error ? error.message : String(error)}`, errors_1.ExitCode.AUTH_FAILURE, { hint: "Unlock the keychain, or keep the login in a file instead: export ECOMWEB_CREDENTIAL_STORE=file" });
|
|
61
|
+
}
|
|
62
|
+
class KeychainCredentialStore {
|
|
63
|
+
constructor(entry) {
|
|
64
|
+
this.entry = entry;
|
|
65
|
+
this.kind = "keychain";
|
|
66
|
+
}
|
|
67
|
+
get(profile) {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = this.entry(exports.KEYCHAIN_SERVICE, profile).getPassword();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw keychainRefused(error);
|
|
74
|
+
}
|
|
75
|
+
return raw ? parseSession(raw) : undefined;
|
|
76
|
+
}
|
|
77
|
+
set(profile, session) {
|
|
78
|
+
try {
|
|
79
|
+
this.entry(exports.KEYCHAIN_SERVICE, profile).setPassword(JSON.stringify(session));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw keychainRefused(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
delete(profile) {
|
|
86
|
+
try {
|
|
87
|
+
this.entry(exports.KEYCHAIN_SERVICE, profile).deletePassword();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.KeychainCredentialStore = KeychainCredentialStore;
|
|
94
|
+
function loadNapiKeyring() {
|
|
95
|
+
const { Entry } = require("@napi-rs/keyring");
|
|
96
|
+
return (service, account) => new Entry(service, account);
|
|
97
|
+
}
|
|
98
|
+
function probeKeychain(load) {
|
|
99
|
+
try {
|
|
100
|
+
const entry = load();
|
|
101
|
+
entry(exports.KEYCHAIN_SERVICE, "__probe__").getPassword();
|
|
102
|
+
return entry;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function openCredentialStore(opts) {
|
|
109
|
+
const forced = (opts.env ?? process.env).ECOMWEB_CREDENTIAL_STORE;
|
|
110
|
+
if (forced !== undefined && forced !== "file" && forced !== "keychain") {
|
|
111
|
+
throw new errors_1.CliError("INVALID_CREDENTIAL_STORE", `ECOMWEB_CREDENTIAL_STORE must be "file" or "keychain", not "${forced}"`, errors_1.ExitCode.VALIDATION);
|
|
112
|
+
}
|
|
113
|
+
if (forced === "file")
|
|
114
|
+
return new FileCredentialStore(opts.dir);
|
|
115
|
+
const entry = probeKeychain(opts.loadEntry ?? loadNapiKeyring);
|
|
116
|
+
if (entry)
|
|
117
|
+
return new KeychainCredentialStore(entry);
|
|
118
|
+
if (forced === "keychain") {
|
|
119
|
+
throw new errors_1.CliError("KEYCHAIN_UNAVAILABLE", "No OS keychain is available on this machine", errors_1.ExitCode.ERROR, {
|
|
120
|
+
hint: "Unset ECOMWEB_CREDENTIAL_STORE to keep the login in a file instead",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return new FileCredentialStore(opts.dir);
|
|
124
|
+
}
|
|
125
|
+
function lazyCredentialStore(open) {
|
|
126
|
+
let inner;
|
|
127
|
+
const store = () => (inner ??= open());
|
|
128
|
+
return {
|
|
129
|
+
get kind() {
|
|
130
|
+
return store().kind;
|
|
131
|
+
},
|
|
132
|
+
get: (profile) => store().get(profile),
|
|
133
|
+
set: (profile, session) => store().set(profile, session),
|
|
134
|
+
delete: (profile) => store().delete(profile),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/lib/credential-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CliError } from "./errors";
|
|
2
|
+
import { TokenResponse } from "./session";
|
|
3
|
+
export interface PendingLogin {
|
|
4
|
+
device_code: string;
|
|
5
|
+
user_code: string;
|
|
6
|
+
verification_uri_complete: string;
|
|
7
|
+
interval: number;
|
|
8
|
+
expires_at: number;
|
|
9
|
+
api_url: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DevicePoster {
|
|
12
|
+
post<T = any>(path: string, body?: unknown): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
export declare function deviceAuthExpired(): CliError;
|
|
15
|
+
export declare function deviceAuthDenied(): CliError;
|
|
16
|
+
export declare function requestDeviceCode(api: DevicePoster, deviceName: string, apiUrl: string, now: number): Promise<PendingLogin>;
|
|
17
|
+
export declare function pollUntil(api: DevicePoster, pending: PendingLogin, opts: {
|
|
18
|
+
deadline: number;
|
|
19
|
+
now: () => number;
|
|
20
|
+
sleep: (ms: number) => Promise<void>;
|
|
21
|
+
onInterval?: (interval: number) => void;
|
|
22
|
+
}): Promise<TokenResponse | undefined>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deviceAuthExpired = deviceAuthExpired;
|
|
4
|
+
exports.deviceAuthDenied = deviceAuthDenied;
|
|
5
|
+
exports.requestDeviceCode = requestDeviceCode;
|
|
6
|
+
exports.pollUntil = pollUntil;
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
function deviceAuthExpired() {
|
|
9
|
+
return new errors_1.CliError("DEVICE_AUTH_EXPIRED", "The sign-in code expired before it was approved", errors_1.ExitCode.AUTH_FAILURE, {
|
|
10
|
+
hint: "Run: ecomweb auth login",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function deviceAuthDenied() {
|
|
14
|
+
return new errors_1.CliError("DEVICE_AUTH_DENIED", "The sign-in was denied in the browser", errors_1.ExitCode.AUTH_FAILURE, {
|
|
15
|
+
hint: "Run: ecomweb auth login",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function requestDeviceCode(api, deviceName, apiUrl, now) {
|
|
19
|
+
const r = await api.post("/auth/device/code", { device_name: deviceName.slice(0, 100) });
|
|
20
|
+
return {
|
|
21
|
+
device_code: r.device_code,
|
|
22
|
+
user_code: r.user_code,
|
|
23
|
+
verification_uri_complete: r.verification_uri_complete,
|
|
24
|
+
interval: r.interval,
|
|
25
|
+
expires_at: now + r.expires_in * 1000,
|
|
26
|
+
api_url: apiUrl,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function pollOnce(api, deviceCode, interval) {
|
|
30
|
+
try {
|
|
31
|
+
return { status: "approved", tokens: await api.post("/auth/device/token", { device_code: deviceCode }) };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
switch ((0, errors_1.apiErrorCode)(error)) {
|
|
35
|
+
case "DEVICE_AUTH_PENDING":
|
|
36
|
+
return { status: "pending", interval };
|
|
37
|
+
case "DEVICE_AUTH_SLOW_DOWN":
|
|
38
|
+
return { status: "pending", interval: interval + 5 };
|
|
39
|
+
case "DEVICE_AUTH_DENIED":
|
|
40
|
+
throw deviceAuthDenied();
|
|
41
|
+
case "DEVICE_AUTH_EXPIRED":
|
|
42
|
+
case "DEVICE_AUTH_INVALID_CODE":
|
|
43
|
+
throw deviceAuthExpired();
|
|
44
|
+
default:
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function pollUntil(api, pending, opts) {
|
|
50
|
+
let interval = pending.interval;
|
|
51
|
+
for (;;) {
|
|
52
|
+
if (opts.now() >= pending.expires_at)
|
|
53
|
+
throw deviceAuthExpired();
|
|
54
|
+
const outcome = await pollOnce(api, pending.device_code, interval);
|
|
55
|
+
if (outcome.status === "approved")
|
|
56
|
+
return outcome.tokens;
|
|
57
|
+
if (outcome.interval !== interval) {
|
|
58
|
+
interval = outcome.interval;
|
|
59
|
+
opts.onInterval?.(interval);
|
|
60
|
+
}
|
|
61
|
+
if (opts.now() + interval * 1000 > opts.deadline)
|
|
62
|
+
return undefined;
|
|
63
|
+
await opts.sleep(interval * 1000);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=device-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-login.js","sourceRoot":"","sources":["../../src/lib/device-login.ts"],"names":[],"mappings":";;AA6BA,8CAIC;AAED,4CAIC;AAED,8CAUC;AA4BD,8BAiBC;AAhGD,qCAA4D;AA6B5D,SAAgB,iBAAiB;IAC/B,OAAO,IAAI,iBAAQ,CAAC,qBAAqB,EAAE,iDAAiD,EAAE,iBAAQ,CAAC,YAAY,EAAE;QACnH,IAAI,EAAE,yBAAyB;KAChC,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,gBAAgB;IAC9B,OAAO,IAAI,iBAAQ,CAAC,oBAAoB,EAAE,uCAAuC,EAAE,iBAAQ,CAAC,YAAY,EAAE;QACxG,IAAI,EAAE,yBAAyB;KAChC,CAAC,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,iBAAiB,CAAC,GAAiB,EAAE,UAAkB,EAAE,MAAc,EAAE,GAAW;IACxG,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,CAAqB,mBAAmB,EAAE,EAAE,WAAW,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7G,OAAO;QACL,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,yBAAyB,EAAE,CAAC,CAAC,yBAAyB;QACtD,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,UAAU,EAAE,GAAG,GAAG,CAAC,CAAC,UAAU,GAAG,IAAI;QACrC,OAAO,EAAE,MAAM;KAChB,CAAC;AACJ,CAAC;AAID,KAAK,UAAU,QAAQ,CAAC,GAAiB,EAAE,UAAkB,EAAE,QAAgB;IAC7E,IAAI,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,IAAI,CAAgB,oBAAoB,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC1H,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,IAAA,qBAAY,EAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,KAAK,qBAAqB;gBACxB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;YACzC,KAAK,uBAAuB;gBAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,CAAC,EAAE,CAAC;YACvD,KAAK,oBAAoB;gBACvB,MAAM,gBAAgB,EAAE,CAAC;YAC3B,KAAK,qBAAqB,CAAC;YAC3B,KAAK,0BAA0B;gBAC7B,MAAM,iBAAiB,EAAE,CAAC;YAC5B;gBACE,MAAM,KAAK,CAAC;QAChB,CAAC;IACH,CAAC;AACH,CAAC;AAMM,KAAK,UAAU,SAAS,CAC7B,GAAiB,EACjB,OAAqB,EACrB,IAA4H;IAE5H,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChC,SAAS,CAAC;QACR,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,UAAU;YAAE,MAAM,iBAAiB,EAAE,CAAC;QAChE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC;QACzD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YAC5B,IAAI,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QACnE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC;AACH,CAAC"}
|
package/dist/lib/errors.d.ts
CHANGED
package/dist/lib/errors.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CliError = exports.ExitCode = void 0;
|
|
4
4
|
exports.formatError = formatError;
|
|
5
|
+
exports.apiErrorCode = apiErrorCode;
|
|
5
6
|
var ExitCode;
|
|
6
7
|
(function (ExitCode) {
|
|
7
8
|
ExitCode[ExitCode["SUCCESS"] = 0] = "SUCCESS";
|
|
@@ -67,7 +68,7 @@ function mapHttpError(err) {
|
|
|
67
68
|
error: {
|
|
68
69
|
code: "FORBIDDEN",
|
|
69
70
|
message: serverMessage || "Insufficient permissions.",
|
|
70
|
-
hint: "
|
|
71
|
+
hint: "Your role in this store (or your API key's scopes) does not allow this.",
|
|
71
72
|
},
|
|
72
73
|
},
|
|
73
74
|
exitCode: ExitCode.AUTH_FAILURE,
|
|
@@ -115,4 +116,8 @@ function mapHttpError(err) {
|
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
function apiErrorCode(error) {
|
|
120
|
+
const code = error?.response?.data?.code;
|
|
121
|
+
return typeof code === "string" ? code : undefined;
|
|
122
|
+
}
|
|
118
123
|
//# sourceMappingURL=errors.js.map
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":";;;AAwDA,kCAcC;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.withFileLock = withFileLock;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
function isStale(lockPath, staleMs) {
|
|
12
|
+
try {
|
|
13
|
+
return Date.now() - (0, node_fs_1.statSync)(lockPath).mtimeMs > staleMs;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function release(lockPath, token) {
|
|
20
|
+
let content;
|
|
21
|
+
try {
|
|
22
|
+
content = (0, node_fs_1.readFileSync)(lockPath, "utf8");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (content.includes(token))
|
|
28
|
+
(0, node_fs_1.rmSync)(lockPath, { force: true });
|
|
29
|
+
}
|
|
30
|
+
async function withFileLock(lockPath, fn, opts = {}) {
|
|
31
|
+
const staleMs = opts.staleMs ?? 30_000;
|
|
32
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
33
|
+
const pollMs = opts.pollMs ?? 100;
|
|
34
|
+
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(lockPath), { recursive: true, mode: 0o700 });
|
|
35
|
+
const token = (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
36
|
+
const deadline = Date.now() + timeoutMs;
|
|
37
|
+
let fd;
|
|
38
|
+
while (fd === undefined) {
|
|
39
|
+
try {
|
|
40
|
+
fd = (0, node_fs_1.openSync)(lockPath, "wx", 0o600);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== "EEXIST")
|
|
44
|
+
throw error;
|
|
45
|
+
if (isStale(lockPath, staleMs)) {
|
|
46
|
+
(0, node_fs_1.rmSync)(lockPath, { force: true });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (Date.now() >= deadline) {
|
|
50
|
+
throw new errors_1.CliError("CREDENTIAL_LOCK_TIMEOUT", `Another ecomweb command is holding ${lockPath}`, errors_1.ExitCode.ERROR, {
|
|
51
|
+
hint: `If no other ecomweb command is running, delete ${lockPath}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
(0, node_fs_1.writeSync)(fd, `${process.pid} ${new Date().toISOString()} ${token}\n`);
|
|
59
|
+
return await fn();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
(0, node_fs_1.closeSync)(fd);
|
|
63
|
+
release(lockPath, token);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/lib/file-lock.ts"],"names":[],"mappings":";;;;;AAoCA,oCA2CC;AA/ED,6CAA0C;AAC1C,qCAAoG;AACpG,0DAA6B;AAC7B,qCAA8C;AAS9C,SAAS,OAAO,CAAC,QAAgB,EAAE,OAAe;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAA,kBAAQ,EAAC,QAAQ,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAGD,SAAS,OAAO,CAAC,QAAgB,EAAE,KAAa;IAC9C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,IAAA,sBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,IAAA,gBAAM,EAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAOM,KAAK,UAAU,YAAY,CAAI,QAAgB,EAAE,EAAoB,EAAE,OAAwB,EAAE;IACtG,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;IAClC,IAAA,mBAAS,EAAC,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAGpE,MAAM,KAAK,GAAG,IAAA,yBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,IAAI,EAAsB,CAAC;IAC3B,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,EAAE,GAAG,IAAA,kBAAQ,EAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YACpE,IAAI,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC/B,IAAA,gBAAM,EAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClC,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,IAAI,iBAAQ,CAAC,yBAAyB,EAAE,sCAAsC,QAAQ,EAAE,EAAE,iBAAQ,CAAC,KAAK,EAAE;oBAC9G,IAAI,EAAE,kDAAkD,QAAQ,EAAE;iBACnE,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,IAAA,mBAAS,EAAC,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC;QACvE,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,IAAA,mBAAS,EAAC,EAAE,CAAC,CAAC;QASd,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type SpawnLike = (command: string, args: string[], options: {
|
|
2
|
+
detached: boolean;
|
|
3
|
+
stdio: "ignore";
|
|
4
|
+
}) => {
|
|
5
|
+
on(event: "error", listener: () => void): unknown;
|
|
6
|
+
unref(): void;
|
|
7
|
+
};
|
|
8
|
+
export declare function openBrowser(url: string, deps?: {
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
spawn?: SpawnLike;
|
|
12
|
+
}): boolean;
|
|
13
|
+
export {};
|