dataconv-client-sdk-ts 0.3.1 → 0.4.1
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 +358 -57
- package/dist/DataConvClient.d.ts +23 -1
- package/dist/DataConvClient.d.ts.map +1 -1
- package/dist/DataConvClient.js +206 -12
- package/dist/DataConvClient.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +860 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workbook-inspection.d.ts +4 -0
- package/dist/workbook-inspection.d.ts.map +1 -0
- package/dist/workbook-inspection.js +43 -0
- package/dist/workbook-inspection.js.map +1 -0
- package/package.json +9 -5
package/dist/cli.js
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash, createHmac, randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { DataConvClient } from './DataConvClient.js';
|
|
7
|
+
function getArgValue(args, flag) {
|
|
8
|
+
const index = args.indexOf(flag);
|
|
9
|
+
if (index < 0)
|
|
10
|
+
return undefined;
|
|
11
|
+
return args[index + 1];
|
|
12
|
+
}
|
|
13
|
+
function hasFlag(args, flag) {
|
|
14
|
+
return args.includes(flag);
|
|
15
|
+
}
|
|
16
|
+
function usage() {
|
|
17
|
+
return [
|
|
18
|
+
'Usage: dataconv <command> [options]',
|
|
19
|
+
'',
|
|
20
|
+
'Commands:',
|
|
21
|
+
' dataconv login --id-token <jwt> [--base-url <url>] [--tenant-id <id>] [--software-id <id>]',
|
|
22
|
+
' dataconv exchange --scope "dataconv.upload" [--vp-token <jwt>] [--client-assertion <jwt>]',
|
|
23
|
+
' [--api-key <key>] [--organization <org>] [--organization-did <did:web:...>] ',
|
|
24
|
+
' [--service-id <publisher-token-exchange|...>] [--operational-subject <did>]',
|
|
25
|
+
' dataconv upload <ruta.xlsx> [--scope "dataconv.upload"] [--mapping-json <path>] [--header-row-index <n>] [--output-json <path>]',
|
|
26
|
+
' dataconv search --resource-type <FHIRType> [--scope "dataconv.read"] [--params <json>] [--output-json <path>]',
|
|
27
|
+
' dataconv patch --thid <thid> [--resource-type <FHIRType>] [--scope "dataconv.patch"] [--output-json <path>]',
|
|
28
|
+
' dataconv batch --thid <thid> [--resource-type <FHIRType>] [--scope "dataconv.batch"] [--output-json <path>]',
|
|
29
|
+
' dataconv api-key-create --email <email> --target <endpointId> [--scope <scope1,scope2>] [--instrument <json>]',
|
|
30
|
+
' dataconv whoami',
|
|
31
|
+
'',
|
|
32
|
+
'Common options:',
|
|
33
|
+
' --dataspace-name <name> Default: DATACONV_DATASPACE_NAME or GLOBAL-DATACARE',
|
|
34
|
+
' --base-url <url> Default: DATACONV_BASE_URL or http://localhost:8080',
|
|
35
|
+
' --issuer-did <did> Default: DATACONV_ISSUER_DID or did:web:globaldatacare.es:employee:loader',
|
|
36
|
+
' --tenant-id <tenant> Default: DATACONV_TENANT_ID or tenant-a',
|
|
37
|
+
' --jurisdiction <code> Default: DATACONV_JURISDICTION or es',
|
|
38
|
+
' --sector <sector> Default: DATACONV_SECTOR or onehealth-research',
|
|
39
|
+
' --software-id <software> Default: DATACONV_SOFTWARE_ID or qvet',
|
|
40
|
+
' --resource-type <type> Default: DATACONV_RESOURCE_TYPE or Composition',
|
|
41
|
+
' --state-file <path> Default: ~/.dataconv/state.json',
|
|
42
|
+
' --api-key <key> Default: DATACONV_API_KEY',
|
|
43
|
+
' --help'
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
function commandHelp(command) {
|
|
47
|
+
const common = [
|
|
48
|
+
'Common options:',
|
|
49
|
+
' --dataspace-name <name> Default: DATACONV_DATASPACE_NAME or GLOBAL-DATACARE',
|
|
50
|
+
' --base-url <url> Default: DATACONV_BASE_URL or http://localhost:8080',
|
|
51
|
+
' --issuer-did <did> Default: DATACONV_ISSUER_DID or did:web:globaldatacare.es:employee:loader',
|
|
52
|
+
' --tenant-id <tenant> Default: DATACONV_TENANT_ID or tenant-a',
|
|
53
|
+
' --jurisdiction <code> Default: DATACONV_JURISDICTION or es',
|
|
54
|
+
' --sector <sector> Default: DATACONV_SECTOR or onehealth-research',
|
|
55
|
+
' --software-id <software> Default: DATACONV_SOFTWARE_ID or qvet',
|
|
56
|
+
' --resource-type <type> Default: DATACONV_RESOURCE_TYPE or Composition',
|
|
57
|
+
' --state-file <path> Default: ~/.dataconv/state.json',
|
|
58
|
+
' --api-key <key> Default: DATACONV_API_KEY',
|
|
59
|
+
].join('\n');
|
|
60
|
+
const exchange = [
|
|
61
|
+
'Command: exchange',
|
|
62
|
+
' dataconv exchange --scope <scope> [--organization <org>] [--organization-did <did:web:...>] [--service-id <id>] [--operational-subject <did>]',
|
|
63
|
+
'',
|
|
64
|
+
'Notes:',
|
|
65
|
+
' - serviceId recomendado: #identity:openid:token:_exchange',
|
|
66
|
+
' - Respeta contrato OpenAPI: el payload de /exchange NO se altera con service-id ni fallbacks.',
|
|
67
|
+
' - --organization-did y --service-id se guardan en estado CLI para resolución de endpoints fuera de payload.',
|
|
68
|
+
' - DID document objetivo para pruebas: <did-web>/.well-known/did.json',
|
|
69
|
+
'',
|
|
70
|
+
'Fallback env vars (localhost testing):',
|
|
71
|
+
' PUBLISHER_OPENID_EXCHANGE',
|
|
72
|
+
' PUBLISHER_DATASET_UPDATE',
|
|
73
|
+
' PUBLISHER_DATASET_PATCH',
|
|
74
|
+
' PUBLISHER_DATASET_BATCH',
|
|
75
|
+
' PUBLISHER_DATASET_SEARCH',
|
|
76
|
+
].join('\n');
|
|
77
|
+
const upload = [
|
|
78
|
+
'Command: upload',
|
|
79
|
+
' dataconv upload <ruta.xlsx> [--scope <scope>] [--mapping-json <path>] [--header-row-index <n>] [--output-json <path>]',
|
|
80
|
+
'',
|
|
81
|
+
'Expected serviceId: #dataset:{softwareId}:{resourceType}:_update',
|
|
82
|
+
'Response endpoint: se toma del header Location (publisher-dataset-update-response).',
|
|
83
|
+
].join('\n');
|
|
84
|
+
const patch = [
|
|
85
|
+
'Command: patch',
|
|
86
|
+
' dataconv patch --thid <thid> [--resource-type <FHIRType>] [--scope <scope>] [--output-json <path>]',
|
|
87
|
+
'',
|
|
88
|
+
'Expected serviceId: #dataset:{softwareId}:{resourceType}:_patch',
|
|
89
|
+
].join('\n');
|
|
90
|
+
const batch = [
|
|
91
|
+
'Command: batch',
|
|
92
|
+
' dataconv batch --thid <thid> [--resource-type <FHIRType>] [--scope <scope>] [--output-json <path>]',
|
|
93
|
+
'',
|
|
94
|
+
'Expected serviceId: #dataset:{softwareId}:{resourceType}:_batch',
|
|
95
|
+
].join('\n');
|
|
96
|
+
const search = [
|
|
97
|
+
'Command: search',
|
|
98
|
+
' dataconv search --resource-type <FHIRType> [--scope <scope>] [--params <json>] [--output-json <path>]',
|
|
99
|
+
'',
|
|
100
|
+
'Expected serviceId: #dataset:api:{resourceType}:_search',
|
|
101
|
+
].join('\n');
|
|
102
|
+
const login = [
|
|
103
|
+
'Command: login',
|
|
104
|
+
' dataconv login --id-token <jwt> [--base-url <url>] [--tenant-id <id>] [--software-id <id>] [--vp-token <jwt>]',
|
|
105
|
+
].join('\n');
|
|
106
|
+
const apiKeyCreate = [
|
|
107
|
+
'Command: api-key-create',
|
|
108
|
+
' dataconv api-key-create --email <email> --target <endpointId> [--scope <scope1,scope2>] [--instrument <json>]',
|
|
109
|
+
].join('\n');
|
|
110
|
+
const whoami = [
|
|
111
|
+
'Command: whoami',
|
|
112
|
+
' dataconv whoami',
|
|
113
|
+
].join('\n');
|
|
114
|
+
const byCommand = {
|
|
115
|
+
login,
|
|
116
|
+
exchange,
|
|
117
|
+
upload,
|
|
118
|
+
patch,
|
|
119
|
+
batch,
|
|
120
|
+
search,
|
|
121
|
+
'api-key-create': apiKeyCreate,
|
|
122
|
+
whoami,
|
|
123
|
+
};
|
|
124
|
+
const key = String(command || '').trim();
|
|
125
|
+
if (!key) {
|
|
126
|
+
return `${usage()}\n\n${common}\n\nUse: dataconv help <command>`;
|
|
127
|
+
}
|
|
128
|
+
return `${byCommand[key] || `Comando no soportado: ${key}`}\n\n${common}`;
|
|
129
|
+
}
|
|
130
|
+
function parseObjectJson(value, fieldName) {
|
|
131
|
+
const raw = String(value || '').trim();
|
|
132
|
+
if (!raw)
|
|
133
|
+
return undefined;
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
throw new Error('debe ser un objeto JSON');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new Error(`${fieldName} inválido: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function getDataspaceProfilesFromEnv() {
|
|
146
|
+
const raw = String(process.env.DATACONV_DATASPACE_PROFILES || '').trim();
|
|
147
|
+
if (!raw)
|
|
148
|
+
return {};
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(raw);
|
|
151
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
152
|
+
return {};
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function resolveDataspaceName(args, currentState) {
|
|
160
|
+
return (getArgValue(args, '--dataspace-name')
|
|
161
|
+
|| process.env.DATACONV_DATASPACE_NAME
|
|
162
|
+
|| currentState?.dataspaceName
|
|
163
|
+
|| 'GLOBAL-DATACARE').trim();
|
|
164
|
+
}
|
|
165
|
+
function resolveDataspaceProfile(args, currentState) {
|
|
166
|
+
const dataspaceName = resolveDataspaceName(args, currentState);
|
|
167
|
+
const profiles = getDataspaceProfilesFromEnv();
|
|
168
|
+
const profile = profiles[dataspaceName] || {};
|
|
169
|
+
return { dataspaceName, profile };
|
|
170
|
+
}
|
|
171
|
+
function nowStamp() {
|
|
172
|
+
return new Date().toISOString();
|
|
173
|
+
}
|
|
174
|
+
function logInfo(message) {
|
|
175
|
+
console.log(`[${nowStamp()}] INFO ${message}`);
|
|
176
|
+
}
|
|
177
|
+
function logSuccess(message) {
|
|
178
|
+
console.log(`[${nowStamp()}] INFO ✔ ${message}`);
|
|
179
|
+
}
|
|
180
|
+
function parseHeaderRowIndex(value) {
|
|
181
|
+
const raw = String(value || '').trim();
|
|
182
|
+
if (!raw)
|
|
183
|
+
return undefined;
|
|
184
|
+
const parsed = Number(raw);
|
|
185
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
186
|
+
throw new Error('--header-row-index debe ser un entero >= 1');
|
|
187
|
+
}
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
function toMappingConfigPayload(value) {
|
|
191
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
192
|
+
throw new Error('mapping-json debe ser un objeto JSON');
|
|
193
|
+
}
|
|
194
|
+
const obj = value;
|
|
195
|
+
if (obj.mappingConfig && typeof obj.mappingConfig === 'object' && !Array.isArray(obj.mappingConfig)) {
|
|
196
|
+
return obj.mappingConfig;
|
|
197
|
+
}
|
|
198
|
+
if (obj.schemaConfig && typeof obj.schemaConfig === 'object' && !Array.isArray(obj.schemaConfig)) {
|
|
199
|
+
return obj.schemaConfig;
|
|
200
|
+
}
|
|
201
|
+
if (obj.fieldMap && typeof obj.fieldMap === 'object' && !Array.isArray(obj.fieldMap)) {
|
|
202
|
+
return {
|
|
203
|
+
...obj,
|
|
204
|
+
fieldMap: obj.fieldMap
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
fieldMap: obj
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async function loadMappingConfigFromFile(filePath, headerRowIndex) {
|
|
212
|
+
const absolutePath = path.resolve(filePath);
|
|
213
|
+
const raw = await readFile(absolutePath, 'utf-8');
|
|
214
|
+
let parsed;
|
|
215
|
+
try {
|
|
216
|
+
parsed = JSON.parse(raw);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
throw new Error(`No se pudo parsear mapping-json: ${error instanceof Error ? error.message : String(error)}`);
|
|
220
|
+
}
|
|
221
|
+
const mappingConfig = toMappingConfigPayload(parsed);
|
|
222
|
+
if (headerRowIndex !== undefined) {
|
|
223
|
+
mappingConfig.headerRowIndex = headerRowIndex;
|
|
224
|
+
}
|
|
225
|
+
if (!mappingConfig.fieldMap || typeof mappingConfig.fieldMap !== 'object' || Array.isArray(mappingConfig.fieldMap)) {
|
|
226
|
+
throw new Error('mapping-json debe incluir fieldMap (objeto clave=campo API, valor=columna Excel)');
|
|
227
|
+
}
|
|
228
|
+
return mappingConfig;
|
|
229
|
+
}
|
|
230
|
+
function parseCsv(value) {
|
|
231
|
+
return String(value || '')
|
|
232
|
+
.split(',')
|
|
233
|
+
.map((item) => item.trim())
|
|
234
|
+
.filter(Boolean);
|
|
235
|
+
}
|
|
236
|
+
function defaultStatePath(explicitPath) {
|
|
237
|
+
const value = String(explicitPath || '').trim();
|
|
238
|
+
if (value)
|
|
239
|
+
return path.resolve(value);
|
|
240
|
+
return path.join(homedir(), '.dataconv', 'state.json');
|
|
241
|
+
}
|
|
242
|
+
function parseCommonArgs(args, base) {
|
|
243
|
+
const { dataspaceName, profile } = resolveDataspaceProfile(args, base);
|
|
244
|
+
return {
|
|
245
|
+
dataspaceName,
|
|
246
|
+
baseUrl: getArgValue(args, '--base-url') || base?.baseUrl || String(profile.baseUrl || '').trim() || process.env.DATACONV_BASE_URL || 'http://localhost:8080',
|
|
247
|
+
issuerDid: getArgValue(args, '--issuer-did') || base?.issuerDid || String(profile.issuerDid || '').trim() || process.env.DATACONV_ISSUER_DID || 'did:web:globaldatacare.es:employee:loader',
|
|
248
|
+
tenantId: getArgValue(args, '--tenant-id') || base?.tenantId || String(profile.tenantId || '').trim() || process.env.DATACONV_TENANT_ID || 'tenant-a',
|
|
249
|
+
jurisdiction: getArgValue(args, '--jurisdiction') || base?.jurisdiction || String(profile.jurisdiction || '').trim() || process.env.DATACONV_JURISDICTION || 'es',
|
|
250
|
+
sector: getArgValue(args, '--sector') || base?.sector || String(profile.sector || '').trim() || process.env.DATACONV_SECTOR || 'onehealth-research',
|
|
251
|
+
softwareId: getArgValue(args, '--software-id') || base?.softwareId || String(profile.softwareId || '').trim() || process.env.DATACONV_SOFTWARE_ID || 'qvet',
|
|
252
|
+
resourceType: getArgValue(args, '--resource-type') || base?.resourceType || String(profile.resourceType || '').trim() || process.env.DATACONV_RESOURCE_TYPE || 'Composition',
|
|
253
|
+
idToken: base?.idToken,
|
|
254
|
+
vpToken: base?.vpToken,
|
|
255
|
+
sessionToken: base?.sessionToken,
|
|
256
|
+
sessionScope: base?.sessionScope,
|
|
257
|
+
sessionExpiresAt: base?.sessionExpiresAt,
|
|
258
|
+
subject: base?.subject,
|
|
259
|
+
organization: base?.organization,
|
|
260
|
+
organizationDid: base?.organizationDid,
|
|
261
|
+
serviceId: getArgValue(args, '--service-id') || base?.serviceId || process.env.DATACONV_SERVICE_ID || undefined,
|
|
262
|
+
publisherTokenExchangeFallback: base?.publisherTokenExchangeFallback || process.env.PUBLISHER_OPENID_EXCHANGE || undefined,
|
|
263
|
+
publisherDatasetUpdateFallback: base?.publisherDatasetUpdateFallback || process.env.PUBLISHER_DATASET_UPDATE || undefined,
|
|
264
|
+
publisherDatasetPatchFallback: base?.publisherDatasetPatchFallback || process.env.PUBLISHER_DATASET_PATCH || undefined,
|
|
265
|
+
publisherDatasetBatchFallback: base?.publisherDatasetBatchFallback || process.env.PUBLISHER_DATASET_BATCH || undefined,
|
|
266
|
+
publisherDatasetSearchFallback: base?.publisherDatasetSearchFallback || process.env.PUBLISHER_DATASET_SEARCH || undefined,
|
|
267
|
+
apiKey: getArgValue(args, '--api-key') || base?.apiKey || process.env.DATACONV_API_KEY || undefined
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function loadState(statePath) {
|
|
271
|
+
try {
|
|
272
|
+
const raw = await readFile(statePath, 'utf-8');
|
|
273
|
+
const parsed = JSON.parse(raw);
|
|
274
|
+
return parsed;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function saveState(statePath, state) {
|
|
281
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
282
|
+
await writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
283
|
+
}
|
|
284
|
+
function decodeJwtPayload(token) {
|
|
285
|
+
const parts = String(token || '').split('.');
|
|
286
|
+
if (parts.length !== 3)
|
|
287
|
+
return {};
|
|
288
|
+
try {
|
|
289
|
+
const padded = parts[1] + '='.repeat((4 - (parts[1].length % 4)) % 4);
|
|
290
|
+
const json = Buffer.from(padded.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
|
|
291
|
+
const parsed = JSON.parse(json);
|
|
292
|
+
return typeof parsed === 'object' && parsed ? parsed : {};
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function normalizeEmail(value) {
|
|
299
|
+
return String(value || '').trim().toLowerCase();
|
|
300
|
+
}
|
|
301
|
+
function hashEmailSameAs(email) {
|
|
302
|
+
return createHash('sha256').update(normalizeEmail(email), 'utf-8').digest('hex').toLowerCase();
|
|
303
|
+
}
|
|
304
|
+
function jwtHs256(payload, secret, kid) {
|
|
305
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
306
|
+
if (kid)
|
|
307
|
+
header.kid = kid;
|
|
308
|
+
const headerRaw = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64url');
|
|
309
|
+
const payloadRaw = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url');
|
|
310
|
+
const signingInput = `${headerRaw}.${payloadRaw}`;
|
|
311
|
+
const signature = createHmac('sha256', secret).update(signingInput, 'utf-8').digest('base64url');
|
|
312
|
+
return `${signingInput}.${signature}`;
|
|
313
|
+
}
|
|
314
|
+
function buildDevVpToken(idToken, holderDid) {
|
|
315
|
+
const idClaims = decodeJwtPayload(idToken);
|
|
316
|
+
const email = String(idClaims.email || idClaims.preferred_username || idClaims.upn || '').trim();
|
|
317
|
+
if (!email) {
|
|
318
|
+
throw new Error('No email found in id_token; provide --vp-token manually or DATACONV_VP_TOKEN');
|
|
319
|
+
}
|
|
320
|
+
const now = Math.floor(Date.now() / 1000);
|
|
321
|
+
const vc = {
|
|
322
|
+
credentialSubject: {
|
|
323
|
+
id: process.env.DATACONV_OPERATIONAL_SUBJECT_DID || holderDid,
|
|
324
|
+
sameAs: hashEmailSameAs(email),
|
|
325
|
+
organization: process.env.DATACONV_ORGANIZATION || '',
|
|
326
|
+
scopes: ['dataconv.upload', 'dataconv.read']
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
return jwtHs256({
|
|
330
|
+
iss: holderDid,
|
|
331
|
+
sub: holderDid,
|
|
332
|
+
jti: randomUUID(),
|
|
333
|
+
iat: now,
|
|
334
|
+
exp: now + 300,
|
|
335
|
+
vp: {
|
|
336
|
+
holder: holderDid,
|
|
337
|
+
verifiableCredential: [vc]
|
|
338
|
+
}
|
|
339
|
+
}, process.env.DATACONV_WALLET_SHARED_SECRET || 'dev-wallet-secret', process.env.DATACONV_WALLET_KID || 'wallet-key-1');
|
|
340
|
+
}
|
|
341
|
+
function buildClientAssertion(baseUrl, holderDid, vpToken) {
|
|
342
|
+
const vpClaims = decodeJwtPayload(vpToken);
|
|
343
|
+
const now = Math.floor(Date.now() / 1000);
|
|
344
|
+
return jwtHs256({
|
|
345
|
+
iss: holderDid,
|
|
346
|
+
sub: holderDid,
|
|
347
|
+
aud: `${baseUrl.replace(/\/+$/, '')}/exchange`,
|
|
348
|
+
iat: now,
|
|
349
|
+
exp: now + 300,
|
|
350
|
+
jti: randomUUID(),
|
|
351
|
+
vp_jti: String(vpClaims.jti || '')
|
|
352
|
+
}, process.env.DATACONV_WALLET_SHARED_SECRET || 'dev-wallet-secret', process.env.DATACONV_WALLET_KID || 'wallet-key-1');
|
|
353
|
+
}
|
|
354
|
+
function isSessionValid(state) {
|
|
355
|
+
if (!state?.sessionToken || !state.sessionExpiresAt)
|
|
356
|
+
return false;
|
|
357
|
+
return state.sessionExpiresAt > Date.now() + 5000;
|
|
358
|
+
}
|
|
359
|
+
async function cmdLogin(args, statePath, currentState) {
|
|
360
|
+
const base = parseCommonArgs(args, currentState);
|
|
361
|
+
const idToken = getArgValue(args, '--id-token') || process.env.DATACONV_ID_TOKEN || currentState?.idToken;
|
|
362
|
+
if (!idToken) {
|
|
363
|
+
throw new Error('login requiere --id-token o DATACONV_ID_TOKEN');
|
|
364
|
+
}
|
|
365
|
+
const vpToken = getArgValue(args, '--vp-token') || process.env.DATACONV_VP_TOKEN || currentState?.vpToken;
|
|
366
|
+
const state = {
|
|
367
|
+
...base,
|
|
368
|
+
idToken,
|
|
369
|
+
vpToken,
|
|
370
|
+
sessionToken: undefined,
|
|
371
|
+
sessionScope: undefined,
|
|
372
|
+
sessionExpiresAt: undefined,
|
|
373
|
+
apiKey: base.apiKey,
|
|
374
|
+
};
|
|
375
|
+
await saveState(statePath, state);
|
|
376
|
+
const claims = decodeJwtPayload(idToken);
|
|
377
|
+
const email = String(claims.email || claims.preferred_username || claims.upn || '').trim();
|
|
378
|
+
logInfo(`Identidad preparada para ${state.dataspaceName || 'GLOBAL-DATACARE'}`);
|
|
379
|
+
if (email) {
|
|
380
|
+
logSuccess(`Usuario autenticado localmente: ${email}`);
|
|
381
|
+
}
|
|
382
|
+
console.log(`Login guardado en ${statePath}`);
|
|
383
|
+
}
|
|
384
|
+
async function cmdExchange(args, statePath, currentState) {
|
|
385
|
+
const state = parseCommonArgs(args, currentState);
|
|
386
|
+
if (!state.idToken) {
|
|
387
|
+
throw new Error('No hay id_token local. Ejecuta primero dataconv login.');
|
|
388
|
+
}
|
|
389
|
+
const scope = getArgValue(args, '--scope') || 'dataconv.upload';
|
|
390
|
+
const holderDid = process.env.DATACONV_WALLET_DID || state.issuerDid;
|
|
391
|
+
const vpToken = getArgValue(args, '--vp-token') || process.env.DATACONV_VP_TOKEN || state.vpToken || buildDevVpToken(state.idToken, holderDid);
|
|
392
|
+
const clientAssertion = getArgValue(args, '--client-assertion') || process.env.DATACONV_CLIENT_ASSERTION || buildClientAssertion(state.baseUrl, holderDid, vpToken);
|
|
393
|
+
const organization = getArgValue(args, '--organization') || process.env.DATACONV_ORGANIZATION || state.organization || '';
|
|
394
|
+
const organizationDid = getArgValue(args, '--organization-did') || process.env.DATACONV_ORGANIZATION_DID || state.organizationDid || '';
|
|
395
|
+
const serviceId = getArgValue(args, '--service-id') || process.env.DATACONV_SERVICE_ID || state.serviceId || '';
|
|
396
|
+
const operationalSubject = getArgValue(args, '--operational-subject') || process.env.DATACONV_OPERATIONAL_SUBJECT_DID || state.subject || '';
|
|
397
|
+
const client = new DataConvClient({
|
|
398
|
+
issuerDid: state.issuerDid,
|
|
399
|
+
alternateName: state.tenantId,
|
|
400
|
+
tenantId: state.tenantId,
|
|
401
|
+
jurisdiction: state.jurisdiction,
|
|
402
|
+
sector: state.sector,
|
|
403
|
+
baseUrl: state.baseUrl,
|
|
404
|
+
idToken: state.idToken,
|
|
405
|
+
vpToken
|
|
406
|
+
});
|
|
407
|
+
const exchanged = await client.exchangeToken({
|
|
408
|
+
subjectToken: state.idToken,
|
|
409
|
+
vpToken,
|
|
410
|
+
clientAssertion,
|
|
411
|
+
scope,
|
|
412
|
+
apiKey: state.apiKey,
|
|
413
|
+
organization,
|
|
414
|
+
operationalSubject
|
|
415
|
+
});
|
|
416
|
+
const expiresAt = Date.now() + Number(exchanged.expires_in || 0) * 1000;
|
|
417
|
+
const nextState = {
|
|
418
|
+
...state,
|
|
419
|
+
vpToken,
|
|
420
|
+
sessionToken: String(exchanged.access_token || ''),
|
|
421
|
+
sessionScope: String(exchanged.scope || scope),
|
|
422
|
+
sessionExpiresAt: expiresAt,
|
|
423
|
+
subject: String(exchanged.subject || ''),
|
|
424
|
+
organization: String(exchanged.organization || ''),
|
|
425
|
+
organizationDid: organizationDid || undefined,
|
|
426
|
+
serviceId: serviceId || undefined,
|
|
427
|
+
publisherTokenExchangeFallback: state.publisherTokenExchangeFallback,
|
|
428
|
+
publisherDatasetUpdateFallback: state.publisherDatasetUpdateFallback,
|
|
429
|
+
publisherDatasetPatchFallback: state.publisherDatasetPatchFallback,
|
|
430
|
+
publisherDatasetBatchFallback: state.publisherDatasetBatchFallback,
|
|
431
|
+
publisherDatasetSearchFallback: state.publisherDatasetSearchFallback,
|
|
432
|
+
apiKey: state.apiKey,
|
|
433
|
+
};
|
|
434
|
+
await saveState(statePath, nextState);
|
|
435
|
+
logInfo(`Autenticando con ${state.dataspaceName || 'GLOBAL-DATACARE'} (OAuth 2.0)...`);
|
|
436
|
+
if (organizationDid) {
|
|
437
|
+
logInfo(`organizationDid=${organizationDid}`);
|
|
438
|
+
}
|
|
439
|
+
if (serviceId) {
|
|
440
|
+
logInfo(`serviceId=${serviceId}`);
|
|
441
|
+
}
|
|
442
|
+
if (state.publisherTokenExchangeFallback) {
|
|
443
|
+
logInfo(`fallback token-exchange=${state.publisherTokenExchangeFallback}`);
|
|
444
|
+
}
|
|
445
|
+
logSuccess(`Token de sesión obtenido. scope=${nextState.sessionScope} exp=${new Date(expiresAt).toISOString()}`);
|
|
446
|
+
}
|
|
447
|
+
async function cmdUpload(args, statePath, currentState) {
|
|
448
|
+
const sourcePath = args[0];
|
|
449
|
+
if (!sourcePath || sourcePath.startsWith('--')) {
|
|
450
|
+
throw new Error('upload requiere ruta de archivo .xlsx');
|
|
451
|
+
}
|
|
452
|
+
let state = parseCommonArgs(args, currentState);
|
|
453
|
+
const requestedScope = getArgValue(args, '--scope') || `${state.resourceType}/_upload`;
|
|
454
|
+
if (!isSessionValid(state)) {
|
|
455
|
+
const exchangeArgs = ['--scope', requestedScope];
|
|
456
|
+
if (state.apiKey) {
|
|
457
|
+
exchangeArgs.push('--api-key', state.apiKey);
|
|
458
|
+
}
|
|
459
|
+
await cmdExchange(exchangeArgs, statePath, state);
|
|
460
|
+
state = (await loadState(statePath)) || state;
|
|
461
|
+
}
|
|
462
|
+
if (!state.sessionToken) {
|
|
463
|
+
throw new Error('No hay session token válido para upload');
|
|
464
|
+
}
|
|
465
|
+
const outputJson = getArgValue(args, '--output-json') || './artifacts/dataconv-upload-response-cli.json';
|
|
466
|
+
const mappingJsonPath = getArgValue(args, '--mapping-json');
|
|
467
|
+
const headerRowIndex = parseHeaderRowIndex(getArgValue(args, '--header-row-index'));
|
|
468
|
+
const fileBytes = new Uint8Array(await readFile(sourcePath));
|
|
469
|
+
const fileName = path.basename(sourcePath);
|
|
470
|
+
const fileSizeMb = (fileBytes.byteLength / (1024 * 1024)).toFixed(2);
|
|
471
|
+
const client = new DataConvClient({
|
|
472
|
+
issuerDid: state.issuerDid,
|
|
473
|
+
alternateName: state.tenantId,
|
|
474
|
+
tenantId: state.tenantId,
|
|
475
|
+
jurisdiction: state.jurisdiction,
|
|
476
|
+
sector: state.sector,
|
|
477
|
+
baseUrl: state.baseUrl,
|
|
478
|
+
idToken: state.idToken,
|
|
479
|
+
vpToken: state.vpToken,
|
|
480
|
+
retryTimes: Number(process.env.DATACONV_RETRY_TIMES || 60),
|
|
481
|
+
retryDelayMs: Number(process.env.DATACONV_RETRY_DELAY_MS || 2000)
|
|
482
|
+
});
|
|
483
|
+
if (mappingJsonPath) {
|
|
484
|
+
logInfo('Validando y aplicando mapping de configuración...');
|
|
485
|
+
const mappingConfig = await loadMappingConfigFromFile(mappingJsonPath, headerRowIndex);
|
|
486
|
+
const configResponse = await client.createTenantConfigAndWait({
|
|
487
|
+
tenantId: state.tenantId,
|
|
488
|
+
jurisdiction: state.jurisdiction,
|
|
489
|
+
sector: state.sector,
|
|
490
|
+
softwareId: state.softwareId,
|
|
491
|
+
iss: state.issuerDid,
|
|
492
|
+
idToken: state.idToken,
|
|
493
|
+
vpToken: state.vpToken,
|
|
494
|
+
entries: [
|
|
495
|
+
{
|
|
496
|
+
softwareId: state.softwareId,
|
|
497
|
+
config: {
|
|
498
|
+
mappingConfig,
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
});
|
|
503
|
+
const configSummaryIssue = client.getMainIssueDescriptionByResponse(configResponse)
|
|
504
|
+
|| client.getMainDiagnosticInfoByResponse(configResponse)
|
|
505
|
+
|| '';
|
|
506
|
+
console.log(`Config mapping aplicada para softwareId=${state.softwareId}`);
|
|
507
|
+
if (configSummaryIssue) {
|
|
508
|
+
console.log(`Config summary: ${configSummaryIssue}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
logInfo(`Subiendo dataset a ${state.dataspaceName || 'GLOBAL-DATACARE'} (${fileName}, ${fileSizeMb} MB)...`);
|
|
512
|
+
const uploadResult = await client.uploadWithFile({
|
|
513
|
+
softwareId: state.softwareId,
|
|
514
|
+
resourceType: state.resourceType,
|
|
515
|
+
fileBytes,
|
|
516
|
+
fileName,
|
|
517
|
+
authorizationToken: state.sessionToken,
|
|
518
|
+
idToken: state.idToken,
|
|
519
|
+
vpToken: state.vpToken,
|
|
520
|
+
iss: state.issuerDid,
|
|
521
|
+
});
|
|
522
|
+
logSuccess(`Upload aceptado. thid=${uploadResult.thid}`);
|
|
523
|
+
logInfo('Esperando resultado de conversión (_upload-response)...');
|
|
524
|
+
const response = await client.pollUploadResponse({
|
|
525
|
+
softwareId: state.softwareId,
|
|
526
|
+
resourceType: state.resourceType,
|
|
527
|
+
thid: uploadResult.thid,
|
|
528
|
+
authorizationToken: state.sessionToken,
|
|
529
|
+
idToken: state.idToken,
|
|
530
|
+
vpToken: state.vpToken,
|
|
531
|
+
iss: state.issuerDid,
|
|
532
|
+
});
|
|
533
|
+
const outputPath = path.resolve(outputJson);
|
|
534
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
535
|
+
await writeFile(outputPath, JSON.stringify(response, null, 2), 'utf-8');
|
|
536
|
+
const summaryIssue = client.getMainIssueDescriptionByResponse(response)
|
|
537
|
+
|| client.getMainDiagnosticInfoByResponse(response)
|
|
538
|
+
|| '';
|
|
539
|
+
logSuccess('PUBLICACIÓN COMPLETADA — dataset procesado');
|
|
540
|
+
console.log(`Upload thid=${uploadResult.thid}`);
|
|
541
|
+
console.log(`Respuesta guardada en ${outputPath}`);
|
|
542
|
+
console.log(`Resumen: ${summaryIssue}`);
|
|
543
|
+
}
|
|
544
|
+
async function cmdSearch(args, statePath, currentState) {
|
|
545
|
+
let state = parseCommonArgs(args, currentState);
|
|
546
|
+
const resourceType = getArgValue(args, '--resource-type') || state.resourceType || 'DocumentReference';
|
|
547
|
+
const requestedScope = getArgValue(args, '--scope') || `${resourceType}/_search`;
|
|
548
|
+
if (!isSessionValid(state)) {
|
|
549
|
+
const exchangeArgs = ['--scope', requestedScope];
|
|
550
|
+
if (state.apiKey) {
|
|
551
|
+
exchangeArgs.push('--api-key', state.apiKey);
|
|
552
|
+
}
|
|
553
|
+
await cmdExchange(exchangeArgs, statePath, state);
|
|
554
|
+
state = (await loadState(statePath)) || state;
|
|
555
|
+
}
|
|
556
|
+
if (!state.sessionToken) {
|
|
557
|
+
throw new Error('No hay session token válido para search');
|
|
558
|
+
}
|
|
559
|
+
const params = parseObjectJson(getArgValue(args, '--params'), '--params') || {};
|
|
560
|
+
const outputJson = getArgValue(args, '--output-json');
|
|
561
|
+
const client = new DataConvClient({
|
|
562
|
+
issuerDid: state.issuerDid,
|
|
563
|
+
alternateName: state.tenantId,
|
|
564
|
+
tenantId: state.tenantId,
|
|
565
|
+
jurisdiction: state.jurisdiction,
|
|
566
|
+
sector: state.sector,
|
|
567
|
+
baseUrl: state.baseUrl,
|
|
568
|
+
idToken: state.idToken,
|
|
569
|
+
vpToken: state.vpToken,
|
|
570
|
+
});
|
|
571
|
+
logInfo(`Buscando ${resourceType} en ${state.dataspaceName || 'GLOBAL-DATACARE'}...`);
|
|
572
|
+
const bundle = await client.searchResources({
|
|
573
|
+
tenantId: state.tenantId,
|
|
574
|
+
jurisdiction: state.jurisdiction,
|
|
575
|
+
sector: state.sector,
|
|
576
|
+
resourceType,
|
|
577
|
+
authorizationToken: state.sessionToken,
|
|
578
|
+
searchParams: params,
|
|
579
|
+
});
|
|
580
|
+
const total = Number(bundle?.total || 0);
|
|
581
|
+
const entries = Array.isArray(bundle?.entry) ? bundle.entry.length : 0;
|
|
582
|
+
logSuccess(`Búsqueda completada. total=${total} entries=${entries}`);
|
|
583
|
+
if (outputJson) {
|
|
584
|
+
const outputPath = path.resolve(outputJson);
|
|
585
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
586
|
+
await writeFile(outputPath, JSON.stringify(bundle, null, 2), 'utf-8');
|
|
587
|
+
console.log(`Resultado guardado en ${outputPath}`);
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
console.log(JSON.stringify({ resourceType, total, entries }, null, 2));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async function cmdPatch(args, statePath, currentState) {
|
|
594
|
+
let state = parseCommonArgs(args, currentState);
|
|
595
|
+
const thid = getArgValue(args, '--thid');
|
|
596
|
+
if (!thid) {
|
|
597
|
+
throw new Error('patch requiere --thid (obtenido de la respuesta de upload)');
|
|
598
|
+
}
|
|
599
|
+
const resourceType = getArgValue(args, '--resource-type') || state.resourceType || 'excel';
|
|
600
|
+
const requestedScope = getArgValue(args, '--scope') || `${resourceType}/_patch`;
|
|
601
|
+
if (!isSessionValid(state)) {
|
|
602
|
+
const exchangeArgs = ['--scope', requestedScope];
|
|
603
|
+
if (state.apiKey)
|
|
604
|
+
exchangeArgs.push('--api-key', state.apiKey);
|
|
605
|
+
await cmdExchange(exchangeArgs, statePath, state);
|
|
606
|
+
state = (await loadState(statePath)) || state;
|
|
607
|
+
}
|
|
608
|
+
if (!state.sessionToken) {
|
|
609
|
+
throw new Error('No hay session token válido para patch');
|
|
610
|
+
}
|
|
611
|
+
const outputJson = getArgValue(args, '--output-json');
|
|
612
|
+
const client = new DataConvClient({
|
|
613
|
+
issuerDid: state.issuerDid,
|
|
614
|
+
alternateName: state.tenantId,
|
|
615
|
+
tenantId: state.tenantId,
|
|
616
|
+
jurisdiction: state.jurisdiction,
|
|
617
|
+
sector: state.sector,
|
|
618
|
+
baseUrl: state.baseUrl,
|
|
619
|
+
idToken: state.idToken,
|
|
620
|
+
vpToken: state.vpToken,
|
|
621
|
+
});
|
|
622
|
+
logInfo(`Confirmando conversión (_patch) thid=${thid}...`);
|
|
623
|
+
const response = await client.patchConversion({
|
|
624
|
+
softwareId: state.softwareId,
|
|
625
|
+
resourceType,
|
|
626
|
+
thid,
|
|
627
|
+
authorizationToken: state.sessionToken,
|
|
628
|
+
idToken: state.idToken,
|
|
629
|
+
vpToken: state.vpToken,
|
|
630
|
+
iss: state.issuerDid,
|
|
631
|
+
});
|
|
632
|
+
const status = String(response?.body?.status || '');
|
|
633
|
+
const promoted = Number(response?.body?.promotedCount ?? response?.body?.total ?? 0);
|
|
634
|
+
logSuccess(`Patch completado. status=${status} promotedCount=${promoted}`);
|
|
635
|
+
if (outputJson) {
|
|
636
|
+
const outputPath = path.resolve(outputJson);
|
|
637
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
638
|
+
await writeFile(outputPath, JSON.stringify(response, null, 2), 'utf-8');
|
|
639
|
+
console.log(`Resultado guardado en ${outputPath}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function cmdBatch(args, statePath, currentState) {
|
|
643
|
+
let state = parseCommonArgs(args, currentState);
|
|
644
|
+
const thid = getArgValue(args, '--thid');
|
|
645
|
+
if (!thid) {
|
|
646
|
+
throw new Error('batch requiere --thid (obtenido de la respuesta de upload)');
|
|
647
|
+
}
|
|
648
|
+
const resourceType = getArgValue(args, '--resource-type') || state.resourceType || 'excel';
|
|
649
|
+
const requestedScope = getArgValue(args, '--scope') || `${resourceType}/_batch`;
|
|
650
|
+
if (!isSessionValid(state)) {
|
|
651
|
+
const exchangeArgs = ['--scope', requestedScope];
|
|
652
|
+
if (state.apiKey)
|
|
653
|
+
exchangeArgs.push('--api-key', state.apiKey);
|
|
654
|
+
await cmdExchange(exchangeArgs, statePath, state);
|
|
655
|
+
state = (await loadState(statePath)) || state;
|
|
656
|
+
}
|
|
657
|
+
if (!state.sessionToken) {
|
|
658
|
+
throw new Error('No hay session token válido para batch');
|
|
659
|
+
}
|
|
660
|
+
const outputJson = getArgValue(args, '--output-json');
|
|
661
|
+
const client = new DataConvClient({
|
|
662
|
+
issuerDid: state.issuerDid,
|
|
663
|
+
alternateName: state.tenantId,
|
|
664
|
+
tenantId: state.tenantId,
|
|
665
|
+
jurisdiction: state.jurisdiction,
|
|
666
|
+
sector: state.sector,
|
|
667
|
+
baseUrl: state.baseUrl,
|
|
668
|
+
idToken: state.idToken,
|
|
669
|
+
vpToken: state.vpToken,
|
|
670
|
+
});
|
|
671
|
+
logInfo(`Confirmando conversión (_batch) thid=${thid}...`);
|
|
672
|
+
const response = await client.batchPromotion({
|
|
673
|
+
softwareId: state.softwareId,
|
|
674
|
+
resourceType,
|
|
675
|
+
thid,
|
|
676
|
+
authorizationToken: state.sessionToken,
|
|
677
|
+
idToken: state.idToken,
|
|
678
|
+
vpToken: state.vpToken,
|
|
679
|
+
iss: state.issuerDid,
|
|
680
|
+
});
|
|
681
|
+
const status = String(response?.body?.status || '');
|
|
682
|
+
const promoted = Number(response?.body?.promotedCount ?? response?.body?.total ?? 0);
|
|
683
|
+
logSuccess(`Batch completado. status=${status} promotedCount=${promoted}`);
|
|
684
|
+
if (outputJson) {
|
|
685
|
+
const outputPath = path.resolve(outputJson);
|
|
686
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
687
|
+
await writeFile(outputPath, JSON.stringify(response, null, 2), 'utf-8');
|
|
688
|
+
console.log(`Resultado guardado en ${outputPath}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async function cmdWhoami(statePath, currentState) {
|
|
692
|
+
const state = currentState || await loadState(statePath);
|
|
693
|
+
if (!state) {
|
|
694
|
+
console.log('Sin estado local');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
console.log(JSON.stringify({
|
|
698
|
+
dataspaceName: state.dataspaceName || '',
|
|
699
|
+
baseUrl: state.baseUrl,
|
|
700
|
+
tenantId: state.tenantId,
|
|
701
|
+
jurisdiction: state.jurisdiction,
|
|
702
|
+
sector: state.sector,
|
|
703
|
+
softwareId: state.softwareId,
|
|
704
|
+
resourceType: state.resourceType,
|
|
705
|
+
hasIdToken: Boolean(state.idToken),
|
|
706
|
+
hasVpToken: Boolean(state.vpToken),
|
|
707
|
+
hasSessionToken: Boolean(state.sessionToken),
|
|
708
|
+
sessionScope: state.sessionScope || '',
|
|
709
|
+
sessionExpiresAt: state.sessionExpiresAt ? new Date(state.sessionExpiresAt).toISOString() : '',
|
|
710
|
+
subject: state.subject || '',
|
|
711
|
+
organization: state.organization || '',
|
|
712
|
+
organizationDid: state.organizationDid || '',
|
|
713
|
+
serviceId: state.serviceId || '',
|
|
714
|
+
entityOpenidExchangeFallback: state.publisherTokenExchangeFallback || '',
|
|
715
|
+
publisherDatasetUpdateFallback: state.publisherDatasetUpdateFallback || '',
|
|
716
|
+
publisherDatasetPatchFallback: state.publisherDatasetPatchFallback || '',
|
|
717
|
+
publisherDatasetBatchFallback: state.publisherDatasetBatchFallback || '',
|
|
718
|
+
publisherDatasetSearchFallback: state.publisherDatasetSearchFallback || '',
|
|
719
|
+
hasApiKey: Boolean(state.apiKey)
|
|
720
|
+
}, null, 2));
|
|
721
|
+
}
|
|
722
|
+
async function cmdApiKeyCreate(args, statePath, currentState) {
|
|
723
|
+
let state = parseCommonArgs(args, currentState);
|
|
724
|
+
const email = String(getArgValue(args, '--email') || '').trim();
|
|
725
|
+
if (!email) {
|
|
726
|
+
throw new Error('api-key-create requiere --email');
|
|
727
|
+
}
|
|
728
|
+
const target = String(getArgValue(args, '--target') || '').trim();
|
|
729
|
+
if (!target) {
|
|
730
|
+
throw new Error('api-key-create requiere --target (endpointId/patrón de endpoint)');
|
|
731
|
+
}
|
|
732
|
+
const explicitScopes = parseCsv(getArgValue(args, '--scope'));
|
|
733
|
+
const scopes = explicitScopes.length > 0 ? explicitScopes : [target];
|
|
734
|
+
const instrumentRaw = String(getArgValue(args, '--instrument') || '').trim();
|
|
735
|
+
let instrument = {};
|
|
736
|
+
if (instrumentRaw) {
|
|
737
|
+
try {
|
|
738
|
+
const parsed = JSON.parse(instrumentRaw);
|
|
739
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
740
|
+
instrument = parsed;
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
throw new Error('instrument debe ser JSON objeto');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
throw new Error(`instrument inválido: ${error instanceof Error ? error.message : String(error)}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (!isSessionValid(state) || !state.sessionToken) {
|
|
751
|
+
await cmdExchange(['--scope', 'dataconv.tenant.keys.manage'], statePath, state);
|
|
752
|
+
state = (await loadState(statePath)) || state;
|
|
753
|
+
}
|
|
754
|
+
if (!state.sessionToken) {
|
|
755
|
+
throw new Error('No hay session token válido para gestionar API keys');
|
|
756
|
+
}
|
|
757
|
+
const client = new DataConvClient({
|
|
758
|
+
issuerDid: state.issuerDid,
|
|
759
|
+
alternateName: state.tenantId,
|
|
760
|
+
tenantId: state.tenantId,
|
|
761
|
+
jurisdiction: state.jurisdiction,
|
|
762
|
+
sector: state.sector,
|
|
763
|
+
baseUrl: state.baseUrl,
|
|
764
|
+
idToken: state.idToken,
|
|
765
|
+
vpToken: state.vpToken,
|
|
766
|
+
});
|
|
767
|
+
const result = await client.createTenantApiKeyActions({
|
|
768
|
+
tenantId: state.tenantId,
|
|
769
|
+
jurisdiction: state.jurisdiction,
|
|
770
|
+
sector: state.sector,
|
|
771
|
+
authorizationToken: state.sessionToken,
|
|
772
|
+
actions: [
|
|
773
|
+
{
|
|
774
|
+
'@context': 'https://schema.org',
|
|
775
|
+
'@type': 'UpdateAction',
|
|
776
|
+
agent: { email },
|
|
777
|
+
target,
|
|
778
|
+
scope: scopes,
|
|
779
|
+
instrument,
|
|
780
|
+
actionStatus: 'active',
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
});
|
|
784
|
+
const first = Array.isArray(result.data) ? result.data[0] : undefined;
|
|
785
|
+
const apiKey = String(first?.resource?.apiKey || '');
|
|
786
|
+
if (!apiKey) {
|
|
787
|
+
throw new Error('Respuesta sin apiKey');
|
|
788
|
+
}
|
|
789
|
+
const nextState = {
|
|
790
|
+
...state,
|
|
791
|
+
apiKey,
|
|
792
|
+
};
|
|
793
|
+
await saveState(statePath, nextState);
|
|
794
|
+
console.log(JSON.stringify({
|
|
795
|
+
identifier: first?.identifier || '',
|
|
796
|
+
actionStatus: first?.actionStatus || '',
|
|
797
|
+
sameAs: first?.agent?.sameAs || '',
|
|
798
|
+
target: first?.target || target,
|
|
799
|
+
scope: first?.scope || scopes,
|
|
800
|
+
apiKey,
|
|
801
|
+
}, null, 2));
|
|
802
|
+
}
|
|
803
|
+
async function main() {
|
|
804
|
+
const argv = process.argv.slice(2);
|
|
805
|
+
if (!argv.length) {
|
|
806
|
+
console.log(commandHelp());
|
|
807
|
+
process.exit(0);
|
|
808
|
+
}
|
|
809
|
+
const command = argv[0];
|
|
810
|
+
const args = argv.slice(1);
|
|
811
|
+
if (command === 'help') {
|
|
812
|
+
console.log(commandHelp(args[0]));
|
|
813
|
+
process.exit(0);
|
|
814
|
+
}
|
|
815
|
+
if (hasFlag(args, '--help')) {
|
|
816
|
+
console.log(commandHelp(command));
|
|
817
|
+
process.exit(0);
|
|
818
|
+
}
|
|
819
|
+
const statePath = defaultStatePath(getArgValue(argv, '--state-file'));
|
|
820
|
+
const state = await loadState(statePath);
|
|
821
|
+
if (command === 'login') {
|
|
822
|
+
await cmdLogin(args, statePath, state);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (command === 'exchange') {
|
|
826
|
+
await cmdExchange(args, statePath, state);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (command === 'upload') {
|
|
830
|
+
await cmdUpload(args, statePath, state);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (command === 'search') {
|
|
834
|
+
await cmdSearch(args, statePath, state);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (command === 'patch') {
|
|
838
|
+
await cmdPatch(args, statePath, state);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (command === 'batch') {
|
|
842
|
+
await cmdBatch(args, statePath, state);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (command === 'api-key-create') {
|
|
846
|
+
await cmdApiKeyCreate(args, statePath, state);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (command === 'whoami') {
|
|
850
|
+
await cmdWhoami(statePath, state);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
throw new Error(`Comando no soportado: ${command}`);
|
|
854
|
+
}
|
|
855
|
+
main().catch((error) => {
|
|
856
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
857
|
+
console.error(message);
|
|
858
|
+
process.exit(1);
|
|
859
|
+
});
|
|
860
|
+
//# sourceMappingURL=cli.js.map
|