mirror of
https://github.com/glitternetwork/pinme.git
synced 2026-09-19 01:44:33 +08:00
Merge branch 'feat-cli-login' of github.com:glitternetwork/pinme into feat-cli-login
This commit is contained in:
@@ -121,7 +121,6 @@ export default async function bindCmd(): Promise<void> {
|
||||
|
||||
// Auto-detect domain type if not explicitly specified
|
||||
const isDns = dns || isDnsDomain(domain);
|
||||
console.log(isDns,'isDns')
|
||||
const displayDomain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
||||
|
||||
// Validate DNS domain format
|
||||
|
||||
+5
-1
@@ -281,6 +281,10 @@ export default async function createCmd(options: CreateOptions): Promise<void> {
|
||||
execSync('pinme upload ./dist', {
|
||||
cwd: frontendDir,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
PINME_PROJECT_NAME: workerData.project_name,
|
||||
},
|
||||
});
|
||||
console.log(chalk.green(' Frontend uploaded to IPFS'));
|
||||
} catch (error: any) {
|
||||
@@ -294,7 +298,7 @@ export default async function createCmd(options: CreateOptions): Promise<void> {
|
||||
console.log(chalk.gray(` Project Name: ${workerData.project_name}`));
|
||||
console.log(chalk.gray(`\nNext steps:`));
|
||||
console.log(chalk.gray(` cd ${projectName}`));
|
||||
console.log(chalk.gray(` pinme save # 首次部署后端 + 前端`));
|
||||
console.log(chalk.gray(` pinme save`));
|
||||
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
|
||||
+92
-98
@@ -8,119 +8,113 @@ import { getAuthHeaders } from './utils/webLogin';
|
||||
const API_BASE = process.env.PINME_API_BASE || '';
|
||||
|
||||
interface DeleteOptions {
|
||||
name?: string;
|
||||
force?: boolean;
|
||||
name?: string;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
// 从 pinme.toml 获取项目名
|
||||
function getProjectName(): string | null {
|
||||
const configPath = path.join(process.cwd(), 'pinme.toml');
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return null;
|
||||
}
|
||||
const config = fs.readFileSync(configPath, 'utf-8');
|
||||
const match = config.match(/project_name\s*=\s*"([^"]+)"/);
|
||||
return match?.[1] || null;
|
||||
const configPath = path.join(process.cwd(), 'pinme.toml');
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return null;
|
||||
}
|
||||
const config = fs.readFileSync(configPath, 'utf-8');
|
||||
const match = config.match(/project_name\s*=\s*"([^"]+)"/);
|
||||
return match?.[1] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a project: removes Worker domain binding, Worker script, D1 database
|
||||
*/
|
||||
export default async function deleteCmd(options: DeleteOptions): Promise<void> {
|
||||
try {
|
||||
// Check if user is logged in
|
||||
const headers = getAuthHeaders();
|
||||
if (!headers['authentication-tokens'] || !headers['token-address']) {
|
||||
console.log(chalk.yellow('\n⚠️ You are not logged in.'));
|
||||
console.log(chalk.gray('Please run: pinme login'));
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
// Check if user is logged in
|
||||
const headers = getAuthHeaders();
|
||||
if (!headers['authentication-tokens'] || !headers['token-address']) {
|
||||
console.log(chalk.yellow('\n⚠️ You are not logged in.'));
|
||||
console.log(chalk.gray('Please run: pinme login'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.blue('Deleting project...\n'));
|
||||
console.log(chalk.blue('Deleting project...\n'));
|
||||
|
||||
// Get project name from pinme.toml or options
|
||||
let projectName = options.name || getProjectName();
|
||||
// Get project name from pinme.toml or options
|
||||
let projectName = options.name || getProjectName();
|
||||
|
||||
if (!projectName) {
|
||||
console.log(chalk.red('\n❌ Error: Cannot find project name.'));
|
||||
console.log(chalk.yellow(' Please make sure you are in the project directory.'));
|
||||
console.log(chalk.gray(' The project directory should contain a pinme.toml file.'));
|
||||
console.log(chalk.gray('\n Or specify the project name:'));
|
||||
console.log(chalk.gray(' cd /path/to/your-project'));
|
||||
console.log(chalk.gray(' pinme delete'));
|
||||
process.exit(1);
|
||||
}
|
||||
if (!projectName) {
|
||||
console.log(chalk.red('\n❌ Error: Cannot find project name.'));
|
||||
console.log(chalk.yellow(' Please make sure you are in the project directory.'));
|
||||
console.log(chalk.gray(' The project directory should contain a pinme.toml file.'));
|
||||
console.log(chalk.gray('\n Or specify the project name:'));
|
||||
console.log(chalk.gray(' cd /path/to/your-project'));
|
||||
console.log(chalk.gray(' pinme delete'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.gray(`Project: ${projectName}`));
|
||||
console.log(chalk.gray(`Directory: ${process.cwd()}`));
|
||||
console.log(chalk.gray(`Project: ${projectName}`));
|
||||
console.log(chalk.gray(`Directory: ${process.cwd()}`));
|
||||
|
||||
// Confirm deletion
|
||||
if (!options.force) {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to delete project "${projectName}"? This will remove Worker, domain binding, and D1 database.`,
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
if (!answers.confirm) {
|
||||
console.log(chalk.gray('Cancelled.'));
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Call API to delete project
|
||||
console.log(chalk.blue('Deleting project on platform...'));
|
||||
const apiUrl = `${API_BASE}/delete_project`;
|
||||
console.log(chalk.gray(`API URL: ${apiUrl}`));
|
||||
console.log(chalk.gray(`Project name: ${projectName}`));
|
||||
|
||||
const response = await axios.post(apiUrl, {
|
||||
project_name: projectName,
|
||||
}, {
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
} else {
|
||||
console.log(chalk.red('No Response'))
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (data.code === 200) {
|
||||
console.log(chalk.green('\n✅ Project deleted successfully!'));
|
||||
console.log(chalk.gray(`\nProject: ${data.data.project_name}`));
|
||||
console.log(chalk.gray(` Domain deleted: ${data.data.domain_deleted ? '✅' : '❌'}`));
|
||||
console.log(chalk.gray(` Worker deleted: ${data.data.worker_deleted ? '✅' : '❌'}`));
|
||||
console.log(chalk.gray(` Database deleted: ${data.data.database_deleted ? '✅' : '❌'}`));
|
||||
|
||||
console.log(chalk.gray('\nLocal files are kept unchanged.'));
|
||||
} else {
|
||||
const errorMsg = data?.msg || 'Failed to delete project';
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
// Confirm deletion
|
||||
if (!options.force) {
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to delete project "${projectName}"? This will remove Worker, domain binding, and D1 database.`,
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
if (!answers.confirm) {
|
||||
console.log(chalk.gray('Cancelled.'));
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(chalk.red(error));
|
||||
const errorMsg = error.response?.data?.msg
|
||||
|| error.message
|
||||
|| 'Failed to delete project';
|
||||
console.error(chalk.red(`\n❌ Error: ${errorMsg}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Call API to delete project
|
||||
console.log(chalk.blue('Deleting project on platform...'));
|
||||
const apiUrl = `${API_BASE}/delete_project`;
|
||||
console.log(chalk.gray(`API URL: ${apiUrl}`));
|
||||
console.log(chalk.gray(`Project name: ${projectName}`));
|
||||
|
||||
const response = await axios.post(apiUrl, {
|
||||
project_name: projectName,
|
||||
}, {
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).catch((error) => {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
throw error;
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (data.code === 200) {
|
||||
console.log(chalk.green('\n✅ Project deleted successfully!'));
|
||||
console.log(chalk.gray(`\nProject: ${data.data.project_name}`));
|
||||
console.log(chalk.gray(` Domain deleted: ${data.data.domain_deleted ? '✅' : '❌'}`));
|
||||
console.log(chalk.gray(` Worker deleted: ${data.data.worker_deleted ? '✅' : '❌'}`));
|
||||
console.log(chalk.gray(` Database deleted: ${data.data.database_deleted ? '✅' : '❌'}`));
|
||||
|
||||
// 删除本地项目目录
|
||||
const projectDir = process.cwd();
|
||||
if (fs.existsSync(projectDir)) {
|
||||
console.log(chalk.blue('\nDeleting local project directory...'));
|
||||
// 先切换到父目录,避免在已删除的目录中
|
||||
const parentDir = path.dirname(projectDir);
|
||||
process.chdir(parentDir);
|
||||
|
||||
await fs.remove(projectDir);
|
||||
console.log(chalk.green(`✅ Local directory deleted: ${projectDir}`));
|
||||
}
|
||||
} else {
|
||||
const errorMsg = data?.msg || 'Failed to delete project';
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.log(chalk.red(error));
|
||||
const errorMsg = error.response?.data?.msg
|
||||
|| error.message
|
||||
|| 'Failed to delete project';
|
||||
console.error(chalk.red(`\n❌ Error: ${errorMsg}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-12
@@ -56,7 +56,7 @@ function buildWorker() {
|
||||
|
||||
function installDependencies() {
|
||||
console.log(chalk.blue('Installing dependencies...'));
|
||||
|
||||
|
||||
// 安装根目录依赖
|
||||
try {
|
||||
execSync('npm install', {
|
||||
@@ -67,7 +67,7 @@ function installDependencies() {
|
||||
} catch (error: any) {
|
||||
throw new Error(`Root dependencies install failed: ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
// 安装后端依赖
|
||||
const backendDir = path.join(PROJECT_DIR, 'backend');
|
||||
if (fs.existsSync(path.join(backendDir, 'package.json'))) {
|
||||
@@ -81,7 +81,7 @@ function installDependencies() {
|
||||
throw new Error(`Backend dependencies install failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 安装前端依赖
|
||||
const frontendDir = path.join(PROJECT_DIR, 'frontend');
|
||||
if (fs.existsSync(path.join(frontendDir, 'package.json'))) {
|
||||
@@ -136,11 +136,11 @@ function getSqlFiles(): string[] {
|
||||
|
||||
async function saveWorker(workerJsPath: string, modulePaths: string[], sqlFiles: string[], metadata: any, projectName: string) {
|
||||
console.log(chalk.blue('Saving worker to platform...'));
|
||||
console.log(chalk.gray(`Project: ${projectName}`));
|
||||
console.log(chalk.gray(`workerJsPath: ${workerJsPath}`));
|
||||
console.log(chalk.gray(`modulePaths: ${modulePaths}`));
|
||||
console.log(chalk.gray(`sqlFiles: ${sqlFiles}`));
|
||||
console.log(chalk.gray(`metadata: ${metadata}`));
|
||||
console.log(chalk.gray(`Project: ${projectName}`));
|
||||
console.log(chalk.gray(`workerJsPath: ${workerJsPath}`));
|
||||
console.log(chalk.gray(`modulePaths: ${modulePaths}`));
|
||||
console.log(chalk.gray(`sqlFiles: ${sqlFiles}`));
|
||||
console.log(chalk.gray(`metadata: ${metadata}`));
|
||||
const apiUrl = `${API_BASE}/save_worker?project_name=${encodeURIComponent(projectName)}`;
|
||||
const headers = getAuthHeaders();
|
||||
console.log(chalk.gray(`API URL: ${apiUrl}`));
|
||||
@@ -192,8 +192,12 @@ async function saveWorker(workerJsPath: string, modulePaths: string[], sqlFiles:
|
||||
throw new Error(response.data?.errors?.[0]?.message || 'Failed to save worker');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
if (error.response) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
} else {
|
||||
console.log(chalk.red('No Response'))
|
||||
}
|
||||
const errorMsg = error.response?.data?.errors?.[0]?.message
|
||||
|| error.response?.data?.error
|
||||
|| error.message
|
||||
@@ -217,12 +221,16 @@ function buildFrontend() {
|
||||
}
|
||||
}
|
||||
|
||||
function deployFrontend() {
|
||||
function deployFrontend(projectName: string) {
|
||||
console.log(chalk.blue('Deploying frontend to IPFS...'));
|
||||
try {
|
||||
execSync('pinme upload ./frontend/dist', {
|
||||
cwd: PROJECT_DIR,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
PINME_PROJECT_NAME: projectName,
|
||||
},
|
||||
});
|
||||
console.log(chalk.green('Frontend deployed to IPFS'));
|
||||
} catch (error: any) {
|
||||
@@ -285,7 +293,7 @@ export default async function saveCmd(options: SaveOptions): Promise<void> {
|
||||
// Frontend: build + deploy
|
||||
console.log(chalk.blue('\n--- Frontend ---'));
|
||||
buildFrontend();
|
||||
deployFrontend();
|
||||
deployFrontend(projectName);
|
||||
|
||||
console.log(chalk.green('\n✅ Deployment complete!'));
|
||||
process.exit(0);
|
||||
|
||||
+6
-2
@@ -107,8 +107,12 @@ async function updateDb(sqlFiles: string[], projectName: string) {
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
if (error.response) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
} else {
|
||||
console.log(chalk.red('No Response'))
|
||||
}
|
||||
const errorMsg = error.response?.data?.errors?.[0]?.message
|
||||
|| error.response?.data?.error
|
||||
|| error.response?.data?.msg
|
||||
|
||||
+6
-2
@@ -42,12 +42,16 @@ function buildFrontend() {
|
||||
}
|
||||
}
|
||||
|
||||
function deployFrontend() {
|
||||
function deployFrontend(projectName: string) {
|
||||
console.log(chalk.blue('Deploying frontend to IPFS...'));
|
||||
try {
|
||||
execSync('pinme upload ./frontend/dist', {
|
||||
cwd: PROJECT_DIR,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
PINME_PROJECT_NAME: projectName,
|
||||
},
|
||||
});
|
||||
console.log(chalk.green('Frontend deployed to IPFS'));
|
||||
} catch (error: any) {
|
||||
@@ -94,7 +98,7 @@ export default async function updateWebCmd(options?: UpdateWebOptions): Promise<
|
||||
// Frontend: build + deploy
|
||||
console.log(chalk.blue('\n--- Frontend Update ---'));
|
||||
buildFrontend();
|
||||
deployFrontend();
|
||||
deployFrontend(projectName);
|
||||
|
||||
console.log(chalk.green('\n✅ Web update complete!'));
|
||||
process.exit(0);
|
||||
|
||||
+6
-2
@@ -152,8 +152,12 @@ async function updateWorker(workerJsPath: string, modulePaths: string[], metadat
|
||||
throw new Error(response.data?.errors?.[0]?.message || 'Failed to update worker');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
if (error.response) {
|
||||
console.log(chalk.red(` Response status: ${error.response?.status}`));
|
||||
console.log(chalk.red(` Response data: ${JSON.stringify(error.response?.data)}`));
|
||||
} else {
|
||||
console.log(chalk.red('No Response'))
|
||||
}
|
||||
const errorMsg = error.response?.data?.errors?.[0]?.message
|
||||
|| error.response?.data?.error
|
||||
|| error.message
|
||||
|
||||
+94
-61
@@ -103,6 +103,34 @@ interface UploadOptions {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function formatEnsUrl(shortUrl?: string): string {
|
||||
if (!shortUrl) return '';
|
||||
const normalized = shortUrl.trim();
|
||||
if (!normalized) return '';
|
||||
if (/^https?:\/\//.test(normalized)) return normalized;
|
||||
if (normalized.includes('.')) return `https://${normalized}`;
|
||||
return `https://${normalized}.pinit.eth.limo`;
|
||||
}
|
||||
|
||||
function printUploadUrls(contentHash: string, shortUrl?: string): void {
|
||||
const uid = getUid();
|
||||
const encryptedCID = encryptHash(contentHash, secretKey, uid);
|
||||
const previewUrl = `${URL}${encryptedCID}`;
|
||||
const projectName = process.env.PINME_PROJECT_NAME?.trim();
|
||||
|
||||
if (projectName) {
|
||||
const ensUrl = formatEnsUrl(shortUrl);
|
||||
console.log(chalk.cyan(`URL:`));
|
||||
console.log(chalk.cyan(ensUrl || previewUrl));
|
||||
console.log(chalk.cyan(`Management page:`));
|
||||
console.log(chalk.cyan(previewUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.cyan(`URL:`));
|
||||
console.log(chalk.cyan(previewUrl));
|
||||
}
|
||||
|
||||
function getDomainFromArgs(): string | null {
|
||||
const args = process.argv.slice(2);
|
||||
const dIdx = args.findIndex((a) => a === '--domain' || a === '-d');
|
||||
@@ -257,44 +285,47 @@ export default async (options?: UploadOptions): Promise<void> => {
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`uploading ${absolutePath} to ipfs...`));
|
||||
let result;
|
||||
try {
|
||||
const result = await upload(absolutePath);
|
||||
if (result) {
|
||||
const uid = getUid();
|
||||
const encryptedCID = encryptHash(result.contentHash, secretKey, uid);
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
figlet.textSync('Successful', { horizontalLayout: 'full' }),
|
||||
),
|
||||
);
|
||||
console.log(chalk.cyan(`URL:`));
|
||||
console.log(chalk.cyan(`${URL}${encryptedCID}`));
|
||||
|
||||
// optional: bind domain after upload
|
||||
if (domainArg) {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Binding domain: ${displayDomain} with CID: ${result.contentHash}`,
|
||||
),
|
||||
);
|
||||
try {
|
||||
await bindDomain(domainArg, result.contentHash, isDns, authConfig);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Token expired') {
|
||||
process.exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
console.log(chalk.green('\n🎉 upload successful, program exit'));
|
||||
}
|
||||
result = await upload(absolutePath);
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
console.error(chalk.red(`Upload error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
console.error(chalk.red('Upload failed: no result returned'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
figlet.textSync('Successful', { horizontalLayout: 'full' }),
|
||||
),
|
||||
);
|
||||
printUploadUrls(result.contentHash, result.shortUrl);
|
||||
|
||||
// optional: bind domain after upload
|
||||
if (domainArg) {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Binding domain: ${displayDomain} with CID: ${result.contentHash}`,
|
||||
),
|
||||
);
|
||||
try {
|
||||
await bindDomain(domainArg, result.contentHash, isDns, authConfig);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Token expired') {
|
||||
process.exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
console.log(chalk.green('\n🎉 upload successful, program exit'));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// No path argument provided, use interactive mode
|
||||
const answer = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
@@ -362,40 +393,42 @@ export default async (options?: UploadOptions): Promise<void> => {
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`uploading ${absolutePath} to ipfs...`));
|
||||
let result;
|
||||
try {
|
||||
const result = await upload(absolutePath);
|
||||
|
||||
if (result) {
|
||||
const uid = getUid();
|
||||
const encryptedCID = encryptHash(result.contentHash, secretKey, uid);
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
figlet.textSync('Successful', { horizontalLayout: 'full' }),
|
||||
),
|
||||
);
|
||||
console.log(chalk.cyan(`URL:`));
|
||||
console.log(chalk.cyan(`${URL}${encryptedCID}`));
|
||||
if (domainArg) {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Binding domain: ${displayDomain} with CID: ${result.contentHash}`,
|
||||
),
|
||||
);
|
||||
try {
|
||||
await bindDomain(domainArg, result.contentHash, isDns, authConfig);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Token expired') {
|
||||
process.exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
console.log(chalk.green('\n🎉 upload successful, program exit'));
|
||||
}
|
||||
result = await upload(absolutePath);
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
console.error(chalk.red(`Upload error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
console.error(chalk.red('Upload failed: no result returned'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
figlet.textSync('Successful', { horizontalLayout: 'full' }),
|
||||
),
|
||||
);
|
||||
printUploadUrls(result.contentHash, result.shortUrl);
|
||||
if (domainArg) {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Binding domain: ${displayDomain} with CID: ${result.contentHash}`,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await bindDomain(domainArg, result.contentHash, isDns, authConfig);
|
||||
} catch (e: any) {
|
||||
if (e.message === 'Token expired') {
|
||||
process.exit(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
console.log(chalk.green('\n🎉 upload successful, program exit'));
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -254,7 +254,7 @@ export async function isVip(
|
||||
// CAR Export API
|
||||
const CAR_API_BASE =
|
||||
process.env.CAR_API_BASE ||
|
||||
process.env.PINME_API_BASE ||
|
||||
process.env.IPFS_API_URL ||
|
||||
'http://ipfs-proxy.opena.chat/api/v3';
|
||||
|
||||
function createCarClient(): AxiosInstance {
|
||||
|
||||
@@ -61,6 +61,7 @@ interface UploadStatusResponse {
|
||||
ShortUrl: string;
|
||||
};
|
||||
is_ready: boolean;
|
||||
domain?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,10 +75,21 @@ async function pollUploadStatus(
|
||||
let consecutiveErrors = 0;
|
||||
let stopProgressUpdates = false;
|
||||
|
||||
const projectName = process.env.PINME_PROJECT_NAME?.trim();
|
||||
while (Date.now() - startTime < maxPollTime) {
|
||||
try {
|
||||
// Build query params
|
||||
const queryParams = new URLSearchParams({
|
||||
trace_id: traceId,
|
||||
uid: deviceId,
|
||||
});
|
||||
if (projectName) {
|
||||
queryParams.append('project_name', projectName);
|
||||
console.log(chalk.gray(`[up_status] project_name: ${projectName}`));
|
||||
}
|
||||
|
||||
const response = await axios.get<UploadStatusResponse>(
|
||||
`${ipfsApiUrl}/up_status?trace_id=${traceId}&uid=${deviceId}`,
|
||||
`${ipfsApiUrl}/up_status?${queryParams.toString()}`,
|
||||
{
|
||||
timeout: pollTimeout,
|
||||
headers: {
|
||||
@@ -399,6 +411,10 @@ async function uploadDirectory(
|
||||
const directoryItem = uploadResult.upload_rst;
|
||||
if (directoryItem) {
|
||||
const fileCount = countFilesInDirectory(directoryPath);
|
||||
// Use domain from backend to construct full URL
|
||||
const shortUrl = directoryItem.ShortUrl;
|
||||
const domain = uploadResult.domain;
|
||||
const fullShortUrl = shortUrl && domain ? `${shortUrl}.${domain}` : shortUrl;
|
||||
const uploadData = {
|
||||
path: directoryPath,
|
||||
filename: path.basename(directoryPath),
|
||||
@@ -407,14 +423,14 @@ async function uploadDirectory(
|
||||
size: sizeCheck.size,
|
||||
fileCount: fileCount,
|
||||
isDirectory: true,
|
||||
shortUrl: directoryItem.ShortUrl || null,
|
||||
shortUrl: fullShortUrl || null,
|
||||
};
|
||||
saveUploadHistory(uploadData);
|
||||
|
||||
clearInterval(timeInterval);
|
||||
return {
|
||||
hash: directoryItem.Hash,
|
||||
shortUrl: directoryItem.ShortUrl,
|
||||
shortUrl: fullShortUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -585,6 +601,10 @@ async function uploadFile(
|
||||
|
||||
const fileItem = uploadResult.upload_rst;
|
||||
if (fileItem) {
|
||||
// Use domain from backend to construct full URL
|
||||
const shortUrl = fileItem.ShortUrl;
|
||||
const domain = uploadResult.domain;
|
||||
const fullShortUrl = shortUrl && domain ? `${shortUrl}.${domain}` : shortUrl;
|
||||
const uploadData = {
|
||||
path: filePath,
|
||||
filename: fileName,
|
||||
@@ -593,7 +613,7 @@ async function uploadFile(
|
||||
size: sizeCheck.size,
|
||||
fileCount: 1,
|
||||
isDirectory: false,
|
||||
shortUrl: fileItem.ShortUrl || null,
|
||||
shortUrl: fullShortUrl || null,
|
||||
};
|
||||
saveUploadHistory(uploadData);
|
||||
|
||||
@@ -602,7 +622,7 @@ async function uploadFile(
|
||||
|
||||
return {
|
||||
hash: fileItem.Hash,
|
||||
shortUrl: fileItem.ShortUrl,
|
||||
shortUrl: fullShortUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from './uploadLimits';
|
||||
import { saveUploadHistory } from './history';
|
||||
import { getUid } from './getDeviceId';
|
||||
import { getAuthHeaders } from './webLogin';
|
||||
|
||||
// Configuration constants
|
||||
const IPFS_API_URL =
|
||||
@@ -65,6 +66,7 @@ interface ChunkStatusResponse {
|
||||
Hash?: string;
|
||||
ShortUrl?: string;
|
||||
};
|
||||
domain?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,9 +155,8 @@ class StepProgressBar {
|
||||
|
||||
const duration = this.formatDuration(Math.floor(elapsed / 1000));
|
||||
const progressBar = this.createProgressBar(progress);
|
||||
this.spinner.text = `Uploading ${
|
||||
this.fileName
|
||||
}... ${progressBar} ${Math.round(progress * 100)}% (${duration})`;
|
||||
this.spinner.text = `Uploading ${this.fileName
|
||||
}... ${progressBar} ${Math.round(progress * 100)}% (${duration})`;
|
||||
}, PROGRESS_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
@@ -341,8 +342,7 @@ async function uploadChunkWithAbort(
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Chunk ${chunkIndex + 1} upload failed after ${MAX_RETRIES} retries: ${
|
||||
error.message
|
||||
`Chunk ${chunkIndex + 1} upload failed after ${MAX_RETRIES} retries: ${error.message
|
||||
}`,
|
||||
);
|
||||
}
|
||||
@@ -418,9 +418,8 @@ async function uploadFileChunks(
|
||||
}
|
||||
|
||||
hasFatalError = true;
|
||||
fatalError = `Chunk ${chunkIndex + 1}/${totalChunks} upload failed: ${
|
||||
error.message
|
||||
}`;
|
||||
fatalError = `Chunk ${chunkIndex + 1}/${totalChunks} upload failed: ${error.message
|
||||
}`;
|
||||
abortController.abort();
|
||||
throw new Error(fatalError);
|
||||
}
|
||||
@@ -455,15 +454,24 @@ async function completeChunkUpload(
|
||||
): Promise<string> {
|
||||
try {
|
||||
const requestBody: any = { session_id: sessionId, uid: deviceId };
|
||||
const projectName = process.env.PINME_PROJECT_NAME?.trim();
|
||||
let authHeaders: Record<string, string> = {};
|
||||
if (importAsCar) {
|
||||
requestBody.import_as_car = true;
|
||||
}
|
||||
if (projectName) {
|
||||
requestBody.project_name = projectName;
|
||||
authHeaders = getAuthHeaders();
|
||||
}
|
||||
const response = await axios.post<ChunkCompleteResponse>(
|
||||
`${IPFS_API_URL}/chunk/complete`,
|
||||
requestBody,
|
||||
{
|
||||
timeout: TIMEOUT,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -485,10 +493,18 @@ async function getChunkStatus(
|
||||
deviceId: string,
|
||||
): Promise<ChunkStatusResponse['data']> {
|
||||
try {
|
||||
const projectName = process.env.PINME_PROJECT_NAME?.trim();
|
||||
const queryParams = new URLSearchParams({
|
||||
trace_id: sessionId,
|
||||
uid: deviceId,
|
||||
});
|
||||
if (projectName) {
|
||||
queryParams.append('project_name', projectName);
|
||||
}
|
||||
|
||||
const response = await axios.get<ChunkStatusResponse>(
|
||||
`${IPFS_API_URL}/up_status`,
|
||||
`${IPFS_API_URL}/up_status?${queryParams.toString()}`,
|
||||
{
|
||||
params: { trace_id: sessionId, uid: deviceId },
|
||||
timeout: TIMEOUT,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
},
|
||||
@@ -531,9 +547,13 @@ async function monitorChunkProgress(
|
||||
if (progressBar) {
|
||||
progressBar.stopSimulatingProgress();
|
||||
}
|
||||
// Use domain from backend to construct full URL
|
||||
const shortUrl = status.upload_rst.ShortUrl;
|
||||
const domain = status.domain;
|
||||
const fullShortUrl = shortUrl && domain ? `${shortUrl}.${domain}` : shortUrl;
|
||||
return {
|
||||
hash: status.upload_rst.Hash,
|
||||
shortUrl: status.upload_rst.ShortUrl,
|
||||
shortUrl: fullShortUrl,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -711,7 +731,7 @@ export default async function (filePath: string, importAsCar: boolean = false):
|
||||
|
||||
try {
|
||||
const isDirectory = fs.statSync(filePath).isDirectory();
|
||||
const result = isDirectory
|
||||
const result = isDirectory
|
||||
? await uploadDirectoryInChunks(filePath, deviceId, importAsCar)
|
||||
: await uploadFileInChunks(filePath, deviceId, importAsCar);
|
||||
|
||||
@@ -722,8 +742,8 @@ export default async function (filePath: string, importAsCar: boolean = false):
|
||||
shortUrl: result.shortUrl,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
throw new Error('Upload failed: no hash returned');
|
||||
} catch (error: any) {
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1646
-412
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pinme",
|
||||
"version": "2.0.0-beta.8",
|
||||
"version": "2.0.0-beta.15",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user