latinfo 0.20.4 → 0.20.6
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/dist/index.js +111 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1587,6 +1587,7 @@ DATA SOURCES
|
|
|
1587
1587
|
latinfo co rues registry --search <query>
|
|
1588
1588
|
|
|
1589
1589
|
COMMANDS
|
|
1590
|
+
sources List all data sources with stats
|
|
1590
1591
|
login [--token <github_pat>] GitHub OAuth or PAT login
|
|
1591
1592
|
logout Remove credentials
|
|
1592
1593
|
whoami Show authenticated user
|
|
@@ -4038,6 +4039,100 @@ function requireTeamAdmin() {
|
|
|
4038
4039
|
process.exit(1);
|
|
4039
4040
|
}
|
|
4040
4041
|
}
|
|
4042
|
+
// --- Imports Status ---
|
|
4043
|
+
async function importsStatus() {
|
|
4044
|
+
const { execSync: exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
4045
|
+
const ghRepo = 'carrerahaus/latinfo-api';
|
|
4046
|
+
// Get recent workflow runs
|
|
4047
|
+
const runsJson = exec(`gh run list --repo ${ghRepo} --workflow=import.yml --limit=5 --json databaseId,status,conclusion,createdAt`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
4048
|
+
const runs = JSON.parse(runsJson);
|
|
4049
|
+
if (runs.length === 0) {
|
|
4050
|
+
console.log('No recent import runs.');
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
console.log('\n IMPORT STATUS\n');
|
|
4054
|
+
for (const run of runs) {
|
|
4055
|
+
const jobsJson = exec(`gh run view ${run.databaseId} --repo ${ghRepo} --json jobs --jq '.jobs[] | "\\(.name)|\\(.status)|\\(.conclusion // "")"'`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
4056
|
+
const age = Math.floor((Date.now() - new Date(run.createdAt).getTime()) / 60000);
|
|
4057
|
+
const ageStr = age < 60 ? `${age}m ago` : `${Math.floor(age / 60)}h ago`;
|
|
4058
|
+
const runIcon = run.status === 'completed' ? (run.conclusion === 'success' ? '✅' : '❌') : '🔄';
|
|
4059
|
+
console.log(` ${runIcon} Run #${run.databaseId} (${ageStr})`);
|
|
4060
|
+
for (const line of jobsJson.split('\n')) {
|
|
4061
|
+
if (!line || line.includes('setup'))
|
|
4062
|
+
continue;
|
|
4063
|
+
const [name, status, conclusion] = line.split('|');
|
|
4064
|
+
const icon = status === 'completed' ? (conclusion === 'success' ? ' ✅' : ' ❌') :
|
|
4065
|
+
status === 'in_progress' ? ' 🔄' :
|
|
4066
|
+
status === 'queued' ? ' ⏳' : ' ⬜';
|
|
4067
|
+
const label = status === 'in_progress' ? 'running' :
|
|
4068
|
+
status === 'queued' ? 'queued' :
|
|
4069
|
+
conclusion || status;
|
|
4070
|
+
console.log(` ${icon} ${name.padEnd(25)} ${label}`);
|
|
4071
|
+
}
|
|
4072
|
+
console.log();
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
// --- Sources ---
|
|
4076
|
+
async function sourcesCmd() {
|
|
4077
|
+
// Get import metadata dynamically from admin endpoint
|
|
4078
|
+
let data = [];
|
|
4079
|
+
try {
|
|
4080
|
+
const adminSecret = requireAdmin();
|
|
4081
|
+
const res = await fetch(`${API_URL}/admin/imports`, { headers: { Authorization: `Bearer ${adminSecret}` } });
|
|
4082
|
+
if (res.ok)
|
|
4083
|
+
data = await res.json();
|
|
4084
|
+
}
|
|
4085
|
+
catch {
|
|
4086
|
+
// Not admin — try reading source YAMLs from repo
|
|
4087
|
+
}
|
|
4088
|
+
if (data.length === 0) {
|
|
4089
|
+
// Fallback: read sources/*.yaml from repo
|
|
4090
|
+
try {
|
|
4091
|
+
const repo = getRepoPath();
|
|
4092
|
+
const sourcesDir = path_1.default.join(repo, 'sources');
|
|
4093
|
+
const files = fs_1.default.readdirSync(sourcesDir).filter(f => f.endsWith('.yaml'));
|
|
4094
|
+
for (const f of files) {
|
|
4095
|
+
const content = fs_1.default.readFileSync(path_1.default.join(sourcesDir, f), 'utf-8');
|
|
4096
|
+
const name = f.replace('.yaml', '');
|
|
4097
|
+
const country = content.match(/country:\s*(\S+)/)?.[1] || '?';
|
|
4098
|
+
const institution = content.match(/institution:\s*(\S+)/)?.[1] || '?';
|
|
4099
|
+
const dataset = content.match(/dataset:\s*(\S+)/)?.[1] || '?';
|
|
4100
|
+
const idName = content.match(/name:\s*(\S+)/)?.[1] || 'id';
|
|
4101
|
+
data.push({ source: name, country, institution, dataset, idName, rows: null, ageHours: null });
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
catch { }
|
|
4105
|
+
}
|
|
4106
|
+
if (data.length === 0) {
|
|
4107
|
+
console.log('No sources found.');
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
console.log('\n SOURCES\n');
|
|
4111
|
+
console.log(` ${'Source'.padEnd(35)} ${'Records'.padStart(12)} ${'Updated'.padEnd(18)} Command`);
|
|
4112
|
+
console.log(` ${'─'.repeat(35)} ${'─'.repeat(12)} ${'─'.repeat(18)} ${'─'.repeat(30)}`);
|
|
4113
|
+
for (const s of data) {
|
|
4114
|
+
// Parse source name: pe-sunat-padron → country=pe, institution=sunat, dataset=padron
|
|
4115
|
+
const parts = s.source.split('-');
|
|
4116
|
+
const country = s.country || (parts[0] === 'pe' ? 'Peru' : parts[0] === 'co' ? 'Colombia' : parts[0]);
|
|
4117
|
+
const institution = s.institution || parts.slice(1, -1).join('-');
|
|
4118
|
+
const dataset = s.dataset || parts[parts.length - 1];
|
|
4119
|
+
const label = `${country} — ${institution.toUpperCase()} ${dataset}`;
|
|
4120
|
+
const rows = s.rows ? s.rows.toLocaleString() : '—';
|
|
4121
|
+
const age = s.ageHours != null ? (s.ageHours < 24 ? `${s.ageHours}h ago` : `${Math.floor(s.ageHours / 24)}d ago`) : '—';
|
|
4122
|
+
// Build route: pe-sunat-padron → pe/sunat/padron, co-rues → co/rues/registry
|
|
4123
|
+
const segs = s.source.split('-');
|
|
4124
|
+
let route;
|
|
4125
|
+
if (segs.length >= 3)
|
|
4126
|
+
route = `${segs[0]}/${segs.slice(1, -1).join('-')}/${segs[segs.length - 1]}`;
|
|
4127
|
+
else if (segs.length === 2)
|
|
4128
|
+
route = `${segs[0]}/${segs[1]}/registry`;
|
|
4129
|
+
else
|
|
4130
|
+
route = s.source;
|
|
4131
|
+
const idName = s.idName || 'id';
|
|
4132
|
+
console.log(` ${label.padEnd(35)} ${rows.padStart(12)} ${age.padEnd(18)} latinfo ${route} <${idName}>`);
|
|
4133
|
+
}
|
|
4134
|
+
console.log();
|
|
4135
|
+
}
|
|
4041
4136
|
// --- Team & Tasks ---
|
|
4042
4137
|
async function teamCmd(args) {
|
|
4043
4138
|
const sub = args[0];
|
|
@@ -4407,6 +4502,9 @@ else {
|
|
|
4407
4502
|
case 'users':
|
|
4408
4503
|
users().catch(e => { console.error(e); process.exit(1); });
|
|
4409
4504
|
break;
|
|
4505
|
+
case 'sources':
|
|
4506
|
+
sourcesCmd().catch(e => { console.error(e); process.exit(1); });
|
|
4507
|
+
break;
|
|
4410
4508
|
case 'plan':
|
|
4411
4509
|
plan().catch(e => { console.error(e); process.exit(1); });
|
|
4412
4510
|
break;
|
|
@@ -4415,13 +4513,19 @@ else {
|
|
|
4415
4513
|
break;
|
|
4416
4514
|
// Team-only commands
|
|
4417
4515
|
case 'imports':
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4516
|
+
if (args[0] === 'status') {
|
|
4517
|
+
requireTeam();
|
|
4518
|
+
importsStatus().catch(e => { console.error(e); process.exit(1); });
|
|
4519
|
+
}
|
|
4520
|
+
else {
|
|
4521
|
+
requireTeamAdmin();
|
|
4522
|
+
if (args[0] === 'run')
|
|
4523
|
+
importsRun(args[1] || 'all').catch(e => { console.error(e); process.exit(1); });
|
|
4524
|
+
else if (args[0] === 'report')
|
|
4525
|
+
importsReport(parseInt(args[1]) || 7).catch(e => { console.error(e); process.exit(1); });
|
|
4526
|
+
else
|
|
4527
|
+
imports().catch(e => { console.error(e); process.exit(1); });
|
|
4528
|
+
}
|
|
4425
4529
|
break;
|
|
4426
4530
|
case 'costs':
|
|
4427
4531
|
requireTeamAdmin();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latinfo",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
4
4
|
"description": "Tax registry & procurement API for Latin America. Query RUC, DNI, NIT, licitaciones from Peru & Colombia. Offline MPHF search, full OCDS data, updated daily.",
|
|
5
5
|
"homepage": "https://latinfo.dev",
|
|
6
6
|
"repository": {
|