@polardbx/pxbaas 0.1.0-beta.2 → 0.1.0-beta.4
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 +116 -13
- package/dist/app/local-index.d.ts +70 -0
- package/dist/app/local-index.d.ts.map +1 -0
- package/dist/app/local-index.js +140 -0
- package/dist/app/local-index.js.map +1 -0
- package/dist/app/preflight.d.ts +24 -1
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/preflight.js +47 -11
- package/dist/app/preflight.js.map +1 -1
- package/dist/app/spec-lint.d.ts.map +1 -1
- package/dist/app/spec-lint.js +16 -0
- package/dist/app/spec-lint.js.map +1 -1
- package/dist/cli/root.d.ts.map +1 -1
- package/dist/cli/root.js +3 -0
- package/dist/cli/root.js.map +1 -1
- package/dist/commands/app/index.d.ts.map +1 -1
- package/dist/commands/app/index.js +428 -44
- package/dist/commands/app/index.js.map +1 -1
- package/dist/commands/completion/index.d.ts.map +1 -1
- package/dist/commands/completion/index.js +5 -1
- package/dist/commands/completion/index.js.map +1 -1
- package/dist/commands/data/index.d.ts +52 -0
- package/dist/commands/data/index.d.ts.map +1 -1
- package/dist/commands/data/index.js +201 -39
- package/dist/commands/data/index.js.map +1 -1
- package/dist/commands/db/index.d.ts +14 -0
- package/dist/commands/db/index.d.ts.map +1 -1
- package/dist/commands/db/index.js +151 -40
- package/dist/commands/db/index.js.map +1 -1
- package/dist/commands/functions/new.d.ts +4 -0
- package/dist/commands/functions/new.d.ts.map +1 -1
- package/dist/commands/functions/new.js +24 -9
- package/dist/commands/functions/new.js.map +1 -1
- package/dist/commands/gen/index.d.ts.map +1 -1
- package/dist/commands/gen/index.js +13 -10
- package/dist/commands/gen/index.js.map +1 -1
- package/dist/commands/login/index.d.ts.map +1 -1
- package/dist/commands/login/index.js +15 -30
- package/dist/commands/login/index.js.map +1 -1
- package/dist/commands/profile/import-env.d.ts +7 -2
- package/dist/commands/profile/import-env.d.ts.map +1 -1
- package/dist/commands/profile/import-env.js +141 -16
- package/dist/commands/profile/import-env.js.map +1 -1
- package/dist/commands/profile/index.d.ts.map +1 -1
- package/dist/commands/profile/index.js +13 -1
- package/dist/commands/profile/index.js.map +1 -1
- package/dist/commands/storage/index.d.ts.map +1 -1
- package/dist/commands/storage/index.js +209 -7
- package/dist/commands/storage/index.js.map +1 -1
- package/dist/commands/tenant/index.d.ts +19 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +662 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/whoami/index.js +19 -0
- package/dist/commands/whoami/index.js.map +1 -1
- package/dist/credentials/credential-store.d.ts +30 -1
- package/dist/credentials/credential-store.d.ts.map +1 -1
- package/dist/credentials/credential-store.js +114 -12
- package/dist/credentials/credential-store.js.map +1 -1
- package/dist/credentials/env-file.d.ts +48 -7
- package/dist/credentials/env-file.d.ts.map +1 -1
- package/dist/credentials/env-file.js +91 -16
- package/dist/credentials/env-file.js.map +1 -1
- package/dist/credentials/jwt-introspect.d.ts +7 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -1
- package/dist/credentials/jwt-introspect.js.map +1 -1
- package/dist/credentials/masker.d.ts +3 -0
- package/dist/credentials/masker.d.ts.map +1 -1
- package/dist/credentials/masker.js +3 -0
- package/dist/credentials/masker.js.map +1 -1
- package/dist/credentials/profile.d.ts +12 -0
- package/dist/credentials/profile.d.ts.map +1 -1
- package/dist/credentials/profile.js +23 -0
- package/dist/credentials/profile.js.map +1 -1
- package/dist/credentials/tty-guard.d.ts +10 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -1
- package/dist/credentials/tty-guard.js +32 -0
- package/dist/credentials/tty-guard.js.map +1 -1
- package/dist/db/introspect.d.ts +54 -0
- package/dist/db/introspect.d.ts.map +1 -0
- package/dist/db/introspect.js +140 -0
- package/dist/db/introspect.js.map +1 -0
- package/dist/main.js +21 -2
- package/dist/main.js.map +1 -1
- package/dist/tenant/bundle.d.ts +66 -0
- package/dist/tenant/bundle.d.ts.map +1 -0
- package/dist/tenant/bundle.js +132 -0
- package/dist/tenant/bundle.js.map +1 -0
- package/dist/tenant/guide.d.ts +50 -0
- package/dist/tenant/guide.d.ts.map +1 -0
- package/dist/tenant/guide.js +152 -0
- package/dist/tenant/guide.js.map +1 -0
- package/dist/tenant/probe.d.ts +65 -0
- package/dist/tenant/probe.d.ts.map +1 -0
- package/dist/tenant/probe.js +159 -0
- package/dist/tenant/probe.js.map +1 -0
- package/dist/tenant/verify.d.ts +37 -0
- package/dist/tenant/verify.d.ts.map +1 -0
- package/dist/tenant/verify.js +132 -0
- package/dist/tenant/verify.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +65 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
- package/skills/pxbaas-app-spec/references/runtime-contract.md +2 -2
- package/skills/pxbaas-cli/SKILL.md +14 -5
- package/skills/pxbaas-cli/references/auth-and-credentials.md +19 -3
- package/skills/pxbaas-cli/references/data-postgrest.md +19 -3
- package/skills/pxbaas-cli/references/db-and-meta.md +5 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
- package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
- package/templates/webapp/react-vite-hono/backend/index.ts +21 -4
- package/templates/webapp/ssr-hono/backend/index.ts +50 -31
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pxbaas profile import <env-file>
|
|
3
3
|
*
|
|
4
|
-
* 直接从一个 Supabase/BaaS 惯例的 .env
|
|
4
|
+
* 直接从一个 Supabase/BaaS 惯例的 .env 文件导入实例凭据并落库为 profile,
|
|
5
5
|
* 免去把 URL / anon key / service key 拆出来再拼 stdin 登录命令。
|
|
6
6
|
*
|
|
7
7
|
* 与 login 共用 commitProfileCredentials(JWT 过期校验 + 租户一致性校验 + keyring 存储)。
|
|
8
8
|
* 密钥来自文件而非 argv/shell history,因此该命令可被 agent 非交互执行。
|
|
9
|
-
*
|
|
9
|
+
* 多租户实例按 DEFAULT_TENANT_ID + TENANT_* 建 multi-tenant profile;全局键不导入
|
|
10
|
+
* ——见 resolveLoginFromEnv。
|
|
11
|
+
*
|
|
12
|
+
* 失败分支(多租户但缺租户级凭据)不再只是拒绝:先做本地诊断,再用一次**临时只读**
|
|
13
|
+
* 探测判断本机能否打开租户管理面,据此分流到「管理员自助路径」或「申请清单」。
|
|
14
|
+
* 该分支绝不创建租户,也绝不把文件里的 DASHBOARD_* 落库。
|
|
10
15
|
*/
|
|
11
16
|
import { readFile } from 'node:fs/promises';
|
|
12
|
-
import {
|
|
17
|
+
import { CredentialStore, ProfileManager, commitProfileCredentials, computeFingerprint, formatError, formatSuccess, formatWarning, parseEnvText, resolveAdminFromEnv, resolveLoginFromEnv, TenantCredentialGapError, } from '../../credentials/index.js';
|
|
18
|
+
import { diagnoseTenantCredentialGap, renderAdminPath, renderRequestChecklist, tryProbeFromEnv, } from '../../tenant/guide.js';
|
|
13
19
|
export function registerProfileImportCommand(profileCmd, program) {
|
|
14
20
|
profileCmd
|
|
15
21
|
.command('import <env-file>')
|
|
16
|
-
.description('Import
|
|
22
|
+
.description('Import an instance profile from a .env file (non-interactive)')
|
|
17
23
|
.option('--name <profile>', 'Profile name to create/overwrite (default: --profile or "default")')
|
|
18
24
|
.option('--intranet', 'Prefer SUPABASE_INTRANET_URL over the public endpoint')
|
|
25
|
+
.option('--with-admin', 'Also bind DASHBOARD_USERNAME/DASHBOARD_PASSWORD as platform admin credentials (verified against the gateway first)')
|
|
19
26
|
.option('--json', 'Output in JSON format')
|
|
20
27
|
.action(async (envFile, options) => {
|
|
21
28
|
try {
|
|
22
29
|
await handleProfileImport(envFile, options, program);
|
|
23
30
|
}
|
|
24
31
|
catch (error) {
|
|
32
|
+
if (error instanceof TenantCredentialGapError) {
|
|
33
|
+
await handleTenantGap(error, envFile, options, program);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
25
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
26
37
|
if (options.json || program.opts().json) {
|
|
27
38
|
console.log(JSON.stringify({ status: 'error', message }, null, 2));
|
|
@@ -34,18 +45,10 @@ export function registerProfileImportCommand(profileCmd, program) {
|
|
|
34
45
|
});
|
|
35
46
|
}
|
|
36
47
|
async function handleProfileImport(envFile, options, program) {
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
text = await readFile(envFile, 'utf-8');
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
43
|
-
throw new Error(`Failed to read env file '${envFile}': ${reason}`);
|
|
44
|
-
}
|
|
45
|
-
const env = parseEnvText(text);
|
|
48
|
+
const env = parseEnvText(await readEnvText(envFile));
|
|
46
49
|
const fields = resolveLoginFromEnv(env, { intranet: options.intranet });
|
|
47
|
-
// profile 名优先级:--name >
|
|
48
|
-
const profileName = options.name || program.opts().profile || 'default';
|
|
50
|
+
// profile 名优先级:--name > --profile(尾置或前置)> 'default'
|
|
51
|
+
const profileName = options.name || options.profile || program.opts().profile || 'default';
|
|
49
52
|
const result = await commitProfileCredentials({
|
|
50
53
|
profileName,
|
|
51
54
|
endpoint: fields.endpoint,
|
|
@@ -53,6 +56,12 @@ async function handleProfileImport(envFile, options, program) {
|
|
|
53
56
|
anonKey: fields.anonKey,
|
|
54
57
|
serviceRoleKey: fields.serviceRoleKey,
|
|
55
58
|
});
|
|
59
|
+
const skippedGlobalKeys = fields.skippedGlobalKeys ?? [];
|
|
60
|
+
const globalKeysNote = skippedGlobalKeys.length > 0
|
|
61
|
+
? `${skippedGlobalKeys.join(' / ')} present in the env file but NOT imported ` +
|
|
62
|
+
'(a profile stores tenant-level keys only; global keys are for platform-level ops)'
|
|
63
|
+
: undefined;
|
|
64
|
+
const adminOutcome = await maybeBindAdminCredentials(env, options, result.endpoint, profileName);
|
|
56
65
|
if (options.json || program.opts().json) {
|
|
57
66
|
const output = {
|
|
58
67
|
status: 'success',
|
|
@@ -65,6 +74,9 @@ async function handleProfileImport(envFile, options, program) {
|
|
|
65
74
|
sources: fields.sources,
|
|
66
75
|
anonKeyFingerprint: result.anonKeyFingerprint,
|
|
67
76
|
serviceKeyFingerprint: result.serviceKeyFingerprint,
|
|
77
|
+
globalKeysImported: skippedGlobalKeys.length === 0,
|
|
78
|
+
...(globalKeysNote ? { skippedGlobalKeys, note: globalKeysNote } : {}),
|
|
79
|
+
...(adminOutcome ? { admin: adminOutcome } : {}),
|
|
68
80
|
};
|
|
69
81
|
console.log(JSON.stringify(output, null, 2));
|
|
70
82
|
}
|
|
@@ -72,10 +84,123 @@ async function handleProfileImport(envFile, options, program) {
|
|
|
72
84
|
console.log(formatSuccess(`Imported profile from env file: ${profileName}`));
|
|
73
85
|
console.log(` Env file: ${envFile}`);
|
|
74
86
|
console.log(` Endpoint: ${result.endpoint} (from ${fields.sources.endpoint})`);
|
|
75
|
-
console.log(` Tenant ID: ${result.tenantId || '(single-tenant)'}`
|
|
87
|
+
console.log(` Tenant ID: ${result.tenantId || '(single-tenant)'}` +
|
|
88
|
+
(fields.sources.tenantId ? ` (from ${fields.sources.tenantId})` : ''));
|
|
76
89
|
console.log(` Mode: ${result.mode}`);
|
|
77
90
|
console.log(` Anon Key: ${result.anonKeyFingerprint} (from ${fields.sources.anonKey})`);
|
|
78
91
|
console.log(` Service Key: ${result.serviceKeyFingerprint} (from ${fields.sources.serviceRoleKey})`);
|
|
92
|
+
if (globalKeysNote) {
|
|
93
|
+
console.log(` Note: ${globalKeysNote}`);
|
|
94
|
+
}
|
|
95
|
+
if (adminOutcome?.note) {
|
|
96
|
+
console.log(` Admin: ${adminOutcome.note}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 管理凭据默认**不外吸**:文件里有 DASHBOARD_* 时只报告"存在但未导入"。
|
|
102
|
+
* 只有显式 --with-admin 才导入,且导入前必须先用它探通一次租户管理面——存进一把
|
|
103
|
+
* 不通的口令,会让后续每条管理命令都 401 且难以归因(与"拒绝保存过期密钥"同理)。
|
|
104
|
+
*/
|
|
105
|
+
async function maybeBindAdminCredentials(env, options, endpoint, profileName) {
|
|
106
|
+
const admin = resolveAdminFromEnv(env);
|
|
107
|
+
if (!admin) {
|
|
108
|
+
return options.withAdmin
|
|
109
|
+
? { bound: false, note: 'no DASHBOARD_USERNAME/DASHBOARD_PASSWORD pair found in this env file' }
|
|
110
|
+
: undefined;
|
|
111
|
+
}
|
|
112
|
+
if (!options.withAdmin) {
|
|
113
|
+
return {
|
|
114
|
+
bound: false,
|
|
115
|
+
note: `${admin.sources.username} / ${admin.sources.password} present but NOT imported ` +
|
|
116
|
+
'(platform admin credentials are never absorbed silently; pass --with-admin to bind them)',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const probe = await tryProbeFromEnv(env, endpoint);
|
|
120
|
+
if (!probe?.adminCapable) {
|
|
121
|
+
return {
|
|
122
|
+
bound: false,
|
|
123
|
+
note: `--with-admin refused: these dashboard credentials could not open the tenant admin channel (${probe?.status ?? 'probe unavailable'}). Tenant keys were imported normally.`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const store = new CredentialStore();
|
|
127
|
+
await store.setAdminCredentials(profileName, {
|
|
128
|
+
username: admin.username,
|
|
129
|
+
password: admin.password,
|
|
130
|
+
storedAt: new Date().toISOString(),
|
|
131
|
+
});
|
|
132
|
+
const profiles = new ProfileManager();
|
|
133
|
+
const profile = await profiles.getProfile(profileName);
|
|
134
|
+
if (profile)
|
|
135
|
+
await profiles.setProfile({ ...profile, adminCapable: true });
|
|
136
|
+
return {
|
|
137
|
+
bound: true,
|
|
138
|
+
note: `platform admin credentials bound (user=${admin.username.slice(0, 2)}***, pass=${computeFingerprint(admin.password)})`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** 多租户缺租户级凭据:诊断 → 分流指引 → 退出 1,并明确"什么都没创建、什么都没导入"。 */
|
|
142
|
+
async function handleTenantGap(error, envFile, options, program) {
|
|
143
|
+
const env = parseEnvText(await readEnvText(envFile));
|
|
144
|
+
const diagnostics = diagnoseTenantCredentialGap(env, { envFile });
|
|
145
|
+
const probe = await tryProbeFromEnv(env, diagnostics.endpoint);
|
|
146
|
+
const adminRoute = Boolean(probe?.adminCapable);
|
|
147
|
+
if (options.json || program.opts().json) {
|
|
148
|
+
console.log(JSON.stringify({
|
|
149
|
+
status: 'error',
|
|
150
|
+
code: 'TENANT_CREDENTIAL_GAP',
|
|
151
|
+
message: error.message,
|
|
152
|
+
missing: error.missing,
|
|
153
|
+
nothingWasCreated: true,
|
|
154
|
+
nothingWasImported: true,
|
|
155
|
+
diagnostics,
|
|
156
|
+
adminChannel: probe
|
|
157
|
+
? { status: probe.status, adminCapable: probe.adminCapable, detail: probe.detail }
|
|
158
|
+
: { status: 'no-dashboard-credentials-in-env', adminCapable: false },
|
|
159
|
+
route: adminRoute ? 'admin-self-service' : 'request-from-admin',
|
|
160
|
+
nextSteps: adminRoute
|
|
161
|
+
? [
|
|
162
|
+
`pxbaas tenant login --endpoint ${diagnostics.endpoint ?? '<gateway url>'}`,
|
|
163
|
+
'pxbaas tenant ls --json',
|
|
164
|
+
`pxbaas tenant keys ${diagnostics.defaultTenantId ?? '<tenant-id>'} --as-profile`,
|
|
165
|
+
`pxbaas tenant keys ${diagnostics.defaultTenantId ?? '<tenant-id>'} --bundle ./<tenant>.pxbaas.env`,
|
|
166
|
+
]
|
|
167
|
+
: [
|
|
168
|
+
'Ask the instance administrator for this tenant’s anon key and service role key.',
|
|
169
|
+
'A tenant-level key’s JWT payload carries a tenant_id claim; a platform-global key does not and cannot reach tenant resources.',
|
|
170
|
+
],
|
|
171
|
+
}, null, 2));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
console.error(formatError(error.message));
|
|
175
|
+
console.error('');
|
|
176
|
+
console.error('Diagnosis of this env file:');
|
|
177
|
+
console.error(` MULTI_TENANT_MODE = ${diagnostics.multiTenantMode ?? '(absent)'}`);
|
|
178
|
+
console.error(` DEFAULT_TENANT_ID = ${diagnostics.defaultTenantId ?? '(absent)'}`);
|
|
179
|
+
console.error(` tenant-level keys = ${diagnostics.tenantLevelKeysPresent.length ? diagnostics.tenantLevelKeysPresent.join(', ') : 'NONE'}`);
|
|
180
|
+
for (const finding of diagnostics.keyFindings) {
|
|
181
|
+
console.error(` ${finding.variable.padEnd(26)} ${finding.fingerprint} ${finding.note}`);
|
|
182
|
+
}
|
|
183
|
+
console.error(` admin creds present = ${diagnostics.adminCredentialsPresent ? 'yes (NOT imported)' : 'no'}`);
|
|
184
|
+
console.error('');
|
|
185
|
+
console.error('Nothing was created and nothing was imported.');
|
|
186
|
+
console.error('');
|
|
187
|
+
if (adminRoute && probe) {
|
|
188
|
+
console.error(renderAdminPath(diagnostics, probe));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.error(renderRequestChecklist(diagnostics));
|
|
192
|
+
}
|
|
193
|
+
console.error('');
|
|
194
|
+
console.error(formatWarning('If you ARE the instance administrator, start with: pxbaas tenant login --endpoint <gateway url>'));
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
async function readEnvText(envFile) {
|
|
198
|
+
try {
|
|
199
|
+
return await readFile(envFile, 'utf-8');
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
203
|
+
throw new Error(`Failed to read env file '${envFile}': ${reason}`);
|
|
79
204
|
}
|
|
80
205
|
}
|
|
81
206
|
//# sourceMappingURL=import-env.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-env.js","sourceRoot":"","sources":["../../../src/commands/profile/import-env.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"import-env.js","sourceRoot":"","sources":["../../../src/commands/profile/import-env.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/profile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/profile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiD7D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProfileManager, formatSuccess, formatError, } from '../../credentials/index.js';
|
|
1
|
+
import { CredentialStore, ProfileManager, formatSuccess, formatError, } from '../../credentials/index.js';
|
|
2
2
|
import { registerProfileImportCommand } from './import-env.js';
|
|
3
3
|
export function registerProfileCommand(program) {
|
|
4
4
|
const profileCmd = program
|
|
@@ -98,15 +98,27 @@ async function handleProfileRm(name, program) {
|
|
|
98
98
|
if (!deleted) {
|
|
99
99
|
throw new Error(`Profile not found: ${name}`);
|
|
100
100
|
}
|
|
101
|
+
// 删 profile 必须连带删掉它的敏感项,否则租户密钥/管理凭据会变成钥匙串里
|
|
102
|
+
// 既看不见也删不掉的孤儿(profile 名已经不存在,再没有命令能定位它们)。
|
|
103
|
+
const secrets = await new CredentialStore().deleteAllForProfile(name);
|
|
101
104
|
const output = {
|
|
102
105
|
profile: name,
|
|
103
106
|
status: 'removed',
|
|
107
|
+
credentialsRemoved: secrets.credentialsRemoved,
|
|
108
|
+
adminCredentialsRemoved: secrets.adminRemoved,
|
|
104
109
|
};
|
|
105
110
|
if (program.opts().json) {
|
|
106
111
|
console.log(JSON.stringify(output, null, 2));
|
|
107
112
|
}
|
|
108
113
|
else {
|
|
109
114
|
console.log(formatSuccess(`Removed profile: ${name}`));
|
|
115
|
+
if (secrets.credentialsRemoved || secrets.adminRemoved) {
|
|
116
|
+
const what = [
|
|
117
|
+
secrets.credentialsRemoved ? 'API keys' : null,
|
|
118
|
+
secrets.adminRemoved ? 'dashboard credentials' : null,
|
|
119
|
+
].filter(Boolean).join(' and ');
|
|
120
|
+
console.log(` Also removed stored ${what}.`);
|
|
121
|
+
}
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/profile/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/profile/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8DpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmF9D"}
|
|
@@ -1,17 +1,75 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { extname, isAbsolute, resolve } from 'node:path';
|
|
1
4
|
import { StorageDriver } from '@polardbx/pxbaas-client';
|
|
2
5
|
import { CredentialStore, ProfileManager, formatSuccess, formatError, } from '../../credentials/index.js';
|
|
6
|
+
// Common static-asset types for `storage cp` uploads; anything unknown falls
|
|
7
|
+
// back to application/octet-stream (server stores bytes as-is either way).
|
|
8
|
+
const CONTENT_TYPES = {
|
|
9
|
+
'.html': 'text/html; charset=utf-8',
|
|
10
|
+
'.htm': 'text/html; charset=utf-8',
|
|
11
|
+
'.css': 'text/css; charset=utf-8',
|
|
12
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
13
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
14
|
+
'.json': 'application/json',
|
|
15
|
+
'.map': 'application/json',
|
|
16
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
17
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
18
|
+
'.csv': 'text/csv; charset=utf-8',
|
|
19
|
+
'.xml': 'application/xml',
|
|
20
|
+
'.svg': 'image/svg+xml',
|
|
21
|
+
'.png': 'image/png',
|
|
22
|
+
'.jpg': 'image/jpeg',
|
|
23
|
+
'.jpeg': 'image/jpeg',
|
|
24
|
+
'.gif': 'image/gif',
|
|
25
|
+
'.webp': 'image/webp',
|
|
26
|
+
'.ico': 'image/x-icon',
|
|
27
|
+
'.pdf': 'application/pdf',
|
|
28
|
+
'.wasm': 'application/wasm',
|
|
29
|
+
'.woff': 'font/woff',
|
|
30
|
+
'.woff2': 'font/woff2',
|
|
31
|
+
'.ttf': 'font/ttf',
|
|
32
|
+
};
|
|
33
|
+
function inferContentType(filePath) {
|
|
34
|
+
return CONTENT_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Classify a `cp` operand: an absolute path, a `./`-style path or anything that
|
|
38
|
+
* exists on the local filesystem is local; everything else is a remote
|
|
39
|
+
* `bucket/key` reference. Checked first so a local file named like a bucket key
|
|
40
|
+
* (e.g. `report.txt` in cwd) is uploaded rather than read from Storage.
|
|
41
|
+
*/
|
|
42
|
+
function isLocalRef(ref) {
|
|
43
|
+
if (isAbsolute(ref) || ref.startsWith('./') || ref.startsWith('../') || ref === '.' || ref === '..') {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
statSync(ref);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
3
54
|
export function registerStorageCommands(program) {
|
|
4
55
|
const storageCmd = program
|
|
5
56
|
.command('storage')
|
|
6
57
|
.description('Storage management');
|
|
58
|
+
// `--debug` is only declared on the program, so subcommand opts() never carry it.
|
|
59
|
+
const isDebug = () => Boolean(program.opts().debug);
|
|
7
60
|
// storage ls [bucket/path]
|
|
8
61
|
storageCmd
|
|
9
62
|
.command('ls [path]')
|
|
10
63
|
.description('List buckets or objects in a bucket')
|
|
64
|
+
.option('--limit <n>', 'Maximum number of objects to list', '1000')
|
|
11
65
|
.option('--json', 'Output in JSON format')
|
|
12
66
|
.action(async (path, options) => {
|
|
13
67
|
try {
|
|
14
|
-
await handleStorageLs(path, {
|
|
68
|
+
await handleStorageLs(path, {
|
|
69
|
+
...options,
|
|
70
|
+
debug: isDebug(),
|
|
71
|
+
json: options.json || program.opts().json,
|
|
72
|
+
});
|
|
15
73
|
}
|
|
16
74
|
catch (error) {
|
|
17
75
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -24,9 +82,15 @@ export function registerStorageCommands(program) {
|
|
|
24
82
|
.command('cp <src> <dst>')
|
|
25
83
|
.description('Copy objects (local<->remote or remote<->remote)')
|
|
26
84
|
.option('--recursive', 'Copy recursively')
|
|
85
|
+
.option('--content-type <mime>', 'Override the Content-Type inferred from the file extension')
|
|
86
|
+
.option('--json', 'Output in JSON format')
|
|
27
87
|
.action(async (src, dst, options) => {
|
|
28
88
|
try {
|
|
29
|
-
await handleStorageCp(src, dst,
|
|
89
|
+
await handleStorageCp(src, dst, {
|
|
90
|
+
...options,
|
|
91
|
+
debug: isDebug(),
|
|
92
|
+
json: options.json || program.opts().json,
|
|
93
|
+
});
|
|
30
94
|
}
|
|
31
95
|
catch (error) {
|
|
32
96
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -40,7 +104,7 @@ export function registerStorageCommands(program) {
|
|
|
40
104
|
.description('Move/rename objects (remote only)')
|
|
41
105
|
.action(async (src, dst) => {
|
|
42
106
|
try {
|
|
43
|
-
await handleStorageMv(src, dst);
|
|
107
|
+
await handleStorageMv(src, dst, { debug: isDebug() });
|
|
44
108
|
}
|
|
45
109
|
catch (error) {
|
|
46
110
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -55,7 +119,7 @@ export function registerStorageCommands(program) {
|
|
|
55
119
|
.option('--recursive', 'Delete recursively')
|
|
56
120
|
.action(async (path, options) => {
|
|
57
121
|
try {
|
|
58
|
-
await handleStorageRm(path, options);
|
|
122
|
+
await handleStorageRm(path, { ...options, debug: isDebug() });
|
|
59
123
|
}
|
|
60
124
|
catch (error) {
|
|
61
125
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -82,6 +146,7 @@ async function handleStorageLs(path, options) {
|
|
|
82
146
|
endpoint: currentProfile.endpoint,
|
|
83
147
|
serviceRoleKey: credentials.serviceRoleKey,
|
|
84
148
|
tenantId: currentProfile.tenantId,
|
|
149
|
+
debug: options.debug,
|
|
85
150
|
});
|
|
86
151
|
// Parse path
|
|
87
152
|
if (!path) {
|
|
@@ -109,8 +174,11 @@ async function handleStorageLs(path, options) {
|
|
|
109
174
|
// List objects in bucket
|
|
110
175
|
const parts = path.split('/');
|
|
111
176
|
const bucket = parts[0];
|
|
177
|
+
// Keep the empty string: the Storage list schema requires `prefix`, so
|
|
178
|
+
// `prefix || undefined` made every bucket-root listing fail with HTTP 400.
|
|
112
179
|
const prefix = parts.slice(1).join('/');
|
|
113
|
-
const
|
|
180
|
+
const limit = parseLimit(options.limit);
|
|
181
|
+
const objects = await storageDriver.listObjects(bucket, prefix, limit);
|
|
114
182
|
if (options.json) {
|
|
115
183
|
console.log(JSON.stringify({ bucket, prefix, count: objects.length, objects }, null, 2));
|
|
116
184
|
}
|
|
@@ -130,9 +198,141 @@ async function handleStorageLs(path, options) {
|
|
|
130
198
|
}
|
|
131
199
|
}
|
|
132
200
|
async function handleStorageCp(src, dst, options) {
|
|
133
|
-
|
|
201
|
+
if (options.recursive) {
|
|
202
|
+
// Fail loudly: silently copying only the first entry would look like success.
|
|
203
|
+
throw new Error('storage cp --recursive is not implemented yet. Copy files one by one (directories are not traversed).');
|
|
204
|
+
}
|
|
205
|
+
const srcIsLocal = isLocalRef(src);
|
|
206
|
+
// A remote reference is always `bucket/key`, so a slash-free operand can only be
|
|
207
|
+
// a local file — including a download target that does not exist yet.
|
|
208
|
+
const dstIsLocal = isLocalRef(dst) || !dst.includes('/');
|
|
209
|
+
if (!srcIsLocal && !src.includes('/')) {
|
|
210
|
+
throw new Error(`Local source not found: ${src}. Remote references must look like bucket/key (e.g., bkt/a.txt).`);
|
|
211
|
+
}
|
|
212
|
+
if (srcIsLocal && dstIsLocal) {
|
|
213
|
+
if (!dst.includes('/')) {
|
|
214
|
+
throw new Error(`Destination "${dst}" is not a remote reference. Expected bucket/key (e.g., bkt/a.txt): Storage requires an object key.`);
|
|
215
|
+
}
|
|
216
|
+
throw new Error('Both source and destination are local paths; storage cp needs exactly one remote side (bucket/key).');
|
|
217
|
+
}
|
|
218
|
+
const storageDriver = await createStorageDriver(options.debug);
|
|
219
|
+
if (!srcIsLocal && !dstIsLocal) {
|
|
220
|
+
// Remote -> remote reuses the verified move endpoint; `POST /object/copy`
|
|
221
|
+
// exists in the driver but is not exercised by this deployment's regression.
|
|
222
|
+
const [srcBucket, srcPath] = splitRemote(src);
|
|
223
|
+
const [dstBucket, dstPath] = splitRemote(dst);
|
|
224
|
+
await storageDriver.move(srcBucket, srcPath, dstBucket, dstPath);
|
|
225
|
+
if (options.json) {
|
|
226
|
+
console.log(JSON.stringify({ operation: 'remote-copy', source: src, destination: dst, viaMove: true }, null, 2));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
console.log(formatSuccess(`Copied ${src} -> ${dst} (remote-to-remote reuses move; the source object is removed)`));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (srcIsLocal) {
|
|
234
|
+
await uploadLocalFile(storageDriver, src, dst, options);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
await downloadRemoteObject(storageDriver, src, dst, options);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Split `bucket/key/...`; an object key is mandatory (a bare bucket name is ambiguous). */
|
|
241
|
+
function splitRemote(ref) {
|
|
242
|
+
const parts = ref.split('/');
|
|
243
|
+
if (parts.length < 2 || parts[1] === '') {
|
|
244
|
+
throw new Error(`Remote path must include bucket and object key (e.g., bucket/path/to/file), got: ${ref}`);
|
|
245
|
+
}
|
|
246
|
+
return [parts[0], parts.slice(1).join('/')];
|
|
247
|
+
}
|
|
248
|
+
async function uploadLocalFile(storageDriver, localPath, dst, options) {
|
|
249
|
+
const [bucket, remotePath] = splitRemote(dst);
|
|
250
|
+
const info = await stat(localPath).catch(() => {
|
|
251
|
+
throw new Error(`Local source not found or not readable: ${localPath}`);
|
|
252
|
+
});
|
|
253
|
+
if (!info.isFile()) {
|
|
254
|
+
throw new Error(`Local source is not a file: ${localPath}. Directory upload needs --recursive, which is not implemented yet.`);
|
|
255
|
+
}
|
|
256
|
+
await assertBucketExists(storageDriver, bucket);
|
|
257
|
+
const contentType = options.contentType ?? inferContentType(localPath);
|
|
258
|
+
const absolute = resolve(localPath);
|
|
259
|
+
// Print the intent BEFORE the request so a slow/failed upload still shows what was sent.
|
|
260
|
+
console.log(`Uploading ${absolute} -> ${bucket}/${remotePath} (${info.size} bytes, Content-Type: ${contentType})`);
|
|
261
|
+
const result = await storageDriver.upload(bucket, remotePath, absolute, contentType);
|
|
262
|
+
if (options.json) {
|
|
263
|
+
console.log(JSON.stringify({
|
|
264
|
+
operation: 'upload',
|
|
265
|
+
source: absolute,
|
|
266
|
+
bucket,
|
|
267
|
+
key: remotePath,
|
|
268
|
+
bytes: info.size,
|
|
269
|
+
contentType,
|
|
270
|
+
result,
|
|
271
|
+
}, null, 2));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
console.log(formatSuccess(`Uploaded ${info.size} byte(s) to ${bucket}/${remotePath}`));
|
|
275
|
+
if (result.Key) {
|
|
276
|
+
// Multi-tenant: the server prefixes the stored key with `<tenantId>/` itself.
|
|
277
|
+
console.log(` Key: ${result.Key}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function downloadRemoteObject(storageDriver, src, localPath, options) {
|
|
281
|
+
const [bucket, remotePath] = splitRemote(src);
|
|
282
|
+
const absolute = resolve(localPath);
|
|
283
|
+
const existing = await stat(absolute).catch(() => undefined);
|
|
284
|
+
if (existing?.isDirectory()) {
|
|
285
|
+
throw new Error(`Local destination is a directory: ${absolute}. Provide a file path.`);
|
|
286
|
+
}
|
|
287
|
+
console.log(`Downloading ${bucket}/${remotePath} -> ${absolute}`);
|
|
288
|
+
const bytes = await storageDriver.download(bucket, remotePath, absolute);
|
|
289
|
+
if (options.json) {
|
|
290
|
+
console.log(JSON.stringify({
|
|
291
|
+
operation: 'download',
|
|
292
|
+
source: `${bucket}/${remotePath}`,
|
|
293
|
+
destination: absolute,
|
|
294
|
+
bytes,
|
|
295
|
+
}, null, 2));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
console.log(formatSuccess(`Downloaded ${bytes} byte(s) to ${absolute}`));
|
|
299
|
+
}
|
|
300
|
+
/** The CLI has no bucket-create command, so point the user at the way out instead of a bare 404. */
|
|
301
|
+
async function assertBucketExists(storageDriver, bucket) {
|
|
302
|
+
const buckets = await storageDriver.listBuckets();
|
|
303
|
+
if (buckets.some((b) => b.name === bucket))
|
|
304
|
+
return;
|
|
305
|
+
const known = buckets.map((b) => b.name).join(', ') || '(none)';
|
|
306
|
+
throw new Error(`Bucket "${bucket}" does not exist. Create it first (POST /storage/v1/bucket with {"name":"${bucket}"}); known buckets: ${known}`);
|
|
307
|
+
}
|
|
308
|
+
async function createStorageDriver(debug) {
|
|
309
|
+
const profileManager = new ProfileManager();
|
|
310
|
+
const currentProfile = await profileManager.getCurrentProfile();
|
|
311
|
+
if (!currentProfile) {
|
|
312
|
+
throw new Error('No active profile. Use "pxbaas login" to authenticate first.');
|
|
313
|
+
}
|
|
314
|
+
const credentialStore = new CredentialStore();
|
|
315
|
+
const credentials = await credentialStore.getCredentials(currentProfile.name);
|
|
316
|
+
if (!credentials) {
|
|
317
|
+
throw new Error(`No credentials found for profile: ${currentProfile.name}. Use "pxbaas login" to authenticate.`);
|
|
318
|
+
}
|
|
319
|
+
return new StorageDriver({
|
|
320
|
+
endpoint: currentProfile.endpoint,
|
|
321
|
+
serviceRoleKey: credentials.serviceRoleKey,
|
|
322
|
+
tenantId: currentProfile.tenantId,
|
|
323
|
+
debug,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function parseLimit(raw) {
|
|
327
|
+
if (raw === undefined)
|
|
328
|
+
return 1000;
|
|
329
|
+
const limit = Number(raw);
|
|
330
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
331
|
+
throw new Error(`--limit must be a positive integer, got: ${raw}`);
|
|
332
|
+
}
|
|
333
|
+
return limit;
|
|
134
334
|
}
|
|
135
|
-
async function handleStorageMv(src, dst) {
|
|
335
|
+
async function handleStorageMv(src, dst, options = {}) {
|
|
136
336
|
// Get current profile
|
|
137
337
|
const profileManager = new ProfileManager();
|
|
138
338
|
const currentProfile = await profileManager.getCurrentProfile();
|
|
@@ -160,6 +360,7 @@ async function handleStorageMv(src, dst) {
|
|
|
160
360
|
endpoint: currentProfile.endpoint,
|
|
161
361
|
serviceRoleKey: credentials.serviceRoleKey,
|
|
162
362
|
tenantId: currentProfile.tenantId,
|
|
363
|
+
debug: options.debug,
|
|
163
364
|
});
|
|
164
365
|
// Move object
|
|
165
366
|
await storageDriver.move(srcBucket, srcPath, dstBucket, dstPath);
|
|
@@ -190,6 +391,7 @@ async function handleStorageRm(path, options) {
|
|
|
190
391
|
endpoint: currentProfile.endpoint,
|
|
191
392
|
serviceRoleKey: credentials.serviceRoleKey,
|
|
192
393
|
tenantId: currentProfile.tenantId,
|
|
394
|
+
debug: options.debug,
|
|
193
395
|
});
|
|
194
396
|
// Delete object
|
|
195
397
|
await storageDriver.delete(bucket, objectPath);
|