@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.
Files changed (115) hide show
  1. package/README.md +116 -13
  2. package/dist/app/local-index.d.ts +70 -0
  3. package/dist/app/local-index.d.ts.map +1 -0
  4. package/dist/app/local-index.js +140 -0
  5. package/dist/app/local-index.js.map +1 -0
  6. package/dist/app/preflight.d.ts +24 -1
  7. package/dist/app/preflight.d.ts.map +1 -1
  8. package/dist/app/preflight.js +47 -11
  9. package/dist/app/preflight.js.map +1 -1
  10. package/dist/app/spec-lint.d.ts.map +1 -1
  11. package/dist/app/spec-lint.js +16 -0
  12. package/dist/app/spec-lint.js.map +1 -1
  13. package/dist/cli/root.d.ts.map +1 -1
  14. package/dist/cli/root.js +3 -0
  15. package/dist/cli/root.js.map +1 -1
  16. package/dist/commands/app/index.d.ts.map +1 -1
  17. package/dist/commands/app/index.js +428 -44
  18. package/dist/commands/app/index.js.map +1 -1
  19. package/dist/commands/completion/index.d.ts.map +1 -1
  20. package/dist/commands/completion/index.js +5 -1
  21. package/dist/commands/completion/index.js.map +1 -1
  22. package/dist/commands/data/index.d.ts +52 -0
  23. package/dist/commands/data/index.d.ts.map +1 -1
  24. package/dist/commands/data/index.js +201 -39
  25. package/dist/commands/data/index.js.map +1 -1
  26. package/dist/commands/db/index.d.ts +14 -0
  27. package/dist/commands/db/index.d.ts.map +1 -1
  28. package/dist/commands/db/index.js +151 -40
  29. package/dist/commands/db/index.js.map +1 -1
  30. package/dist/commands/functions/new.d.ts +4 -0
  31. package/dist/commands/functions/new.d.ts.map +1 -1
  32. package/dist/commands/functions/new.js +24 -9
  33. package/dist/commands/functions/new.js.map +1 -1
  34. package/dist/commands/gen/index.d.ts.map +1 -1
  35. package/dist/commands/gen/index.js +13 -10
  36. package/dist/commands/gen/index.js.map +1 -1
  37. package/dist/commands/login/index.d.ts.map +1 -1
  38. package/dist/commands/login/index.js +15 -30
  39. package/dist/commands/login/index.js.map +1 -1
  40. package/dist/commands/profile/import-env.d.ts +7 -2
  41. package/dist/commands/profile/import-env.d.ts.map +1 -1
  42. package/dist/commands/profile/import-env.js +141 -16
  43. package/dist/commands/profile/import-env.js.map +1 -1
  44. package/dist/commands/profile/index.d.ts.map +1 -1
  45. package/dist/commands/profile/index.js +13 -1
  46. package/dist/commands/profile/index.js.map +1 -1
  47. package/dist/commands/storage/index.d.ts.map +1 -1
  48. package/dist/commands/storage/index.js +209 -7
  49. package/dist/commands/storage/index.js.map +1 -1
  50. package/dist/commands/tenant/index.d.ts +19 -0
  51. package/dist/commands/tenant/index.d.ts.map +1 -0
  52. package/dist/commands/tenant/index.js +662 -0
  53. package/dist/commands/tenant/index.js.map +1 -0
  54. package/dist/commands/whoami/index.js +19 -0
  55. package/dist/commands/whoami/index.js.map +1 -1
  56. package/dist/credentials/credential-store.d.ts +30 -1
  57. package/dist/credentials/credential-store.d.ts.map +1 -1
  58. package/dist/credentials/credential-store.js +114 -12
  59. package/dist/credentials/credential-store.js.map +1 -1
  60. package/dist/credentials/env-file.d.ts +48 -7
  61. package/dist/credentials/env-file.d.ts.map +1 -1
  62. package/dist/credentials/env-file.js +91 -16
  63. package/dist/credentials/env-file.js.map +1 -1
  64. package/dist/credentials/jwt-introspect.d.ts +7 -0
  65. package/dist/credentials/jwt-introspect.d.ts.map +1 -1
  66. package/dist/credentials/jwt-introspect.js.map +1 -1
  67. package/dist/credentials/masker.d.ts +3 -0
  68. package/dist/credentials/masker.d.ts.map +1 -1
  69. package/dist/credentials/masker.js +3 -0
  70. package/dist/credentials/masker.js.map +1 -1
  71. package/dist/credentials/profile.d.ts +12 -0
  72. package/dist/credentials/profile.d.ts.map +1 -1
  73. package/dist/credentials/profile.js +23 -0
  74. package/dist/credentials/profile.js.map +1 -1
  75. package/dist/credentials/tty-guard.d.ts +10 -0
  76. package/dist/credentials/tty-guard.d.ts.map +1 -1
  77. package/dist/credentials/tty-guard.js +32 -0
  78. package/dist/credentials/tty-guard.js.map +1 -1
  79. package/dist/db/introspect.d.ts +54 -0
  80. package/dist/db/introspect.d.ts.map +1 -0
  81. package/dist/db/introspect.js +140 -0
  82. package/dist/db/introspect.js.map +1 -0
  83. package/dist/main.js +21 -2
  84. package/dist/main.js.map +1 -1
  85. package/dist/tenant/bundle.d.ts +66 -0
  86. package/dist/tenant/bundle.d.ts.map +1 -0
  87. package/dist/tenant/bundle.js +132 -0
  88. package/dist/tenant/bundle.js.map +1 -0
  89. package/dist/tenant/guide.d.ts +50 -0
  90. package/dist/tenant/guide.d.ts.map +1 -0
  91. package/dist/tenant/guide.js +152 -0
  92. package/dist/tenant/guide.js.map +1 -0
  93. package/dist/tenant/probe.d.ts +65 -0
  94. package/dist/tenant/probe.d.ts.map +1 -0
  95. package/dist/tenant/probe.js +159 -0
  96. package/dist/tenant/probe.js.map +1 -0
  97. package/dist/tenant/verify.d.ts +37 -0
  98. package/dist/tenant/verify.d.ts.map +1 -0
  99. package/dist/tenant/verify.js +132 -0
  100. package/dist/tenant/verify.js.map +1 -0
  101. package/dist/version.d.ts +29 -0
  102. package/dist/version.d.ts.map +1 -0
  103. package/dist/version.js +65 -0
  104. package/dist/version.js.map +1 -0
  105. package/package.json +2 -2
  106. package/skills/pxbaas-app-spec/references/runtime-contract.md +2 -2
  107. package/skills/pxbaas-cli/SKILL.md +14 -5
  108. package/skills/pxbaas-cli/references/auth-and-credentials.md +19 -3
  109. package/skills/pxbaas-cli/references/data-postgrest.md +19 -3
  110. package/skills/pxbaas-cli/references/db-and-meta.md +5 -0
  111. package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
  112. package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
  113. package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
  114. package/templates/webapp/react-vite-hono/backend/index.ts +21 -4
  115. 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 文件导入单租户实例凭据并落库为 profile,
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
- * 多租户实例暂不支持(env 文件只有 global keys)——见 resolveLoginFromEnv 的显式报错。
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 { parseEnvText, resolveLoginFromEnv, commitProfileCredentials, formatSuccess, formatError, } from '../../credentials/index.js';
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 a single-tenant instance profile from a .env file (non-interactive)')
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
- let text;
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 > 全局 --profile > 'default'
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":"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"}
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;AAQpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiD7D"}
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;AASpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+D9D"}
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, { ...options, json: options.json || program.opts().json });
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, options);
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 objects = await storageDriver.listObjects(bucket, prefix || undefined);
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
- throw new Error('storage cp not yet implemented');
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);