@vectorplane/ctrl-cli 0.1.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/LICENSE +21 -0
- package/README.md +167 -0
- package/bin/vp.js +12 -0
- package/dist/commands/bootstrap.d.ts +1 -0
- package/dist/commands/bootstrap.js +40 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +96 -0
- package/dist/commands/context.d.ts +1 -0
- package/dist/commands/context.js +46 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +69 -0
- package/dist/commands/event.d.ts +1 -0
- package/dist/commands/event.js +65 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +39 -0
- package/dist/commands/session.d.ts +1 -0
- package/dist/commands/session.js +110 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +46 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +108 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +32 -0
- package/dist/commands/workspace.d.ts +1 -0
- package/dist/commands/workspace.js +66 -0
- package/dist/core/api.d.ts +24 -0
- package/dist/core/api.js +190 -0
- package/dist/core/auth.d.ts +16 -0
- package/dist/core/auth.js +71 -0
- package/dist/core/cli.d.ts +9 -0
- package/dist/core/cli.js +66 -0
- package/dist/core/config.d.ts +31 -0
- package/dist/core/config.js +263 -0
- package/dist/core/constants.d.ts +22 -0
- package/dist/core/constants.js +78 -0
- package/dist/core/env.d.ts +2 -0
- package/dist/core/env.js +10 -0
- package/dist/core/errors.d.ts +18 -0
- package/dist/core/errors.js +37 -0
- package/dist/core/git.d.ts +2 -0
- package/dist/core/git.js +66 -0
- package/dist/core/logger.d.ts +8 -0
- package/dist/core/logger.js +52 -0
- package/dist/core/machine.d.ts +4 -0
- package/dist/core/machine.js +87 -0
- package/dist/core/queue.d.ts +14 -0
- package/dist/core/queue.js +62 -0
- package/dist/core/runtime.d.ts +13 -0
- package/dist/core/runtime.js +16 -0
- package/dist/core/serializer.d.ts +7 -0
- package/dist/core/serializer.js +31 -0
- package/dist/core/server.d.ts +10 -0
- package/dist/core/server.js +84 -0
- package/dist/core/session.d.ts +16 -0
- package/dist/core/session.js +35 -0
- package/dist/core/workspace-binding.d.ts +15 -0
- package/dist/core/workspace-binding.js +41 -0
- package/dist/core/workspace.d.ts +2 -0
- package/dist/core/workspace.js +121 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +97 -0
- package/dist/types/api.d.ts +61 -0
- package/dist/types/api.js +2 -0
- package/dist/types/auth.d.ts +36 -0
- package/dist/types/auth.js +2 -0
- package/dist/types/config.d.ts +58 -0
- package/dist/types/config.js +2 -0
- package/dist/types/machine.d.ts +49 -0
- package/dist/types/machine.js +2 -0
- package/dist/types/snapshot.d.ts +15 -0
- package/dist/types/snapshot.js +2 -0
- package/dist/types/workspace.d.ts +44 -0
- package/dist/types/workspace.js +2 -0
- package/package.json +61 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { CALLBACK_TIMEOUT_MS, CLI_CLIENT_ID } from "./constants.js";
|
|
4
|
+
import { AuthError } from "./errors.js";
|
|
5
|
+
import { createCallbackServer } from "./server.js";
|
|
6
|
+
function buildLoginUrl(profile, callbackUrl, state) {
|
|
7
|
+
const url = new URL("/cli/login", profile.appBaseUrl);
|
|
8
|
+
url.searchParams.set("redirect_uri", callbackUrl);
|
|
9
|
+
url.searchParams.set("state", state);
|
|
10
|
+
return url.toString();
|
|
11
|
+
}
|
|
12
|
+
export function generateLoginState() {
|
|
13
|
+
return randomBytes(24).toString("hex");
|
|
14
|
+
}
|
|
15
|
+
export async function openBrowser(url) {
|
|
16
|
+
const platform = process.platform;
|
|
17
|
+
const command = platform === "darwin"
|
|
18
|
+
? ["open", url]
|
|
19
|
+
: platform === "win32"
|
|
20
|
+
? ["cmd", "/c", "start", "", url]
|
|
21
|
+
: ["xdg-open", url];
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const child = spawn(command[0], command.slice(1), {
|
|
24
|
+
detached: platform !== "win32",
|
|
25
|
+
stdio: "ignore",
|
|
26
|
+
});
|
|
27
|
+
child.once("error", () => resolve(false));
|
|
28
|
+
child.once("spawn", () => {
|
|
29
|
+
child.unref();
|
|
30
|
+
resolve(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function runLoginFlow(params) {
|
|
35
|
+
const state = generateLoginState();
|
|
36
|
+
const callbackServer = await createCallbackServer(params.config.callbackHost, params.config.callbackPort, params.config.callbackPath, state);
|
|
37
|
+
try {
|
|
38
|
+
const loginUrl = buildLoginUrl(params.profile, callbackServer.callbackUrl, state);
|
|
39
|
+
params.logger.info("abrindo navegador para autenticação...");
|
|
40
|
+
const opened = await openBrowser(loginUrl);
|
|
41
|
+
if (!opened) {
|
|
42
|
+
params.logger.warn("não foi possível abrir o navegador automaticamente.");
|
|
43
|
+
process.stdout.write(`${loginUrl}\n`);
|
|
44
|
+
}
|
|
45
|
+
params.logger.info("aguardando confirmação...");
|
|
46
|
+
const callback = await callbackServer.waitForCallback(CALLBACK_TIMEOUT_MS);
|
|
47
|
+
const payload = {
|
|
48
|
+
code: callback.code,
|
|
49
|
+
client: CLI_CLIENT_ID,
|
|
50
|
+
device: params.machine,
|
|
51
|
+
runtime: params.runtime,
|
|
52
|
+
};
|
|
53
|
+
const tokenResponse = await params.apiClient.exchangeToken(payload);
|
|
54
|
+
if (!tokenResponse.access_token || !tokenResponse.refresh_token) {
|
|
55
|
+
throw new AuthError("A API retornou uma sessão inválida.");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
accessToken: tokenResponse.access_token,
|
|
59
|
+
refreshToken: tokenResponse.refresh_token,
|
|
60
|
+
workspace: tokenResponse.workspace,
|
|
61
|
+
tokenType: tokenResponse.token_type,
|
|
62
|
+
expiresIn: tokenResponse.expires_in,
|
|
63
|
+
obtainedAt: new Date().toISOString(),
|
|
64
|
+
expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000).toISOString(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await callbackServer.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedArgs {
|
|
2
|
+
positionals: string[];
|
|
3
|
+
options: Record<string, string | boolean>;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseArgs(args: string[]): ParsedArgs;
|
|
6
|
+
export declare function getStringOption(parsed: ParsedArgs, key: string): string | undefined;
|
|
7
|
+
export declare function getBooleanOption(parsed: ParsedArgs, key: string): boolean;
|
|
8
|
+
export declare function requirePositional(parsed: ParsedArgs, index: number, message: string): string;
|
|
9
|
+
export declare function parseJsonOption(value: string | undefined, fallback?: Record<string, unknown>): Record<string, unknown>;
|
package/dist/core/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.js";
|
|
2
|
+
export function parseArgs(args) {
|
|
3
|
+
const positionals = [];
|
|
4
|
+
const options = {};
|
|
5
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6
|
+
const current = args[index];
|
|
7
|
+
if (!current.startsWith("--")) {
|
|
8
|
+
positionals.push(current);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const option = current.slice(2);
|
|
12
|
+
const [key, inlineValue] = option.split("=", 2);
|
|
13
|
+
if (inlineValue !== undefined) {
|
|
14
|
+
options[key] = inlineValue;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const next = args[index + 1];
|
|
18
|
+
if (!next || next.startsWith("--")) {
|
|
19
|
+
options[key] = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
options[key] = next;
|
|
23
|
+
index += 1;
|
|
24
|
+
}
|
|
25
|
+
return { positionals, options };
|
|
26
|
+
}
|
|
27
|
+
export function getStringOption(parsed, key) {
|
|
28
|
+
const value = parsed.options[key];
|
|
29
|
+
return typeof value === "string" ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
export function getBooleanOption(parsed, key) {
|
|
32
|
+
const value = parsed.options[key];
|
|
33
|
+
if (typeof value === "boolean") {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === "string") {
|
|
37
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
export function requirePositional(parsed, index, message) {
|
|
42
|
+
const value = parsed.positionals[index];
|
|
43
|
+
if (!value) {
|
|
44
|
+
throw new ValidationError(message);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
export function parseJsonOption(value, fallback = {}) {
|
|
49
|
+
if (!value) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(value);
|
|
54
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
throw new ValidationError("O payload JSON deve ser um objeto.");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof ValidationError) {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
throw new ValidationError("Falha ao interpretar o payload JSON.", error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AuthSession } from "../types/auth.js";
|
|
2
|
+
import type { CliConfig, CliProfileConfig, CliProfileState, CliState, PendingQueueItem, QueueStore, WorkspaceBinding, WorkspaceBindingStore } from "../types/config.js";
|
|
3
|
+
import type { DeviceIdentity } from "../types/machine.js";
|
|
4
|
+
export declare function getActiveProfileName(config: CliConfig): string;
|
|
5
|
+
export declare function getActiveProfile(config: CliConfig): CliProfileConfig;
|
|
6
|
+
export declare function getProfileState(state: CliState, profile: string): CliProfileState;
|
|
7
|
+
export declare function loadConfig(): Promise<CliConfig>;
|
|
8
|
+
export declare function saveConfig(config: CliConfig): Promise<void>;
|
|
9
|
+
export declare function upsertProfile(profileName: string, patch: Partial<CliProfileConfig>): Promise<CliConfig>;
|
|
10
|
+
export declare function setActiveProfile(profileName: string): Promise<CliConfig>;
|
|
11
|
+
export declare function loadSession(profileName?: string): Promise<AuthSession | null>;
|
|
12
|
+
export declare function saveSession(session: AuthSession, profileName?: string): Promise<void>;
|
|
13
|
+
export declare function deleteSession(profileName?: string): Promise<void>;
|
|
14
|
+
export declare function loadState(): Promise<CliState>;
|
|
15
|
+
export declare function saveState(state: CliState): Promise<void>;
|
|
16
|
+
export declare function updateProfileState(profileName: string, patch: Partial<CliProfileState>): Promise<CliState>;
|
|
17
|
+
export declare function loadQueue(): Promise<QueueStore>;
|
|
18
|
+
export declare function saveQueue(queue: QueueStore): Promise<void>;
|
|
19
|
+
export declare function enqueueItem(item: PendingQueueItem): Promise<void>;
|
|
20
|
+
export declare function removeQueueItem(itemId: string): Promise<void>;
|
|
21
|
+
export declare function replaceQueueItem(item: PendingQueueItem): Promise<void>;
|
|
22
|
+
export declare function loadWorkspaceBindings(): Promise<WorkspaceBindingStore>;
|
|
23
|
+
export declare function saveWorkspaceBindings(store: WorkspaceBindingStore): Promise<void>;
|
|
24
|
+
export declare function bindWorkspace(binding: WorkspaceBinding): Promise<void>;
|
|
25
|
+
export declare function unbindWorkspace(rootPath: string): Promise<void>;
|
|
26
|
+
export declare function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity>;
|
|
27
|
+
export declare function ensureSessionAvailable(profileName?: string): Promise<AuthSession>;
|
|
28
|
+
export declare function getConfigDirectoryPath(): Promise<string>;
|
|
29
|
+
export declare function getSnapshotsDirectoryPath(): Promise<string>;
|
|
30
|
+
export declare function persistSnapshot(profileName: string, hash: string, payload: unknown): Promise<string>;
|
|
31
|
+
export declare function configDirectoryExists(): Promise<boolean>;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { CONFIG_DIRECTORY_NAME, CONFIG_FILE_NAME, DEFAULT_CONFIG, DEFAULT_PROFILE, DEFAULT_PROFILE_NAME, DEFAULT_PROFILE_STATE, DEFAULT_QUEUE, DEFAULT_STATE, DEFAULT_WORKSPACE_BINDINGS, DEVICE_FILE_NAME, QUEUE_FILE_NAME, SESSION_FILE_NAME, SNAPSHOTS_DIRECTORY_NAME, STATE_FILE_NAME, WORKSPACE_BINDINGS_FILE_NAME, } from "./constants.js";
|
|
6
|
+
import { ConfigError } from "./errors.js";
|
|
7
|
+
function configDirectory() {
|
|
8
|
+
return path.join(os.homedir(), CONFIG_DIRECTORY_NAME);
|
|
9
|
+
}
|
|
10
|
+
function configFilePath(name) {
|
|
11
|
+
return path.join(configDirectory(), name);
|
|
12
|
+
}
|
|
13
|
+
function snapshotsDirectoryPath() {
|
|
14
|
+
return path.join(configDirectory(), SNAPSHOTS_DIRECTORY_NAME);
|
|
15
|
+
}
|
|
16
|
+
async function ensureDirectory(directoryPath) {
|
|
17
|
+
await mkdir(directoryPath, { recursive: true, mode: 0o700 });
|
|
18
|
+
}
|
|
19
|
+
async function ensureConfigDirectory() {
|
|
20
|
+
await ensureDirectory(configDirectory());
|
|
21
|
+
}
|
|
22
|
+
async function readJsonFile(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
const content = await readFile(filePath, "utf8");
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code === "ENOENT") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
throw new ConfigError(`Falha ao ler ${filePath}`, error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function writeJsonFile(filePath, payload, mode = 0o600) {
|
|
35
|
+
await ensureConfigDirectory();
|
|
36
|
+
const tempPath = `${filePath}.${randomUUID()}.tmp`;
|
|
37
|
+
await writeFile(tempPath, `${JSON.stringify(payload, null, 2)}\n`, { mode });
|
|
38
|
+
await chmod(tempPath, mode);
|
|
39
|
+
await rename(tempPath, filePath);
|
|
40
|
+
}
|
|
41
|
+
function normalizeProfileConfig(name, partial) {
|
|
42
|
+
return {
|
|
43
|
+
...DEFAULT_PROFILE,
|
|
44
|
+
...partial,
|
|
45
|
+
name,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function getActiveProfileName(config) {
|
|
49
|
+
return config.activeProfile in config.profiles ? config.activeProfile : DEFAULT_PROFILE_NAME;
|
|
50
|
+
}
|
|
51
|
+
export function getActiveProfile(config) {
|
|
52
|
+
const name = getActiveProfileName(config);
|
|
53
|
+
return normalizeProfileConfig(name, config.profiles[name]);
|
|
54
|
+
}
|
|
55
|
+
export function getProfileState(state, profile) {
|
|
56
|
+
return {
|
|
57
|
+
...DEFAULT_PROFILE_STATE,
|
|
58
|
+
...(state.profiles[profile] ?? {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function loadConfig() {
|
|
62
|
+
const filePath = configFilePath(CONFIG_FILE_NAME);
|
|
63
|
+
const stored = await readJsonFile(filePath);
|
|
64
|
+
const profiles = Object.entries(stored?.profiles ?? DEFAULT_CONFIG.profiles).reduce((accumulator, [name, value]) => {
|
|
65
|
+
accumulator[name] = normalizeProfileConfig(name, value);
|
|
66
|
+
return accumulator;
|
|
67
|
+
}, {});
|
|
68
|
+
if (!profiles[DEFAULT_PROFILE_NAME]) {
|
|
69
|
+
profiles[DEFAULT_PROFILE_NAME] = DEFAULT_PROFILE;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...DEFAULT_CONFIG,
|
|
73
|
+
...stored,
|
|
74
|
+
profiles,
|
|
75
|
+
activeProfile: stored?.activeProfile && profiles[stored.activeProfile] ? stored.activeProfile : DEFAULT_PROFILE_NAME,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export async function saveConfig(config) {
|
|
79
|
+
await writeJsonFile(configFilePath(CONFIG_FILE_NAME), config);
|
|
80
|
+
}
|
|
81
|
+
export async function upsertProfile(profileName, patch) {
|
|
82
|
+
const config = await loadConfig();
|
|
83
|
+
const next = {
|
|
84
|
+
...config,
|
|
85
|
+
profiles: {
|
|
86
|
+
...config.profiles,
|
|
87
|
+
[profileName]: normalizeProfileConfig(profileName, {
|
|
88
|
+
...(config.profiles[profileName] ?? DEFAULT_PROFILE),
|
|
89
|
+
...patch,
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
await saveConfig(next);
|
|
94
|
+
return next;
|
|
95
|
+
}
|
|
96
|
+
export async function setActiveProfile(profileName) {
|
|
97
|
+
const config = await loadConfig();
|
|
98
|
+
if (!config.profiles[profileName]) {
|
|
99
|
+
throw new ConfigError(`Perfil ${profileName} não existe.`);
|
|
100
|
+
}
|
|
101
|
+
const next = { ...config, activeProfile: profileName };
|
|
102
|
+
await saveConfig(next);
|
|
103
|
+
const state = await loadState();
|
|
104
|
+
await saveState({ ...state, activeProfile: profileName });
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
async function loadSessionStore() {
|
|
108
|
+
return (await readJsonFile(configFilePath(SESSION_FILE_NAME))) ?? { profiles: {} };
|
|
109
|
+
}
|
|
110
|
+
async function saveSessionStore(store) {
|
|
111
|
+
await writeJsonFile(configFilePath(SESSION_FILE_NAME), store);
|
|
112
|
+
}
|
|
113
|
+
export async function loadSession(profileName) {
|
|
114
|
+
const config = await loadConfig();
|
|
115
|
+
const store = await loadSessionStore();
|
|
116
|
+
return store.profiles[profileName ?? getActiveProfileName(config)] ?? null;
|
|
117
|
+
}
|
|
118
|
+
export async function saveSession(session, profileName) {
|
|
119
|
+
const config = await loadConfig();
|
|
120
|
+
const activeProfile = profileName ?? getActiveProfileName(config);
|
|
121
|
+
const store = await loadSessionStore();
|
|
122
|
+
store.profiles[activeProfile] = session;
|
|
123
|
+
await saveSessionStore(store);
|
|
124
|
+
}
|
|
125
|
+
export async function deleteSession(profileName) {
|
|
126
|
+
const config = await loadConfig();
|
|
127
|
+
const activeProfile = profileName ?? getActiveProfileName(config);
|
|
128
|
+
const store = await loadSessionStore();
|
|
129
|
+
delete store.profiles[activeProfile];
|
|
130
|
+
await saveSessionStore(store);
|
|
131
|
+
}
|
|
132
|
+
export async function loadState() {
|
|
133
|
+
const stored = await readJsonFile(configFilePath(STATE_FILE_NAME));
|
|
134
|
+
const profiles = Object.entries(stored?.profiles ?? DEFAULT_STATE.profiles).reduce((accumulator, [name, value]) => {
|
|
135
|
+
accumulator[name] = {
|
|
136
|
+
...DEFAULT_PROFILE_STATE,
|
|
137
|
+
...value,
|
|
138
|
+
};
|
|
139
|
+
return accumulator;
|
|
140
|
+
}, {});
|
|
141
|
+
if (!profiles[DEFAULT_PROFILE_NAME]) {
|
|
142
|
+
profiles[DEFAULT_PROFILE_NAME] = DEFAULT_PROFILE_STATE;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
activeProfile: stored?.activeProfile ?? DEFAULT_PROFILE_NAME,
|
|
146
|
+
profiles,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export async function saveState(state) {
|
|
150
|
+
await writeJsonFile(configFilePath(STATE_FILE_NAME), state);
|
|
151
|
+
}
|
|
152
|
+
export async function updateProfileState(profileName, patch) {
|
|
153
|
+
const state = await loadState();
|
|
154
|
+
const next = {
|
|
155
|
+
...state,
|
|
156
|
+
profiles: {
|
|
157
|
+
...state.profiles,
|
|
158
|
+
[profileName]: {
|
|
159
|
+
...getProfileState(state, profileName),
|
|
160
|
+
...patch,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
await saveState(next);
|
|
165
|
+
return next;
|
|
166
|
+
}
|
|
167
|
+
export async function loadQueue() {
|
|
168
|
+
return (await readJsonFile(configFilePath(QUEUE_FILE_NAME))) ?? DEFAULT_QUEUE;
|
|
169
|
+
}
|
|
170
|
+
export async function saveQueue(queue) {
|
|
171
|
+
await writeJsonFile(configFilePath(QUEUE_FILE_NAME), queue);
|
|
172
|
+
}
|
|
173
|
+
export async function enqueueItem(item) {
|
|
174
|
+
const queue = await loadQueue();
|
|
175
|
+
queue.items.push(item);
|
|
176
|
+
await saveQueue(queue);
|
|
177
|
+
}
|
|
178
|
+
export async function removeQueueItem(itemId) {
|
|
179
|
+
const queue = await loadQueue();
|
|
180
|
+
queue.items = queue.items.filter((item) => item.id !== itemId);
|
|
181
|
+
await saveQueue(queue);
|
|
182
|
+
}
|
|
183
|
+
export async function replaceQueueItem(item) {
|
|
184
|
+
const queue = await loadQueue();
|
|
185
|
+
queue.items = queue.items.map((current) => current.id === item.id ? item : current);
|
|
186
|
+
await saveQueue(queue);
|
|
187
|
+
}
|
|
188
|
+
export async function loadWorkspaceBindings() {
|
|
189
|
+
return (await readJsonFile(configFilePath(WORKSPACE_BINDINGS_FILE_NAME))) ?? DEFAULT_WORKSPACE_BINDINGS;
|
|
190
|
+
}
|
|
191
|
+
export async function saveWorkspaceBindings(store) {
|
|
192
|
+
await writeJsonFile(configFilePath(WORKSPACE_BINDINGS_FILE_NAME), store);
|
|
193
|
+
}
|
|
194
|
+
export async function bindWorkspace(binding) {
|
|
195
|
+
const store = await loadWorkspaceBindings();
|
|
196
|
+
store.bindings[binding.rootPath] = binding;
|
|
197
|
+
await saveWorkspaceBindings(store);
|
|
198
|
+
}
|
|
199
|
+
export async function unbindWorkspace(rootPath) {
|
|
200
|
+
const store = await loadWorkspaceBindings();
|
|
201
|
+
delete store.bindings[rootPath];
|
|
202
|
+
await saveWorkspaceBindings(store);
|
|
203
|
+
}
|
|
204
|
+
export async function loadOrCreateDeviceIdentity() {
|
|
205
|
+
const filePath = configFilePath(DEVICE_FILE_NAME);
|
|
206
|
+
const now = new Date().toISOString();
|
|
207
|
+
const existing = await readJsonFile(filePath);
|
|
208
|
+
if (existing) {
|
|
209
|
+
const updated = { ...existing, lastSeenAt: now };
|
|
210
|
+
await writeJsonFile(filePath, updated);
|
|
211
|
+
return updated;
|
|
212
|
+
}
|
|
213
|
+
const created = {
|
|
214
|
+
machineId: `vp-${randomUUID().replaceAll("-", "").slice(0, 20)}`,
|
|
215
|
+
firstSeenAt: now,
|
|
216
|
+
lastSeenAt: now,
|
|
217
|
+
};
|
|
218
|
+
await writeJsonFile(filePath, created);
|
|
219
|
+
return created;
|
|
220
|
+
}
|
|
221
|
+
export async function ensureSessionAvailable(profileName) {
|
|
222
|
+
const session = await loadSession(profileName);
|
|
223
|
+
if (!session) {
|
|
224
|
+
throw new ConfigError("Nenhuma sessão local encontrada. Execute `vp login` primeiro.");
|
|
225
|
+
}
|
|
226
|
+
return session;
|
|
227
|
+
}
|
|
228
|
+
export async function getConfigDirectoryPath() {
|
|
229
|
+
await ensureConfigDirectory();
|
|
230
|
+
return configDirectory();
|
|
231
|
+
}
|
|
232
|
+
export async function getSnapshotsDirectoryPath() {
|
|
233
|
+
await ensureDirectory(snapshotsDirectoryPath());
|
|
234
|
+
return snapshotsDirectoryPath();
|
|
235
|
+
}
|
|
236
|
+
export async function persistSnapshot(profileName, hash, payload) {
|
|
237
|
+
const directory = await getSnapshotsDirectoryPath();
|
|
238
|
+
const filePath = path.join(directory, `${profileName}-${new Date().toISOString().replaceAll(":", "-")}-${hash.slice(0, 12)}.json`);
|
|
239
|
+
await writeJsonFile(filePath, payload);
|
|
240
|
+
await pruneStoredSnapshots(profileName);
|
|
241
|
+
return filePath;
|
|
242
|
+
}
|
|
243
|
+
async function pruneStoredSnapshots(profileName) {
|
|
244
|
+
const config = await loadConfig();
|
|
245
|
+
const directory = await getSnapshotsDirectoryPath();
|
|
246
|
+
const entries = (await readdir(directory))
|
|
247
|
+
.filter((entry) => entry.startsWith(`${profileName}-`) && entry.endsWith(".json"))
|
|
248
|
+
.sort()
|
|
249
|
+
.reverse();
|
|
250
|
+
for (const entry of entries.slice(config.snapshotStoreLimit)) {
|
|
251
|
+
await rm(path.join(directory, entry), { force: true });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export async function configDirectoryExists() {
|
|
255
|
+
try {
|
|
256
|
+
await stat(configDirectory());
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CliConfig, CliProfileConfig, CliProfileState, CliState, QueueStore, WorkspaceBindingStore } from "../types/config.js";
|
|
2
|
+
export declare const CLI_NAME = "vp";
|
|
3
|
+
export declare const CLI_CLIENT_ID = "vp-cli";
|
|
4
|
+
export declare const PRODUCT_NAME = "VectorPlane";
|
|
5
|
+
export declare const CONFIG_DIRECTORY_NAME = ".vectorplane";
|
|
6
|
+
export declare const SESSION_FILE_NAME = "session.json";
|
|
7
|
+
export declare const CONFIG_FILE_NAME = "config.json";
|
|
8
|
+
export declare const DEVICE_FILE_NAME = "device.json";
|
|
9
|
+
export declare const STATE_FILE_NAME = "state.json";
|
|
10
|
+
export declare const QUEUE_FILE_NAME = "queue.json";
|
|
11
|
+
export declare const WORKSPACE_BINDINGS_FILE_NAME = "workspaces.json";
|
|
12
|
+
export declare const SNAPSHOTS_DIRECTORY_NAME = "snapshots";
|
|
13
|
+
export declare const DEFAULT_PROFILE_NAME = "default";
|
|
14
|
+
export declare const DEFAULT_PROFILE: CliProfileConfig;
|
|
15
|
+
export declare const DEFAULT_CONFIG: CliConfig;
|
|
16
|
+
export declare const DEFAULT_PROFILE_STATE: CliProfileState;
|
|
17
|
+
export declare const DEFAULT_STATE: CliState;
|
|
18
|
+
export declare const DEFAULT_QUEUE: QueueStore;
|
|
19
|
+
export declare const DEFAULT_WORKSPACE_BINDINGS: WorkspaceBindingStore;
|
|
20
|
+
export declare const CALLBACK_TIMEOUT_MS = 120000;
|
|
21
|
+
export declare const SENSITIVE_KEYS: string[];
|
|
22
|
+
export declare const SAFE_ENV_KEYS: string[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const CLI_NAME = "vp";
|
|
2
|
+
export const CLI_CLIENT_ID = "vp-cli";
|
|
3
|
+
export const PRODUCT_NAME = "VectorPlane";
|
|
4
|
+
export const CONFIG_DIRECTORY_NAME = ".vectorplane";
|
|
5
|
+
export const SESSION_FILE_NAME = "session.json";
|
|
6
|
+
export const CONFIG_FILE_NAME = "config.json";
|
|
7
|
+
export const DEVICE_FILE_NAME = "device.json";
|
|
8
|
+
export const STATE_FILE_NAME = "state.json";
|
|
9
|
+
export const QUEUE_FILE_NAME = "queue.json";
|
|
10
|
+
export const WORKSPACE_BINDINGS_FILE_NAME = "workspaces.json";
|
|
11
|
+
export const SNAPSHOTS_DIRECTORY_NAME = "snapshots";
|
|
12
|
+
export const DEFAULT_PROFILE_NAME = "default";
|
|
13
|
+
export const DEFAULT_PROFILE = {
|
|
14
|
+
name: DEFAULT_PROFILE_NAME,
|
|
15
|
+
apiBaseUrl: "https://api.vectorplane.io",
|
|
16
|
+
appBaseUrl: "https://app.vectorplane.io",
|
|
17
|
+
workspace: null,
|
|
18
|
+
orgId: null,
|
|
19
|
+
environment: "production",
|
|
20
|
+
};
|
|
21
|
+
export const DEFAULT_CONFIG = {
|
|
22
|
+
activeProfile: DEFAULT_PROFILE_NAME,
|
|
23
|
+
profiles: {
|
|
24
|
+
[DEFAULT_PROFILE_NAME]: DEFAULT_PROFILE,
|
|
25
|
+
},
|
|
26
|
+
callbackHost: "127.0.0.1",
|
|
27
|
+
callbackPort: 4217,
|
|
28
|
+
callbackPath: "/callback",
|
|
29
|
+
requestTimeoutMs: 15_000,
|
|
30
|
+
publicIpLookupTimeoutMs: 2_000,
|
|
31
|
+
debug: false,
|
|
32
|
+
telemetryEnabled: false,
|
|
33
|
+
queueMaxRetries: 5,
|
|
34
|
+
snapshotStoreLimit: 20,
|
|
35
|
+
};
|
|
36
|
+
export const DEFAULT_PROFILE_STATE = {
|
|
37
|
+
lastSyncAt: null,
|
|
38
|
+
lastWorkspace: null,
|
|
39
|
+
lastCommand: null,
|
|
40
|
+
lastSyncHash: null,
|
|
41
|
+
lastSyncStatus: null,
|
|
42
|
+
lastError: null,
|
|
43
|
+
lastSnapshotPath: null,
|
|
44
|
+
lastSessionId: null,
|
|
45
|
+
};
|
|
46
|
+
export const DEFAULT_STATE = {
|
|
47
|
+
activeProfile: DEFAULT_PROFILE_NAME,
|
|
48
|
+
profiles: {
|
|
49
|
+
[DEFAULT_PROFILE_NAME]: DEFAULT_PROFILE_STATE,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export const DEFAULT_QUEUE = {
|
|
53
|
+
items: [],
|
|
54
|
+
};
|
|
55
|
+
export const DEFAULT_WORKSPACE_BINDINGS = {
|
|
56
|
+
bindings: {},
|
|
57
|
+
};
|
|
58
|
+
export const CALLBACK_TIMEOUT_MS = 120_000;
|
|
59
|
+
export const SENSITIVE_KEYS = ["token", "authorization", "refresh", "secret", "password", "cookie"];
|
|
60
|
+
export const SAFE_ENV_KEYS = [
|
|
61
|
+
"SHELL",
|
|
62
|
+
"TERM",
|
|
63
|
+
"LANG",
|
|
64
|
+
"LC_ALL",
|
|
65
|
+
"LC_MESSAGES",
|
|
66
|
+
"COLORTERM",
|
|
67
|
+
"EDITOR",
|
|
68
|
+
"VISUAL",
|
|
69
|
+
"PWD",
|
|
70
|
+
"HOME",
|
|
71
|
+
"USER",
|
|
72
|
+
"USERNAME",
|
|
73
|
+
"PATH",
|
|
74
|
+
"WSL_DISTRO_NAME",
|
|
75
|
+
"CI",
|
|
76
|
+
"CODEX_ENV",
|
|
77
|
+
];
|
|
78
|
+
//# sourceMappingURL=constants.js.map
|
package/dist/core/env.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SAFE_ENV_KEYS } from "./constants.js";
|
|
2
|
+
export function collectSafeEnvironmentVariables() {
|
|
3
|
+
return SAFE_ENV_KEYS
|
|
4
|
+
.map((key) => {
|
|
5
|
+
const value = process.env[key];
|
|
6
|
+
return value ? { key, value } : null;
|
|
7
|
+
})
|
|
8
|
+
.filter((entry) => entry !== null);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class CLIError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly cause?: unknown | undefined;
|
|
4
|
+
constructor(message: string, code?: string, cause?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare class AuthError extends CLIError {
|
|
7
|
+
constructor(message: string, cause?: unknown);
|
|
8
|
+
}
|
|
9
|
+
export declare class ConfigError extends CLIError {
|
|
10
|
+
constructor(message: string, cause?: unknown);
|
|
11
|
+
}
|
|
12
|
+
export declare class ApiError extends CLIError {
|
|
13
|
+
readonly status?: number | undefined;
|
|
14
|
+
constructor(message: string, status?: number | undefined, cause?: unknown);
|
|
15
|
+
}
|
|
16
|
+
export declare class ValidationError extends CLIError {
|
|
17
|
+
constructor(message: string, cause?: unknown);
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class CLIError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
cause;
|
|
4
|
+
constructor(message, code = "cli_error", cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.cause = cause;
|
|
8
|
+
this.name = "CLIError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class AuthError extends CLIError {
|
|
12
|
+
constructor(message, cause) {
|
|
13
|
+
super(message, "auth_error", cause);
|
|
14
|
+
this.name = "AuthError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class ConfigError extends CLIError {
|
|
18
|
+
constructor(message, cause) {
|
|
19
|
+
super(message, "config_error", cause);
|
|
20
|
+
this.name = "ConfigError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class ApiError extends CLIError {
|
|
24
|
+
status;
|
|
25
|
+
constructor(message, status, cause) {
|
|
26
|
+
super(message, "api_error", cause);
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.name = "ApiError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ValidationError extends CLIError {
|
|
32
|
+
constructor(message, cause) {
|
|
33
|
+
super(message, "validation_error", cause);
|
|
34
|
+
this.name = "ValidationError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.js.map
|