wip importer
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
ba381636a0
commit
da856152c7
@@ -38,6 +38,7 @@
|
||||
"svix": "^1.24.0",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"url-metadata": "^4.1.0",
|
||||
"uuid": "^9.0.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
39
apps/api/src/controllers/import.controller.ts
Normal file
39
apps/api/src/controllers/import.controller.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { pathOr } from 'ramda';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { toDots } from '@openpanel/common';
|
||||
import type {
|
||||
IClickhouseEvent,
|
||||
IServiceCreateEventPayload,
|
||||
} from '@openpanel/db';
|
||||
import { ch, formatClickhouseDate } from '@openpanel/db';
|
||||
import type { PostEventPayload } from '@openpanel/sdk';
|
||||
|
||||
export async function importEvents(
|
||||
request: FastifyRequest<{
|
||||
Body: IClickhouseEvent[];
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
console.log('HERE?!', request.body.length);
|
||||
|
||||
const values: IClickhouseEvent[] = request.body.map((event) => {
|
||||
return {
|
||||
...event,
|
||||
project_id: request.client?.projectId ?? '',
|
||||
created_at: formatClickhouseDate(event.created_at),
|
||||
};
|
||||
});
|
||||
|
||||
const res = await ch.insert({
|
||||
table: 'events',
|
||||
values,
|
||||
format: 'JSONEachRow',
|
||||
clickhouse_settings: {
|
||||
date_time_input_format: 'best_effort',
|
||||
},
|
||||
});
|
||||
|
||||
reply.send('OK');
|
||||
}
|
||||
38
apps/api/src/routes/import.router.ts
Normal file
38
apps/api/src/routes/import.router.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as controller from '@/controllers/import.controller';
|
||||
import { validateImportRequest } from '@/utils/auth';
|
||||
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||
|
||||
import { Prisma } from '@openpanel/db';
|
||||
|
||||
const importRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||
fastify.addHook('preHandler', async (req: FastifyRequest, reply) => {
|
||||
try {
|
||||
const client = await validateImportRequest(req.headers);
|
||||
req.client = client;
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return reply.status(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'Client ID seems to be malformed',
|
||||
});
|
||||
} else if (e instanceof Error) {
|
||||
return reply
|
||||
.status(401)
|
||||
.send({ error: 'Unauthorized', message: e.message });
|
||||
}
|
||||
return reply
|
||||
.status(401)
|
||||
.send({ error: 'Unauthorized', message: 'Unexpected error' });
|
||||
}
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
url: '/events',
|
||||
handler: controller.importEvents,
|
||||
});
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
export default importRouter;
|
||||
@@ -128,6 +128,36 @@ export async function validateExportRequest(
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function validateImportRequest(
|
||||
headers: RawRequestDefaultExpression['headers']
|
||||
): Promise<IServiceClient> {
|
||||
const clientId = headers['openpanel-client-id'] as string;
|
||||
const clientSecret = (headers['openpanel-client-secret'] as string) || '';
|
||||
const client = await db.client.findUnique({
|
||||
where: {
|
||||
id: clientId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!client) {
|
||||
throw new Error('Import: Invalid client id');
|
||||
}
|
||||
|
||||
if (!client.secret) {
|
||||
throw new Error('Import: Client has no secret');
|
||||
}
|
||||
|
||||
if (client.type === ClientType.write) {
|
||||
throw new Error('Import: Client is not allowed to import');
|
||||
}
|
||||
|
||||
if (!(await verifyPassword(clientSecret, client.secret))) {
|
||||
throw new Error('Import: Invalid client secret');
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export function validateClerkJwt(token?: string) {
|
||||
if (!token) {
|
||||
return null;
|
||||
|
||||
41
packages/cli/package.json
Normal file
41
packages/cli/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@openpanel/cli",
|
||||
"version": "0.0.1-beta",
|
||||
"module": "index.ts",
|
||||
"bin": {
|
||||
"openpanel": "dist/bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsup",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"arg": "^5.0.2",
|
||||
"glob": "^10.4.3",
|
||||
"inquirer": "^9.3.5",
|
||||
"ramda": "^0.29.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openpanel/db": "workspace:^",
|
||||
"@openpanel/eslint-config": "workspace:*",
|
||||
"@openpanel/prettier-config": "workspace:*",
|
||||
"@openpanel/sdk": "workspace:*",
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/ramda": "^0.30.1",
|
||||
"eslint": "^8.48.0",
|
||||
"prettier": "^3.0.3",
|
||||
"tsup": "^7.2.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@openpanel/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@openpanel/prettier-config"
|
||||
}
|
||||
26
packages/cli/src/cli.ts
Normal file
26
packages/cli/src/cli.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import arg from 'arg';
|
||||
|
||||
import importer from './importer';
|
||||
|
||||
function cli() {
|
||||
const args = arg(
|
||||
{
|
||||
'--help': Boolean,
|
||||
},
|
||||
{
|
||||
permissive: true,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('cli args', args);
|
||||
|
||||
const [command] = args._;
|
||||
|
||||
switch (command) {
|
||||
case 'import': {
|
||||
return importer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cli();
|
||||
199
packages/cli/src/importer/index.ts
Normal file
199
packages/cli/src/importer/index.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import arg from 'arg';
|
||||
import { groupBy } from 'ramda';
|
||||
|
||||
import type { PostEventPayload } from '@openpanel/sdk';
|
||||
|
||||
const BATCH_SIZE = 10000; // Define your batch size
|
||||
const SLEEP_TIME = 100; // Define your sleep time between batches
|
||||
|
||||
function progress(value: string) {
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(value);
|
||||
}
|
||||
|
||||
function stripMixpanelProperties(obj: Record<string, unknown>) {
|
||||
const properties = ['time', 'distinct_id'];
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key in obj) {
|
||||
if (key.match(/^(\$|mp_)/) || properties.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
result[key] = obj[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function safeParse(json: string) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseFileContent(fileContent: string): {
|
||||
event: string;
|
||||
properties: {
|
||||
time: number;
|
||||
distinct_id?: string | number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}[] {
|
||||
try {
|
||||
return JSON.parse(fileContent);
|
||||
} catch (error) {
|
||||
const lines = fileContent.trim().split('\n');
|
||||
return lines
|
||||
.map((line, index) => {
|
||||
const json = safeParse(line);
|
||||
if (!json) {
|
||||
console.log('Warning: Failed to parse JSON');
|
||||
console.log('Index:', index);
|
||||
console.log('Line:', line);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
export default function importer() {
|
||||
const args = arg(
|
||||
{
|
||||
'--file': String,
|
||||
},
|
||||
{
|
||||
permissive: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!args['--file']) {
|
||||
throw new Error('Missing --file argument');
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
const filePath = path.resolve(cwd, args['--file']);
|
||||
const fileContent = parseFileContent(fs.readFileSync(filePath, 'utf-8'));
|
||||
|
||||
// const groups = groupBy((event) => event.properties.$device_id, fileContent);
|
||||
// const groupEntries = Object.entries(groups);
|
||||
|
||||
// const profiles = new Map<string, any[]>();
|
||||
|
||||
// for (const [deviceId, items] of Object.entries(groups)) {
|
||||
// items.forEach((item) => {
|
||||
// if (item.properties.distinct_id) {
|
||||
// if (!profiles.has(item.properties.distinct_id)) {
|
||||
// profiles.set(item.properties.distinct_id, []);
|
||||
// }
|
||||
// profiles.get(item.properties.distinct_id)!.push(item);
|
||||
// } else {
|
||||
// item.properties.$device_id
|
||||
// }
|
||||
// })
|
||||
// profiles.
|
||||
// }
|
||||
// console.log('Total:', groupEntries.length);
|
||||
// console.log('Undefined:', groups.undefined?.length ?? 0);
|
||||
|
||||
// const uniqueKeys = new Set<string>();
|
||||
// groups.undefined.forEach((event) => {
|
||||
// if (event.properties.distinct_id) {
|
||||
// console.log(event);
|
||||
// }
|
||||
// });
|
||||
|
||||
// 1: group by device id
|
||||
// 2: add session start, session end and populate session_id
|
||||
// 3: check if distinct_id exists on any event
|
||||
// - If it does, get all events with that distinct_id and NO device_id and within session_start and session_end
|
||||
// - add add the session_id to those events
|
||||
// 4: send all events to the server
|
||||
|
||||
const events: PostEventPayload[] = fileContent
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((event) => {
|
||||
if (event.properties.mp_lib === 'web') {
|
||||
console.log(event);
|
||||
}
|
||||
return {
|
||||
profileId: event.properties.distinct_id
|
||||
? String(event.properties.distinct_id)
|
||||
: undefined,
|
||||
name: event.event,
|
||||
timestamp: new Date(event.properties.time * 1000).toISOString(),
|
||||
properties: {
|
||||
__country: event.properties.country_code,
|
||||
__region: event.properties.$region,
|
||||
__city: event.properties.$city,
|
||||
__os: event.properties.$os,
|
||||
__browser: event.properties.$browser,
|
||||
__browser_version: event.properties.$browser_version,
|
||||
__referrer: event.properties.$referrer,
|
||||
__device_id: event.properties.$device_id,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(events.length / BATCH_SIZE);
|
||||
const estimatedTime = (totalPages / 8) * SLEEP_TIME + (totalPages / 8) * 80;
|
||||
console.log(`Estimated time: ${estimatedTime / 1000} seconds`);
|
||||
|
||||
async function batcher(page: number) {
|
||||
const batch = events.slice(page * BATCH_SIZE, (page + 1) * BATCH_SIZE);
|
||||
|
||||
if (batch.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size = Buffer.byteLength(JSON.stringify(batch));
|
||||
console.log(batch.length, size / (1024 * 1024));
|
||||
|
||||
// await fetch('http://localhost:3333/import/events', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'openpanel-client-id': 'dd3db204-dcf6-49e2-9e82-de01cba7e585',
|
||||
// 'openpanel-client-secret': 'sec_293b903816e327e10c9d',
|
||||
// },
|
||||
// body: JSON.stringify(batch),
|
||||
// });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));
|
||||
}
|
||||
|
||||
async function runBatchesInParallel(
|
||||
totalPages: number,
|
||||
concurrentBatches: number
|
||||
) {
|
||||
let currentPage = 0;
|
||||
|
||||
while (currentPage < totalPages) {
|
||||
const promises = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < concurrentBatches && currentPage < totalPages;
|
||||
i++, currentPage++
|
||||
) {
|
||||
console.log(`Sending batch ${currentPage}... %)`);
|
||||
promises.push(batcher(currentPage));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
console.log(totalPages);
|
||||
|
||||
// Trigger the batches
|
||||
try {
|
||||
runBatchesInParallel(totalPages, 8); // Run 8 batches in parallel
|
||||
} catch (e) {
|
||||
console.log('ERROR?!', e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
255
packages/cli/src/importer/load.ts
Normal file
255
packages/cli/src/importer/load.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import { glob } from 'glob';
|
||||
|
||||
import type { IClickhouseEvent } from '@openpanel/db';
|
||||
|
||||
const BATCH_SIZE = 8000; // Define your batch size
|
||||
const SLEEP_TIME = 100; // Define your sleep time between batches
|
||||
|
||||
function progress(value: string) {
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(value);
|
||||
}
|
||||
|
||||
function stripMixpanelProperties(obj: Record<string, unknown>) {
|
||||
const properties = ['time', 'distinct_id'];
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key in obj) {
|
||||
if (key.match(/^(\$|mp_)/) || properties.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
result[key] = obj[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function safeParse(json: string) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseFileContent(fileContent: string): {
|
||||
event: string;
|
||||
properties: {
|
||||
time: number;
|
||||
distinct_id?: string | number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}[] {
|
||||
try {
|
||||
return JSON.parse(fileContent);
|
||||
} catch (error) {
|
||||
const lines = fileContent.trim().split('\n');
|
||||
return lines
|
||||
.map((line, index) => {
|
||||
const json = safeParse(line);
|
||||
if (!json) {
|
||||
console.log('Warning: Failed to parse JSON');
|
||||
console.log('Index:', index);
|
||||
console.log('Line:', line);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadFilesBatcher() {
|
||||
const files = await glob(['../../../../Downloads/mp-data/*.txt'], {
|
||||
root: '/',
|
||||
});
|
||||
|
||||
function chunks(array: string[], size: number) {
|
||||
const results = [];
|
||||
while (array.length) {
|
||||
results.push(array.splice(0, size));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const times = [];
|
||||
const chunksArray = chunks(files, 5);
|
||||
let chunkIndex = 0;
|
||||
for (const chunk of chunksArray) {
|
||||
if (times.length > 0) {
|
||||
// Print out how much time is approximately left
|
||||
const average = times.reduce((a, b) => a + b) / times.length;
|
||||
const remaining = (chunksArray.length - chunkIndex) * average;
|
||||
console.log(`Estimated time left: ${remaining / 1000 / 60} minutes`);
|
||||
}
|
||||
console.log('Processing chunk:', chunkIndex);
|
||||
chunkIndex++;
|
||||
const d = Date.now();
|
||||
await loadFiles(chunk);
|
||||
times.push(Date.now() - d);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFiles(files: string[] = []) {
|
||||
const data: any[] = [];
|
||||
const filesToParse = files.slice(0, 10);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
filesToParse.forEach((file) => {
|
||||
const readStream = fs.createReadStream(file);
|
||||
const content: any[] = [];
|
||||
|
||||
readStream.on('data', (chunk) => {
|
||||
// console.log(`Received ${chunk.length} bytes of data.`);
|
||||
content.push(chunk.toString('utf-8'));
|
||||
});
|
||||
|
||||
readStream.on('end', () => {
|
||||
console.log('Finished reading file:', file);
|
||||
data.push(parseFileContent(content.join('')));
|
||||
if (data.length === filesToParse.length) {
|
||||
resolve(1);
|
||||
}
|
||||
});
|
||||
|
||||
readStream.on('error', (error) => {
|
||||
console.error('Error reading file:', error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const events: IClickhouseEvent[] = data.flat().map((event) => {
|
||||
if (event.properties.mp_lib === 'web') {
|
||||
return {
|
||||
profile_id: event.properties.distinct_id
|
||||
? String(event.properties.distinct_id)
|
||||
: '',
|
||||
name: event.event,
|
||||
created_at: new Date(event.properties.time * 1000).toISOString(),
|
||||
properties: stripMixpanelProperties(event.properties) as Record<
|
||||
string,
|
||||
string
|
||||
>,
|
||||
country: event.properties.country_code,
|
||||
region: event.properties.$region,
|
||||
city: event.properties.$city,
|
||||
os: event.properties.$os,
|
||||
browser: event.properties.$browser,
|
||||
browser_version: event.properties.$browser_version
|
||||
? String(event.properties.$browser_version)
|
||||
: '',
|
||||
referrer: event.properties.$initial_referrer,
|
||||
referrer_type: event.properties.$search_engine ? 'search' : '', // FIX (IN API)
|
||||
referrer_name: event.properties.$search_engine ?? '', // FIX (IN API)
|
||||
device_id: event.properties.$device_id,
|
||||
session_id: '',
|
||||
project_id: '', // FIX (IN API)
|
||||
path: event.properties.$current_url, // FIX
|
||||
origin: '', // FIX (IN API)
|
||||
os_version: '', // FIX
|
||||
model: '',
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
id: randomUUID(),
|
||||
duration: 0,
|
||||
device: '', // FIX
|
||||
brand: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
profile_id: event.properties.distinct_id
|
||||
? String(event.properties.distinct_id)
|
||||
: '',
|
||||
name: event.event,
|
||||
created_at: new Date(event.properties.time * 1000).toISOString(),
|
||||
properties: stripMixpanelProperties(event.properties) as Record<
|
||||
string,
|
||||
string
|
||||
>,
|
||||
country: event.properties.country_code ?? '',
|
||||
region: event.properties.$region ?? '',
|
||||
city: event.properties.$city ?? '',
|
||||
os: event.properties.$os ?? '',
|
||||
browser: event.properties.$browser ?? '',
|
||||
browser_version: event.properties.$browser_version
|
||||
? String(event.properties.$browser_version)
|
||||
: '',
|
||||
referrer: event.properties.$initial_referrer ?? '',
|
||||
referrer_type: event.properties.$search_engine ? 'search' : '', // FIX (IN API)
|
||||
referrer_name: event.properties.$search_engine ?? '', // FIX (IN API)
|
||||
device_id: event.properties.$device_id ?? '',
|
||||
session_id: '',
|
||||
project_id: '', // FIX (IN API)
|
||||
path: event.properties.$current_url ?? '', // FIX
|
||||
origin: '', // FIX (IN API)
|
||||
os_version: '', // FIX
|
||||
model: '',
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
id: randomUUID(),
|
||||
duration: 0,
|
||||
device: '', // FIX
|
||||
brand: '',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(events.length / BATCH_SIZE);
|
||||
const estimatedTime = (totalPages / 8) * SLEEP_TIME + (totalPages / 8) * 80;
|
||||
console.log(`Estimated time: ${estimatedTime / 1000} seconds`);
|
||||
|
||||
async function batcher(page: number) {
|
||||
const batch = events.slice(page * BATCH_SIZE, (page + 1) * BATCH_SIZE);
|
||||
|
||||
if (batch.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const size = Buffer.byteLength(JSON.stringify(batch));
|
||||
// console.log(batch.length, size / (1024 * 1024));
|
||||
|
||||
await fetch('http://localhost:3333/import/events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'openpanel-client-id': 'dd3db204-dcf6-49e2-9e82-de01cba7e585',
|
||||
'openpanel-client-secret': 'sec_293b903816e327e10c9d',
|
||||
},
|
||||
body: JSON.stringify(batch),
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));
|
||||
}
|
||||
|
||||
async function runBatchesInParallel(
|
||||
totalPages: number,
|
||||
concurrentBatches: number
|
||||
) {
|
||||
let currentPage = 0;
|
||||
|
||||
while (currentPage < totalPages) {
|
||||
const promises = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < concurrentBatches && currentPage < totalPages;
|
||||
i++, currentPage++
|
||||
) {
|
||||
progress(
|
||||
`Sending batch ${currentPage} (${Math.round((currentPage / totalPages) * 100)}... %)`
|
||||
);
|
||||
promises.push(batcher(currentPage));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
console.log(totalPages);
|
||||
|
||||
// Trigger the batches
|
||||
try {
|
||||
await runBatchesInParallel(totalPages, 8); // Run 8 batches in parallel
|
||||
} catch (e) {
|
||||
console.log('ERROR?!', e);
|
||||
}
|
||||
}
|
||||
|
||||
loadFilesBatcher();
|
||||
8
packages/cli/tsconfig.json
Normal file
8
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@openpanel/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"incremental": false,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
9
packages/cli/tsup.config.ts
Normal file
9
packages/cli/tsup.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
import config from '@openpanel/tsconfig/tsup.config.json' assert { type: 'json' };
|
||||
|
||||
export default defineConfig({
|
||||
...(config as any),
|
||||
entry: ['src/cli.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
});
|
||||
@@ -24,9 +24,11 @@ declare global {
|
||||
|
||||
window.op = (t, ...args) => {
|
||||
// @ts-expect-error
|
||||
const fn = op[t].bind(op);
|
||||
const fn = op[t] ? op[t].bind(op) : undefined;
|
||||
if (typeof fn === 'function') {
|
||||
fn(...args);
|
||||
} else {
|
||||
console.warn(`op.js: ${t} is not a function`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
181
pnpm-lock.yaml
generated
181
pnpm-lock.yaml
generated
@@ -112,6 +112,9 @@ importers:
|
||||
url-metadata:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
uuid:
|
||||
specifier: ^9.0.1
|
||||
version: 9.0.1
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
@@ -814,6 +817,58 @@ importers:
|
||||
specifier: ^5.2.2
|
||||
version: 5.3.3
|
||||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
arg:
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
glob:
|
||||
specifier: ^10.4.3
|
||||
version: 10.4.5
|
||||
inquirer:
|
||||
specifier: ^9.3.5
|
||||
version: 9.3.6
|
||||
ramda:
|
||||
specifier: ^0.29.1
|
||||
version: 0.29.1
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
devDependencies:
|
||||
'@openpanel/db':
|
||||
specifier: workspace:^
|
||||
version: link:../db
|
||||
'@openpanel/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/eslint
|
||||
'@openpanel/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/prettier
|
||||
'@openpanel/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../sdks/sdk
|
||||
'@openpanel/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
'@types/node':
|
||||
specifier: ^20.14.10
|
||||
version: 20.14.11
|
||||
'@types/ramda':
|
||||
specifier: ^0.30.1
|
||||
version: 0.30.1
|
||||
eslint:
|
||||
specifier: ^8.48.0
|
||||
version: 8.56.0
|
||||
prettier:
|
||||
specifier: ^3.0.3
|
||||
version: 3.2.5
|
||||
tsup:
|
||||
specifier: ^7.2.0
|
||||
version: 7.3.0(typescript@5.3.3)
|
||||
typescript:
|
||||
specifier: ^5.2.2
|
||||
version: 5.3.3
|
||||
|
||||
packages/common:
|
||||
dependencies:
|
||||
'@openpanel/constants':
|
||||
@@ -4246,6 +4301,11 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@inquirer/figures@1.0.4:
|
||||
resolution: {integrity: sha512-R7Gsg6elpuqdn55fBH2y9oYzrU/yKrSmIsDX4ROT51vohrECFzTf2zw9BfUbOW8xjfmM2QbVoVYdTwhrtEKWSQ==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/@ioredis/commands@1.2.0:
|
||||
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||
dev: false
|
||||
@@ -7562,6 +7622,12 @@ packages:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/node@20.14.11:
|
||||
resolution: {integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/@types/nprogress@0.2.3:
|
||||
resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==}
|
||||
dev: false
|
||||
@@ -7579,6 +7645,12 @@ packages:
|
||||
types-ramda: 0.29.7
|
||||
dev: true
|
||||
|
||||
/@types/ramda@0.30.1:
|
||||
resolution: {integrity: sha512-aoyF/ADPL6N+/NXXfhPWF+Qj6w1Cql59m9wX0Gi15uyF+bpzXeLd63HPdiTDE2bmLXfNcVufsDPKmbfOrOzTBA==}
|
||||
dependencies:
|
||||
types-ramda: 0.30.1
|
||||
dev: true
|
||||
|
||||
/@types/range-parser@1.2.7:
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
dev: true
|
||||
@@ -8829,6 +8901,10 @@ packages:
|
||||
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
||||
dev: false
|
||||
|
||||
/chardet@0.7.0:
|
||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||
dev: false
|
||||
|
||||
/charenc@0.0.2:
|
||||
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||
dev: false
|
||||
@@ -8949,6 +9025,11 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cli-width@4.1.0:
|
||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: false
|
||||
|
||||
/client-only@0.0.1:
|
||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||
dev: false
|
||||
@@ -11047,6 +11128,15 @@ packages:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
dev: false
|
||||
|
||||
/external-editor@3.1.0:
|
||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
chardet: 0.7.0
|
||||
iconv-lite: 0.4.24
|
||||
tmp: 0.0.33
|
||||
dev: false
|
||||
|
||||
/fast-content-type-parse@1.1.0:
|
||||
resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
|
||||
dev: false
|
||||
@@ -11653,6 +11743,18 @@ packages:
|
||||
minimatch: 9.0.3
|
||||
minipass: 7.0.4
|
||||
path-scurry: 1.10.1
|
||||
dev: false
|
||||
|
||||
/glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
foreground-child: 3.1.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.0
|
||||
path-scurry: 1.11.1
|
||||
|
||||
/glob@6.0.4:
|
||||
resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==}
|
||||
@@ -11681,6 +11783,7 @@ packages:
|
||||
|
||||
/glob@7.1.7:
|
||||
resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
@@ -11692,6 +11795,7 @@ packages:
|
||||
|
||||
/glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
@@ -12176,6 +12280,24 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/inquirer@9.3.6:
|
||||
resolution: {integrity: sha512-riK/iQB2ctwkpWYgjjWIRv3MBLt2gzb2Sj0JNQNbyTXgyXsLWcDPJ5WS5ZDTCx7BRFnJsARtYh+58fjP5M2Y0Q==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@inquirer/figures': 1.0.4
|
||||
ansi-escapes: 4.3.2
|
||||
cli-width: 4.1.0
|
||||
external-editor: 3.1.0
|
||||
mute-stream: 1.0.0
|
||||
ora: 5.4.1
|
||||
run-async: 3.0.0
|
||||
rxjs: 7.8.1
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
yoctocolors-cjs: 2.1.2
|
||||
dev: false
|
||||
|
||||
/internal-ip@4.3.0:
|
||||
resolution: {integrity: sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -12608,6 +12730,14 @@ packages:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
dev: false
|
||||
|
||||
/jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
/jake@10.8.7:
|
||||
resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==}
|
||||
@@ -14310,6 +14440,12 @@ packages:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
/minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
dev: false
|
||||
@@ -14350,6 +14486,11 @@ packages:
|
||||
/minipass@7.0.4:
|
||||
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: false
|
||||
|
||||
/minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
@@ -14424,6 +14565,11 @@ packages:
|
||||
msgpackr-extract: 3.0.2
|
||||
dev: false
|
||||
|
||||
/mute-stream@1.0.0:
|
||||
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
dev: false
|
||||
|
||||
/mv@2.1.1:
|
||||
resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@@ -15137,6 +15283,9 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/package-json-from-dist@1.0.0:
|
||||
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
|
||||
|
||||
/parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -15254,6 +15403,14 @@ packages:
|
||||
dependencies:
|
||||
lru-cache: 10.2.0
|
||||
minipass: 7.0.4
|
||||
dev: false
|
||||
|
||||
/path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
dependencies:
|
||||
lru-cache: 10.2.0
|
||||
minipass: 7.1.2
|
||||
|
||||
/path-to-regexp@0.1.7:
|
||||
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
|
||||
@@ -16717,6 +16874,11 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/run-async@3.0.0:
|
||||
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: false
|
||||
|
||||
/run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
@@ -16726,6 +16888,12 @@ packages:
|
||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
||||
dev: false
|
||||
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -17392,7 +17560,7 @@ packages:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
commander: 4.1.1
|
||||
glob: 10.3.10
|
||||
glob: 10.4.5
|
||||
lines-and-columns: 1.2.4
|
||||
mz: 2.7.0
|
||||
pirates: 4.0.6
|
||||
@@ -17930,6 +18098,12 @@ packages:
|
||||
ts-toolbelt: 9.6.0
|
||||
dev: true
|
||||
|
||||
/types-ramda@0.30.1:
|
||||
resolution: {integrity: sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==}
|
||||
dependencies:
|
||||
ts-toolbelt: 9.6.0
|
||||
dev: true
|
||||
|
||||
/typescript@5.3.3:
|
||||
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -18695,6 +18869,11 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/yoctocolors-cjs@2.1.2:
|
||||
resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/zod@3.22.4:
|
||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||
dev: false
|
||||
|
||||
Reference in New Issue
Block a user