@aopslabs/aops 0.2.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 +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import fsSync from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { getAopsCliConfigFilePath } from './config.js';
|
|
7
|
+
import { decryptWithPassword, encryptWithPassword, } from './password-crypto.js';
|
|
8
|
+
const STORE_FILENAME = 'chatv3-sessions.json';
|
|
9
|
+
const SECRET_FILENAME = 'chatv3-session.key';
|
|
10
|
+
function normalizeNonEmpty(value) {
|
|
11
|
+
if (typeof value !== 'string')
|
|
12
|
+
return undefined;
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
return trimmed ? trimmed : undefined;
|
|
15
|
+
}
|
|
16
|
+
function baseDirFromConfig() {
|
|
17
|
+
if (normalizeNonEmpty(process.env.AOPS_CLI_CONFIG_PATH) || normalizeNonEmpty(process.env.AGENT_OPS_CONFIG_PATH)) {
|
|
18
|
+
return path.dirname(getAopsCliConfigFilePath());
|
|
19
|
+
}
|
|
20
|
+
return path.join(os.homedir(), '.aops');
|
|
21
|
+
}
|
|
22
|
+
export function resolveChatv3SessionStorePath(explicitPath) {
|
|
23
|
+
return path.resolve(normalizeNonEmpty(explicitPath) ??
|
|
24
|
+
normalizeNonEmpty(process.env.AOPS_CHATV3_SESSION_STORE_PATH) ??
|
|
25
|
+
path.join(baseDirFromConfig(), STORE_FILENAME));
|
|
26
|
+
}
|
|
27
|
+
function resolveChatv3SessionSecretPath() {
|
|
28
|
+
return path.join(baseDirFromConfig(), SECRET_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
async function ensureDir(filePath) {
|
|
31
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
async function enforceOwnerOnlyFileMode(filePath) {
|
|
34
|
+
if (process.platform === 'win32')
|
|
35
|
+
return;
|
|
36
|
+
await fs.chmod(filePath, 0o600);
|
|
37
|
+
}
|
|
38
|
+
async function readStore(storePath) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await fs.readFile(storePath, 'utf8');
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
sessions: parsed && typeof parsed.sessions === 'object' && parsed.sessions ? parsed.sessions : {},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code === 'ENOENT') {
|
|
49
|
+
return { version: 1, sessions: {} };
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function writeStore(storePath, store) {
|
|
55
|
+
await ensureDir(storePath);
|
|
56
|
+
await fs.writeFile(storePath, `${JSON.stringify(store, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
57
|
+
await enforceOwnerOnlyFileMode(storePath);
|
|
58
|
+
}
|
|
59
|
+
async function resolveSessionSecret() {
|
|
60
|
+
const envSecret = normalizeNonEmpty(process.env.AOPS_CHATV3_SESSION_SECRET) ??
|
|
61
|
+
normalizeNonEmpty(process.env.AOPS_MCP_TOKEN_SECRET) ??
|
|
62
|
+
normalizeNonEmpty(process.env.AOPS_MCP_CONFIG_SECRET);
|
|
63
|
+
if (envSecret)
|
|
64
|
+
return envSecret;
|
|
65
|
+
const secretPath = resolveChatv3SessionSecretPath();
|
|
66
|
+
try {
|
|
67
|
+
const raw = await fs.readFile(secretPath, 'utf8');
|
|
68
|
+
const secret = normalizeNonEmpty(raw);
|
|
69
|
+
if (secret) {
|
|
70
|
+
await enforceOwnerOnlyFileMode(secretPath);
|
|
71
|
+
return secret;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code !== 'ENOENT')
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
const secret = crypto.randomBytes(32).toString('base64');
|
|
79
|
+
await ensureDir(secretPath);
|
|
80
|
+
await fs.writeFile(secretPath, `${secret}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
81
|
+
await enforceOwnerOnlyFileMode(secretPath);
|
|
82
|
+
return secret;
|
|
83
|
+
}
|
|
84
|
+
function encodeEncryptedPayload(payload) {
|
|
85
|
+
return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
|
|
86
|
+
}
|
|
87
|
+
function decodeEncryptedPayload(value) {
|
|
88
|
+
return JSON.parse(Buffer.from(value, 'base64').toString('utf8'));
|
|
89
|
+
}
|
|
90
|
+
async function encryptSecret(value) {
|
|
91
|
+
const { payload } = await encryptWithPassword(value, await resolveSessionSecret());
|
|
92
|
+
return encodeEncryptedPayload(payload);
|
|
93
|
+
}
|
|
94
|
+
async function decryptSecret(value) {
|
|
95
|
+
return decryptWithPassword(decodeEncryptedPayload(value), await resolveSessionSecret());
|
|
96
|
+
}
|
|
97
|
+
async function encryptEpochKeys(keys) {
|
|
98
|
+
if (!keys || keys.length === 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
return Promise.all(keys.map(async ({ rawEpochKey, ...rest }) => ({
|
|
101
|
+
...rest,
|
|
102
|
+
rawEpochKeyEnc: await encryptSecret(rawEpochKey),
|
|
103
|
+
})));
|
|
104
|
+
}
|
|
105
|
+
async function decryptEpochKeys(keys) {
|
|
106
|
+
if (!keys || keys.length === 0)
|
|
107
|
+
return undefined;
|
|
108
|
+
return Promise.all(keys.map(async ({ rawEpochKeyEnc, ...rest }) => ({
|
|
109
|
+
...rest,
|
|
110
|
+
rawEpochKey: await decryptSecret(rawEpochKeyEnc),
|
|
111
|
+
})));
|
|
112
|
+
}
|
|
113
|
+
export async function saveChatv3Session(input, secrets, options = {}) {
|
|
114
|
+
const storePath = resolveChatv3SessionStorePath(options.storePath);
|
|
115
|
+
const store = await readStore(storePath);
|
|
116
|
+
const existing = store.sessions[input.id];
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
const wrapSecretEnc = secrets.wrapSecret ? await encryptSecret(secrets.wrapSecret) : undefined;
|
|
119
|
+
const epochKeysEnc = await encryptEpochKeys(secrets.epochKeys);
|
|
120
|
+
const record = {
|
|
121
|
+
version: 1,
|
|
122
|
+
...input,
|
|
123
|
+
rooms: input.rooms,
|
|
124
|
+
createdAt: input.createdAt ?? existing?.createdAt ?? now,
|
|
125
|
+
updatedAt: input.updatedAt ?? now,
|
|
126
|
+
memberTokenEnc: await encryptSecret(secrets.memberToken),
|
|
127
|
+
...(wrapSecretEnc ? { wrapSecretEnc } : {}),
|
|
128
|
+
...(epochKeysEnc ? { epochKeysEnc } : {}),
|
|
129
|
+
};
|
|
130
|
+
store.sessions[input.id] = record;
|
|
131
|
+
await writeStore(storePath, store);
|
|
132
|
+
return record;
|
|
133
|
+
}
|
|
134
|
+
export async function loadChatv3Session(id, options = {}) {
|
|
135
|
+
const store = await readStore(resolveChatv3SessionStorePath(options.storePath));
|
|
136
|
+
const record = store.sessions[id];
|
|
137
|
+
if (!record)
|
|
138
|
+
throw new Error(`ChatV3 session not found: ${id}`);
|
|
139
|
+
return {
|
|
140
|
+
record,
|
|
141
|
+
secrets: {
|
|
142
|
+
memberToken: await decryptSecret(record.memberTokenEnc),
|
|
143
|
+
wrapSecret: record.wrapSecretEnc ? await decryptSecret(record.wrapSecretEnc) : undefined,
|
|
144
|
+
epochKeys: await decryptEpochKeys(record.epochKeysEnc),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export async function getChatv3SessionRecord(id, options = {}) {
|
|
149
|
+
const store = await readStore(resolveChatv3SessionStorePath(options.storePath));
|
|
150
|
+
return store.sessions[id] ?? null;
|
|
151
|
+
}
|
|
152
|
+
export async function listChatv3SessionRecords(options = {}) {
|
|
153
|
+
const store = await readStore(resolveChatv3SessionStorePath(options.storePath));
|
|
154
|
+
return Object.values(store.sessions).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
155
|
+
}
|
|
156
|
+
export async function deleteChatv3Session(id, options = {}) {
|
|
157
|
+
const storePath = resolveChatv3SessionStorePath(options.storePath);
|
|
158
|
+
const store = await readStore(storePath);
|
|
159
|
+
const existed = Boolean(store.sessions[id]);
|
|
160
|
+
delete store.sessions[id];
|
|
161
|
+
await writeStore(storePath, store);
|
|
162
|
+
return existed;
|
|
163
|
+
}
|
|
164
|
+
export function summarizeChatv3Session(record) {
|
|
165
|
+
return {
|
|
166
|
+
id: record.id,
|
|
167
|
+
serverBaseUrl: record.serverBaseUrl,
|
|
168
|
+
handle: record.handle,
|
|
169
|
+
channel: record.channel,
|
|
170
|
+
keyId: record.keyId,
|
|
171
|
+
activeRoomId: record.activeRoomId,
|
|
172
|
+
rooms: record.rooms,
|
|
173
|
+
createdAt: record.createdAt,
|
|
174
|
+
updatedAt: record.updatedAt,
|
|
175
|
+
encryptionMode: record.channel.encryptionMode ?? (record.wrapSecretEnc ? 'e2e' : 'server-encrypted'),
|
|
176
|
+
epochKeyCount: record.epochKeysEnc?.length ?? 0,
|
|
177
|
+
secretStorage: 'encrypted-at-rest',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export async function readChatv3SessionStoreRaw(options = {}) {
|
|
181
|
+
const storePath = resolveChatv3SessionStorePath(options.storePath);
|
|
182
|
+
if (!fsSync.existsSync(storePath))
|
|
183
|
+
return '';
|
|
184
|
+
return fs.readFile(storePath, 'utf8');
|
|
185
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function normalizeNonEmpty(value) {
|
|
2
|
+
if (typeof value !== 'string')
|
|
3
|
+
return undefined;
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
6
|
+
}
|
|
7
|
+
export function compactPayload(input) {
|
|
8
|
+
const payload = {};
|
|
9
|
+
Object.entries(input).forEach(([key, value]) => {
|
|
10
|
+
if (value === undefined)
|
|
11
|
+
return;
|
|
12
|
+
if (typeof value === 'string' && value.trim().length === 0)
|
|
13
|
+
return;
|
|
14
|
+
if (Array.isArray(value) && value.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
payload[key] = value;
|
|
17
|
+
});
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
export function printJsonResponse(params) {
|
|
21
|
+
console.log(JSON.stringify({
|
|
22
|
+
status: params.status,
|
|
23
|
+
result: params.result ?? null,
|
|
24
|
+
rawText: params.rawText,
|
|
25
|
+
}, null, 2));
|
|
26
|
+
}
|
|
27
|
+
function hasLongOption(cmd, flags) {
|
|
28
|
+
const longFlag = flags
|
|
29
|
+
.split(/[ ,|]+/)
|
|
30
|
+
.map((token) => token.trim())
|
|
31
|
+
.find((token) => token.startsWith('--'));
|
|
32
|
+
if (!longFlag)
|
|
33
|
+
return false;
|
|
34
|
+
return cmd.options.some((option) => option.long === longFlag);
|
|
35
|
+
}
|
|
36
|
+
function addOptionIfMissing(cmd, flags, description, parser) {
|
|
37
|
+
if (hasLongOption(cmd, flags))
|
|
38
|
+
return cmd;
|
|
39
|
+
if (parser) {
|
|
40
|
+
cmd.option(flags, description, parser);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
cmd.option(flags, description);
|
|
44
|
+
}
|
|
45
|
+
return cmd;
|
|
46
|
+
}
|
|
47
|
+
export function applyCommonOptions(cmd, config = {}) {
|
|
48
|
+
const { withAuth = true, withProject = false, withYes = true, withJson = true } = config;
|
|
49
|
+
addOptionIfMissing(cmd, '--api-base-url <url>', 'API base URL (default: AOPS_API_BASE_URL or http://localhost:5900)');
|
|
50
|
+
void withAuth;
|
|
51
|
+
addOptionIfMissing(cmd, '--timeout-ms <ms>', 'Request timeout in milliseconds', (v) => Number.parseInt(String(v), 10));
|
|
52
|
+
if (withProject) {
|
|
53
|
+
addOptionIfMissing(cmd, '--project-id <id>', 'Project id');
|
|
54
|
+
addOptionIfMissing(cmd, '--project-name <name>', 'Project name');
|
|
55
|
+
addOptionIfMissing(cmd, '--project-slug <slug>', 'Project slug (resolved server-side to a project id)');
|
|
56
|
+
}
|
|
57
|
+
if (withYes) {
|
|
58
|
+
addOptionIfMissing(cmd, '--yes', 'Non-interactive (fail if required args are missing)');
|
|
59
|
+
}
|
|
60
|
+
if (withJson) {
|
|
61
|
+
addOptionIfMissing(cmd, '--json', 'Output JSON only');
|
|
62
|
+
}
|
|
63
|
+
return cmd;
|
|
64
|
+
}
|
|
65
|
+
export function isUuidLike(value) {
|
|
66
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
67
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { compareCanonicalText } from '@aopslabs/artifact-trust-contracts';
|
|
6
|
+
const HOST_REGISTRATION_KIND = 'aops-host-registration';
|
|
7
|
+
const HOST_REGISTRATION_VERSION = '1';
|
|
8
|
+
const HOST_REGISTRATIONS_DIRNAME = 'host-registrations';
|
|
9
|
+
const AOPS_SQLITE_FILENAME = 'aops.aops.sqlite';
|
|
10
|
+
const AOPS_SERVER_ENV_FILENAME = 'aops.server.env';
|
|
11
|
+
const HOST_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error', 'fatal']);
|
|
12
|
+
const ALWAYS_PATH_KEYS = new Set([
|
|
13
|
+
'workspaceRoot', 'cliDistEntry', 'cliSrcEntry', 'cliCwd', 'dcmManifestPath',
|
|
14
|
+
'routesManifestPath', 'operationsManifestPath', 'metadataPath', 'manifestPath',
|
|
15
|
+
'registrationPath',
|
|
16
|
+
]);
|
|
17
|
+
const MAYBE_PATH_LIST_KEYS = new Set([
|
|
18
|
+
'allowlist', 'toolingModuleCandidates', 'pluginModuleCandidates',
|
|
19
|
+
'cliCommandCandidates', 'manifestFileCandidates',
|
|
20
|
+
]);
|
|
21
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
22
|
+
function nonEmpty(value) {
|
|
23
|
+
if (typeof value !== 'string')
|
|
24
|
+
return undefined;
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
return trimmed || undefined;
|
|
27
|
+
}
|
|
28
|
+
function dataRecord(value) {
|
|
29
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
30
|
+
}
|
|
31
|
+
function cloneJsonData(value, depth = 0) {
|
|
32
|
+
if (depth > 64)
|
|
33
|
+
throw new Error('community_host_registration_json_depth_exceeded');
|
|
34
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
37
|
+
return value;
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
if (value.length > 10_000)
|
|
40
|
+
throw new Error('community_host_registration_json_array_too_large');
|
|
41
|
+
return value.map((item) => cloneJsonData(item, depth + 1));
|
|
42
|
+
}
|
|
43
|
+
if (!value || typeof value !== 'object')
|
|
44
|
+
throw new Error('community_host_registration_json_value_invalid');
|
|
45
|
+
const output = Object.create(null);
|
|
46
|
+
for (const [key, item] of Object.entries(value)) {
|
|
47
|
+
if (FORBIDDEN_KEYS.has(key))
|
|
48
|
+
throw new Error('community_host_registration_json_key_forbidden:' + key);
|
|
49
|
+
output[key] = cloneJsonData(item, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
function normalizedDomain(value) {
|
|
54
|
+
const domain = nonEmpty(value)?.toLowerCase() ?? '';
|
|
55
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(domain))
|
|
56
|
+
throw new Error('host_registration_domain_invalid');
|
|
57
|
+
return domain;
|
|
58
|
+
}
|
|
59
|
+
function stringList(value) {
|
|
60
|
+
if (!Array.isArray(value))
|
|
61
|
+
return [];
|
|
62
|
+
return [...new Set(value.map(nonEmpty).filter((item) => Boolean(item)))];
|
|
63
|
+
}
|
|
64
|
+
function assertRegistrationEntries(manifest) {
|
|
65
|
+
const runtimeEnv = dataRecord(dataRecord(manifest.runtime).env);
|
|
66
|
+
const hasEntries = ['manifestProviders', 'plugins', 'sources'].some((key) => Array.isArray(manifest[key]) && manifest[key].length > 0) || Object.keys(runtimeEnv).length > 0;
|
|
67
|
+
if (!hasEntries)
|
|
68
|
+
throw new Error('host_registration_entries_required:' + manifest.domain);
|
|
69
|
+
}
|
|
70
|
+
export function normalizeHostRegistrationManifest(input) {
|
|
71
|
+
const raw = cloneJsonData(dataRecord(input));
|
|
72
|
+
const kind = nonEmpty(raw.kind) ?? HOST_REGISTRATION_KIND;
|
|
73
|
+
const registrationVersion = nonEmpty(raw.registrationVersion) ?? HOST_REGISTRATION_VERSION;
|
|
74
|
+
if (kind !== HOST_REGISTRATION_KIND)
|
|
75
|
+
throw new Error('host_registration_kind_invalid');
|
|
76
|
+
if (registrationVersion !== HOST_REGISTRATION_VERSION)
|
|
77
|
+
throw new Error('host_registration_version_invalid');
|
|
78
|
+
const manifest = {
|
|
79
|
+
...raw,
|
|
80
|
+
kind,
|
|
81
|
+
registrationVersion,
|
|
82
|
+
domain: normalizedDomain(raw.domain),
|
|
83
|
+
...(nonEmpty(raw.displayName) ? { displayName: nonEmpty(raw.displayName) } : {}),
|
|
84
|
+
...(nonEmpty(raw.packageName) ? { packageName: nonEmpty(raw.packageName) } : {}),
|
|
85
|
+
...(nonEmpty(raw.description) ? { description: nonEmpty(raw.description) } : {}),
|
|
86
|
+
...(nonEmpty(raw.baseDir) ? { baseDir: nonEmpty(raw.baseDir) } : {}),
|
|
87
|
+
...(stringList(raw.notes).length > 0 ? { notes: stringList(raw.notes) } : {}),
|
|
88
|
+
};
|
|
89
|
+
assertRegistrationEntries(manifest);
|
|
90
|
+
return manifest;
|
|
91
|
+
}
|
|
92
|
+
function isWindowsAbsolute(value) {
|
|
93
|
+
return /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('\\\\');
|
|
94
|
+
}
|
|
95
|
+
function isPackageSpecifier(value) {
|
|
96
|
+
if (!value || value.startsWith('.') || value.startsWith('/') || isWindowsAbsolute(value))
|
|
97
|
+
return false;
|
|
98
|
+
if (value.includes('\\'))
|
|
99
|
+
return false;
|
|
100
|
+
return value.startsWith('@') ? /^@[^/]+\/[^/]+(?:\/[^/]+)*$/.test(value) : /^[a-z0-9][a-z0-9._-]*$/i.test(value);
|
|
101
|
+
}
|
|
102
|
+
function materializePath(value, baseDir) {
|
|
103
|
+
const expanded = value === '~'
|
|
104
|
+
? os.homedir()
|
|
105
|
+
: value.startsWith('~/') || value.startsWith('~\\')
|
|
106
|
+
? path.join(os.homedir(), value.slice(2))
|
|
107
|
+
: value;
|
|
108
|
+
if (expanded.startsWith('file://') || expanded.includes('\${'))
|
|
109
|
+
return expanded;
|
|
110
|
+
return path.resolve(baseDir, expanded);
|
|
111
|
+
}
|
|
112
|
+
function materializeMaybePath(value, baseDir) {
|
|
113
|
+
const trimmed = value.trim();
|
|
114
|
+
if (!trimmed || trimmed.startsWith('file://') || trimmed.includes('\${'))
|
|
115
|
+
return trimmed;
|
|
116
|
+
if (isPackageSpecifier(trimmed))
|
|
117
|
+
return trimmed;
|
|
118
|
+
if (trimmed.startsWith('.') || trimmed.startsWith('~') || path.isAbsolute(trimmed) || isWindowsAbsolute(trimmed)) {
|
|
119
|
+
return materializePath(trimmed, baseDir);
|
|
120
|
+
}
|
|
121
|
+
return path.extname(trimmed) || trimmed.includes('/') || trimmed.includes('\\')
|
|
122
|
+
? materializePath(trimmed, baseDir)
|
|
123
|
+
: trimmed;
|
|
124
|
+
}
|
|
125
|
+
function materializeValues(value, key, baseDir) {
|
|
126
|
+
if (typeof value === 'string') {
|
|
127
|
+
if (ALWAYS_PATH_KEYS.has(key))
|
|
128
|
+
return materializePath(value, baseDir);
|
|
129
|
+
if (key === 'module')
|
|
130
|
+
return materializeMaybePath(value, baseDir);
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
return value.map((item) => (MAYBE_PATH_LIST_KEYS.has(key) && typeof item === 'string'
|
|
135
|
+
? materializeMaybePath(item, baseDir)
|
|
136
|
+
: materializeValues(item, '', baseDir)));
|
|
137
|
+
}
|
|
138
|
+
if (!value || typeof value !== 'object')
|
|
139
|
+
return value;
|
|
140
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, item]) => [
|
|
141
|
+
childKey,
|
|
142
|
+
materializeValues(item, childKey, baseDir),
|
|
143
|
+
]));
|
|
144
|
+
}
|
|
145
|
+
export function materializeHostRegistrationManifest(manifest, sourceBaseDir) {
|
|
146
|
+
const normalized = normalizeHostRegistrationManifest(manifest);
|
|
147
|
+
const baseDir = normalized.baseDir
|
|
148
|
+
? materializePath(String(normalized.baseDir), sourceBaseDir)
|
|
149
|
+
: path.resolve(sourceBaseDir);
|
|
150
|
+
return normalizeHostRegistrationManifest({
|
|
151
|
+
...materializeValues(normalized, '', baseDir),
|
|
152
|
+
baseDir,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export function resolveAopsConfigDir(processEnv = process.env) {
|
|
156
|
+
const configured = nonEmpty(processEnv.AOPS_CLI_CONFIG_PATH) ?? nonEmpty(processEnv.AGENT_OPS_CONFIG_PATH);
|
|
157
|
+
if (!configured)
|
|
158
|
+
return path.join(os.homedir(), '.aops');
|
|
159
|
+
if (process.platform !== 'win32' && isWindowsAbsolute(configured))
|
|
160
|
+
return path.join(os.homedir(), '.aops');
|
|
161
|
+
const resolved = path.resolve(configured);
|
|
162
|
+
return resolved.toLowerCase().endsWith('.json') ? path.dirname(resolved) : resolved;
|
|
163
|
+
}
|
|
164
|
+
export function getHostRegistrationsDir(options = {}) {
|
|
165
|
+
const configured = nonEmpty(options.registrationsDir)
|
|
166
|
+
?? nonEmpty(options.processEnv?.AOPS_HOST_REGISTRATIONS_DIR)
|
|
167
|
+
?? nonEmpty(process.env.AOPS_HOST_REGISTRATIONS_DIR);
|
|
168
|
+
return configured
|
|
169
|
+
? path.resolve(configured)
|
|
170
|
+
: path.join(resolveAopsConfigDir(options.processEnv), HOST_REGISTRATIONS_DIRNAME);
|
|
171
|
+
}
|
|
172
|
+
function registrationFilePath(domain, options) {
|
|
173
|
+
return path.join(getHostRegistrationsDir(options), normalizedDomain(domain) + '.json');
|
|
174
|
+
}
|
|
175
|
+
export function writeHostRegistration(manifest, options = {}) {
|
|
176
|
+
const normalized = normalizeHostRegistrationManifest(manifest);
|
|
177
|
+
const filePath = registrationFilePath(normalized.domain, options);
|
|
178
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
179
|
+
const temporary = filePath + '.' + process.pid + '.tmp';
|
|
180
|
+
try {
|
|
181
|
+
fs.writeFileSync(temporary, JSON.stringify(normalized, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
182
|
+
fs.renameSync(temporary, filePath);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
fs.rmSync(temporary, { force: true });
|
|
186
|
+
}
|
|
187
|
+
return filePath;
|
|
188
|
+
}
|
|
189
|
+
export function listInstalledHostRegistrations(options = {}) {
|
|
190
|
+
const registrationsDir = getHostRegistrationsDir(options);
|
|
191
|
+
if (!fs.existsSync(registrationsDir))
|
|
192
|
+
return [];
|
|
193
|
+
return fs.readdirSync(registrationsDir)
|
|
194
|
+
.filter((entry) => entry.toLowerCase().endsWith('.json'))
|
|
195
|
+
.sort()
|
|
196
|
+
.map((entry) => {
|
|
197
|
+
const filePath = path.join(registrationsDir, entry);
|
|
198
|
+
const stats = fs.lstatSync(filePath);
|
|
199
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
200
|
+
throw new Error('host_registration_registry_file_invalid:' + filePath);
|
|
201
|
+
const manifest = normalizeHostRegistrationManifest(JSON.parse(fs.readFileSync(filePath, 'utf8')));
|
|
202
|
+
return {
|
|
203
|
+
domain: manifest.domain,
|
|
204
|
+
filePath,
|
|
205
|
+
manifest: normalizeHostRegistrationManifest({
|
|
206
|
+
...manifest,
|
|
207
|
+
provenance: { ...dataRecord(manifest.provenance), source: filePath, sourceType: 'registry' },
|
|
208
|
+
}),
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export function unregisterHostRegistration(domain, options = {}) {
|
|
213
|
+
const filePath = registrationFilePath(domain, options);
|
|
214
|
+
if (!fs.existsSync(filePath))
|
|
215
|
+
return false;
|
|
216
|
+
const stats = fs.lstatSync(filePath);
|
|
217
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
218
|
+
throw new Error('host_registration_registry_file_invalid:' + filePath);
|
|
219
|
+
fs.unlinkSync(filePath);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
function mergeBy(items, incoming, key) {
|
|
223
|
+
const values = new Map();
|
|
224
|
+
for (const item of incoming)
|
|
225
|
+
values.set(String(item[key] ?? ''), item);
|
|
226
|
+
for (const item of items)
|
|
227
|
+
values.set(String(item[key] ?? ''), item);
|
|
228
|
+
return [...values.values()]
|
|
229
|
+
.filter((item) => nonEmpty(item[key]))
|
|
230
|
+
.sort((a, b) => compareCanonicalText(String(a[key]), String(b[key])));
|
|
231
|
+
}
|
|
232
|
+
export function mergeHostRegistrationsIntoConfig(baseConfig, registrations) {
|
|
233
|
+
let runtimeEnv = {};
|
|
234
|
+
let allowlist = [];
|
|
235
|
+
let strictAllowlist = false;
|
|
236
|
+
let tolerantBootstrap = false;
|
|
237
|
+
let providers = [];
|
|
238
|
+
let sources = [];
|
|
239
|
+
let plugins = [];
|
|
240
|
+
for (const registration of registrations.map(normalizeHostRegistrationManifest)) {
|
|
241
|
+
runtimeEnv = { ...runtimeEnv, ...dataRecord(dataRecord(registration.runtime).env) };
|
|
242
|
+
const loader = dataRecord(registration.pluginLoader);
|
|
243
|
+
allowlist = [...new Set([...allowlist, ...stringList(loader.allowlist), ...(registration.plugins ?? []).map((item) => nonEmpty(item.module)).filter(Boolean)])];
|
|
244
|
+
strictAllowlist ||= loader.strictAllowlist === true;
|
|
245
|
+
tolerantBootstrap ||= loader.tolerantBootstrap === true;
|
|
246
|
+
providers = mergeBy(providers, registration.manifestProviders ?? [], 'id');
|
|
247
|
+
sources = mergeBy(sources, registration.sources ?? [], 'id');
|
|
248
|
+
plugins = mergeBy(plugins, registration.plugins ?? [], 'domain');
|
|
249
|
+
}
|
|
250
|
+
const baseRuntime = dataRecord(baseConfig.runtime);
|
|
251
|
+
const baseLoader = dataRecord(baseConfig.pluginLoader);
|
|
252
|
+
const baseGateway = dataRecord(baseConfig.agentGateway);
|
|
253
|
+
const baseCatalog = dataRecord(baseGateway.catalog);
|
|
254
|
+
return {
|
|
255
|
+
runtime: { ...baseRuntime, env: { ...runtimeEnv, ...dataRecord(baseRuntime.env) } },
|
|
256
|
+
pluginLoader: {
|
|
257
|
+
allowlist: [...new Set([...allowlist, ...stringList(baseLoader.allowlist)])],
|
|
258
|
+
strictAllowlist: baseLoader.strictAllowlist ?? strictAllowlist,
|
|
259
|
+
tolerantBootstrap: baseLoader.tolerantBootstrap ?? tolerantBootstrap,
|
|
260
|
+
},
|
|
261
|
+
agentGateway: providers.length > 0 || sources.length > 0 || Object.keys(baseGateway).length > 0 ? {
|
|
262
|
+
...baseGateway,
|
|
263
|
+
enabled: baseGateway.enabled ?? true,
|
|
264
|
+
includeLocal: baseGateway.includeLocal ?? true,
|
|
265
|
+
sources: mergeBy(baseGateway.sources ?? [], sources, 'id'),
|
|
266
|
+
catalog: {
|
|
267
|
+
...baseCatalog,
|
|
268
|
+
enabled: baseCatalog.enabled ?? true,
|
|
269
|
+
manifestProviders: mergeBy(baseCatalog.manifestProviders ?? [], providers, 'id'),
|
|
270
|
+
},
|
|
271
|
+
} : undefined,
|
|
272
|
+
plugins: mergeBy(baseConfig.plugins ?? [], plugins, 'domain'),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function normalizeLogLevel(value) {
|
|
276
|
+
const normalized = nonEmpty(value)?.toLowerCase();
|
|
277
|
+
return normalized && HOST_LOG_LEVELS.has(normalized) ? normalized : undefined;
|
|
278
|
+
}
|
|
279
|
+
function stripQuotes(value) {
|
|
280
|
+
return value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
281
|
+
? value.slice(1, -1)
|
|
282
|
+
: value;
|
|
283
|
+
}
|
|
284
|
+
function parseEnv(content) {
|
|
285
|
+
const assignments = {};
|
|
286
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
287
|
+
const match = rawLine.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
288
|
+
if (match)
|
|
289
|
+
assignments[match[1]] = stripQuotes(String(match[2]).trim());
|
|
290
|
+
}
|
|
291
|
+
return assignments;
|
|
292
|
+
}
|
|
293
|
+
function quoteEnv(value) {
|
|
294
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : JSON.stringify(value);
|
|
295
|
+
}
|
|
296
|
+
export function isSqliteRepoUrl(repoUrl) {
|
|
297
|
+
const value = repoUrl.trim().toLowerCase();
|
|
298
|
+
return value === ':memory:' || value.startsWith('sqlite:') || value.startsWith('file:') || /\.(?:db|sqlite|sqlite3)$/.test(value);
|
|
299
|
+
}
|
|
300
|
+
export function inferAopsRepoDialect(repoUrl) {
|
|
301
|
+
return isSqliteRepoUrl(repoUrl) ? 'sqlite' : 'pg';
|
|
302
|
+
}
|
|
303
|
+
export function getAopsRuntimeConfigDir(processEnv = process.env) {
|
|
304
|
+
const explicit = nonEmpty(processEnv.AOPS_CLI_CONFIG_PATH) ?? nonEmpty(processEnv.AGENT_OPS_CONFIG_PATH);
|
|
305
|
+
return !explicit && processEnv.NODE_ENV === 'test'
|
|
306
|
+
? path.resolve(process.cwd(), '.tmp', 'aops-runtime-test-config')
|
|
307
|
+
: resolveAopsConfigDir(processEnv);
|
|
308
|
+
}
|
|
309
|
+
export function getAopsServerEnvPath(processEnv = process.env) {
|
|
310
|
+
return path.resolve(getAopsRuntimeConfigDir(processEnv), AOPS_SERVER_ENV_FILENAME);
|
|
311
|
+
}
|
|
312
|
+
export function readAopsServerEnvConfig(processEnv = process.env, explicitPath) {
|
|
313
|
+
const envPath = explicitPath ? path.resolve(explicitPath) : getAopsServerEnvPath(processEnv);
|
|
314
|
+
if (!fs.existsSync(envPath))
|
|
315
|
+
return { path: envPath, exists: false, assignments: {}, repoUrl: null, repoDialect: null, redactedRepoUrl: null, hostSettings: { logLevel: null } };
|
|
316
|
+
const assignments = parseEnv(fs.readFileSync(envPath, 'utf8'));
|
|
317
|
+
const repoUrl = nonEmpty(assignments.AOPS_REPO_URL) ?? nonEmpty(assignments.AOPS_PG_URL) ?? nonEmpty(assignments.AOPS_SQLITE_URL) ?? null;
|
|
318
|
+
let redactedRepoUrl = repoUrl;
|
|
319
|
+
try {
|
|
320
|
+
if (repoUrl) {
|
|
321
|
+
const parsed = new URL(repoUrl);
|
|
322
|
+
if (parsed.password)
|
|
323
|
+
parsed.password = '***';
|
|
324
|
+
redactedRepoUrl = parsed.toString();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch { }
|
|
328
|
+
return { path: envPath, exists: true, assignments, repoUrl, repoDialect: repoUrl ? inferAopsRepoDialect(repoUrl) : null, redactedRepoUrl, hostSettings: { logLevel: normalizeLogLevel(assignments.AOPS_LOG_LEVEL) ?? normalizeLogLevel(assignments.LOG_LEVEL) ?? null } };
|
|
329
|
+
}
|
|
330
|
+
export function writeAopsServerEnvConfig(patch, processEnv = process.env, explicitPath) {
|
|
331
|
+
const current = readAopsServerEnvConfig(processEnv, explicitPath);
|
|
332
|
+
const next = { ...current.assignments };
|
|
333
|
+
if (patch.repoUrl !== undefined) {
|
|
334
|
+
const repoUrl = nonEmpty(patch.repoUrl);
|
|
335
|
+
delete next.AOPS_REPO_URL;
|
|
336
|
+
delete next.AOPS_PG_URL;
|
|
337
|
+
delete next.AOPS_SQLITE_URL;
|
|
338
|
+
if (repoUrl) {
|
|
339
|
+
next.AOPS_REPO_URL = repoUrl;
|
|
340
|
+
next[inferAopsRepoDialect(repoUrl) === 'sqlite' ? 'AOPS_SQLITE_URL' : 'AOPS_PG_URL'] = repoUrl;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (patch.logLevel !== undefined) {
|
|
344
|
+
const level = normalizeLogLevel(patch.logLevel);
|
|
345
|
+
if (level)
|
|
346
|
+
next.AOPS_LOG_LEVEL = level;
|
|
347
|
+
else
|
|
348
|
+
delete next.AOPS_LOG_LEVEL;
|
|
349
|
+
}
|
|
350
|
+
const managed = new Set(['AOPS_REPO_URL', 'AOPS_PG_URL', 'AOPS_SQLITE_URL', 'AOPS_LOG_LEVEL']);
|
|
351
|
+
const existingLines = current.exists ? fs.readFileSync(current.path, 'utf8').split(/\r?\n/) : [];
|
|
352
|
+
const touched = new Set();
|
|
353
|
+
const lines = existingLines.map((line) => {
|
|
354
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
355
|
+
if (!match || !managed.has(match[1]))
|
|
356
|
+
return line;
|
|
357
|
+
touched.add(match[1]);
|
|
358
|
+
return next[match[1]] ? match[1] + '=' + quoteEnv(next[match[1]]) : '';
|
|
359
|
+
});
|
|
360
|
+
for (const key of managed)
|
|
361
|
+
if (next[key] && !touched.has(key))
|
|
362
|
+
lines.push(key + '=' + quoteEnv(next[key]));
|
|
363
|
+
fs.mkdirSync(path.dirname(current.path), { recursive: true });
|
|
364
|
+
fs.writeFileSync(current.path, lines.filter((line, index, all) => line || all[index - 1]).join('\n').trimEnd() + '\n', { mode: 0o600 });
|
|
365
|
+
try {
|
|
366
|
+
fs.chmodSync(current.path, 0o600);
|
|
367
|
+
}
|
|
368
|
+
catch { }
|
|
369
|
+
return readAopsServerEnvConfig(processEnv, current.path);
|
|
370
|
+
}
|
|
371
|
+
export function getDefaultAopsSqlitePath(processEnv = process.env) {
|
|
372
|
+
return path.resolve(getAopsRuntimeConfigDir(processEnv), AOPS_SQLITE_FILENAME);
|
|
373
|
+
}
|
|
374
|
+
export function getDefaultAopsSqliteRepoUrl(processEnv = process.env) {
|
|
375
|
+
return pathToFileURL(getDefaultAopsSqlitePath(processEnv)).href;
|
|
376
|
+
}
|