pux-to-html
Advanced tools
Comparing version 1.0.1 to 1.0.2
{ | ||
"name": "pux-to-html", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"license": "MIT", | ||
"author": "Roman Ožana <roman@ozana.cz> (https://ozana.cz)", | ||
"description": "Converts 1PUX (export from 1Password) file to HTML files", | ||
@@ -14,6 +16,3 @@ "keywords": [ | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:OzzyCzech/pux-to-html.git" | ||
}, | ||
"repository": "git@github.com:OzzyCzech/pux-to-html.git", | ||
"main": "index.js", | ||
@@ -23,11 +22,8 @@ "type": "module", | ||
"@sindresorhus/slugify": "^2.2.1", | ||
"adm-zip": "^0.5.10", | ||
"commander": "^10.0.1", | ||
"meow": "^12.0.1" | ||
"adm-zip": "^0.5.12", | ||
"commander": "^12.0.0", | ||
"meow": "^13.2.0" | ||
}, | ||
"license": "MIT", | ||
"author": { | ||
"name": "Roman Ožana", | ||
"email": "roman@ozana.cz", | ||
"url": "https://ozana.cz" | ||
"devDependencies": { | ||
"xo": "^0.38.0" | ||
}, | ||
@@ -34,0 +30,0 @@ "bin": { |
# Convert 1PUX format to HTML | ||
[![NPM Downloads](https://img.shields.io/npm/dm/pux-to-html?style=for-the-badge)](https://www.npmjs.com/package/pux-to-html) | ||
[![NPM Version](https://img.shields.io/npm/v/pux-to-html?style=for-the-badge)](https://www.npmjs.com/package/pux-to-html) | ||
[![NPM License](https://img.shields.io/npm/l/pux-to-html?style=for-the-badge)](https://github.com/OzzyCzech/pux-to-html/blob/main/LICENSE) | ||
[![Last Commit](https://img.shields.io/github/last-commit/OzzyCzech/pux-to-html?style=for-the-badge)](https://github.com/OzzyCzech/pux-to-html/commits/main) | ||
`pux2html` is a console script that converts 1PUX file to HTML files: | ||
@@ -33,2 +38,2 @@ | ||
allows you to export 1PUX files, so you can access your data | ||
outside of [1Password](https://1password.com/) | ||
outside [1Password](https://1password.com/) |
export function getHtml({content, title}) { | ||
return `<!doctype html> | ||
return `<!doctype html> | ||
<html lang="en"> | ||
@@ -15,3 +15,3 @@ <head> | ||
<div class="m-4">${content}</div> | ||
</body>` | ||
</body>`; | ||
} |
function getCopyButton(value) { | ||
return `<button | ||
return `<button | ||
type="button" | ||
@@ -10,10 +10,10 @@ title="Copy to clipboard" | ||
</svg> | ||
</button>` | ||
</button>`; | ||
} | ||
function getSection({title, fields}) { | ||
return fields && fields.length > 0 ? ` | ||
return fields && fields.length > 0 ? ` | ||
${title ? `<h6 class="uppercase font-semibold">${title}</h6>` : ''} | ||
<ul class="divide-y border rounded-xl"> | ||
${fields.map(getField).join("")} | ||
${fields.map(getField).join('')} | ||
</ul>` : ''; | ||
@@ -23,70 +23,65 @@ } | ||
function getNotes({notesPlain}) { | ||
return notesPlain ? `<h6 class="font-semibold">Note</h6><pre class="text-gray-100 bg-gray-900 rounded p-4 m-2">${notesPlain}</pre>` : ''; | ||
return notesPlain ? `<h6 class="font-semibold">Note</h6><pre class="text-gray-100 bg-gray-900 rounded p-4 m-2">${notesPlain}</pre>` : ''; | ||
} | ||
function getUrl(url, label) { | ||
try { | ||
const link = new URL(url) | ||
return `<a href="${url}" target="_blank" class="hover:underline hover:text-sky-700" rel="noopener noreferrer">${link.hostname}</a>` | ||
} catch (error) { | ||
return `<code>${url}</code>` | ||
} | ||
try { | ||
const link = new URL(url); | ||
return `<a href="${url}" target="_blank" class="hover:underline hover:text-sky-700" rel="noopener noreferrer">${link.hostname}</a>`; | ||
} catch { | ||
return `<code>${url}</code>`; | ||
} | ||
} | ||
function getSecret(value) { | ||
return `<code>${value}</code> ${getCopyButton(value)}` | ||
return `<code>${value}</code> ${getCopyButton(value)}`; | ||
} | ||
function getOTP(url) { | ||
try { | ||
const link = new URL(url); | ||
const secret = link.searchParams.get('secret'); | ||
const issuer = link.searchParams.get('issuer'); | ||
try { | ||
const link = new URL(url) | ||
const secret = link.searchParams.get('secret'); | ||
const issuer = link.searchParams.get('issuer'); | ||
return `<ul> | ||
return `<ul> | ||
<li>Issues: <code>${issuer}</code></li> | ||
<li>Secret: <code>${secret}</code></li> | ||
<li>URL: <code>${url}</code></li> | ||
</ul>` | ||
} catch (error) { | ||
return `<code>${url}</code>` | ||
} | ||
</ul>`; | ||
} catch { | ||
return `<code>${url}</code>`; | ||
} | ||
} | ||
function getField({value, name, title, url, label, designation}) { | ||
if (value instanceof Object) { | ||
if (value.hasOwnProperty('url') && value.url) { | ||
value = getUrl(value.url); | ||
} else if (value.hasOwnProperty('string') && value.string) { | ||
value = `<code>${value.string}</code>`; | ||
} else if (value.hasOwnProperty('totp') && value.totp) { | ||
label = 'one-time password'; | ||
value = getOTP(value.totp); | ||
} else if (value.hasOwnProperty('concealed') && value.concealed) { | ||
value = getSecret(value.concealed); | ||
} else if (value.hasOwnProperty('file') && value.file) { | ||
label = 'file'; | ||
value = `<code>${value.file.fileName}</code>`; | ||
} else { | ||
value = undefined; // Unknown type | ||
} | ||
} | ||
if (value instanceof Object) { | ||
if (value.hasOwnProperty('url') && value.url) { | ||
value = getUrl(value.url) | ||
} else if (value.hasOwnProperty('string') && value.string) { | ||
value = `<code>${value.string}</code>` | ||
} else if (value.hasOwnProperty('totp') && value.totp) { | ||
label = 'one-time password' | ||
value = getOTP(value.totp) | ||
} else if (value.hasOwnProperty('concealed') && value.concealed) { | ||
value = getSecret(value.concealed) | ||
} else if (value.hasOwnProperty('file') && value.file) { | ||
label = 'file' | ||
value = `<code>${value.file.fileName}</code>` | ||
} else { | ||
value = undefined; // unknown type | ||
} | ||
} | ||
// Password or username fields | ||
if (designation === 'password' || designation === 'username') { | ||
value = getSecret(value); | ||
} | ||
// password or username fields | ||
if (designation === "password" || designation === "username") { | ||
value = getSecret(value); | ||
} | ||
// If the field is a URL, display it as a link | ||
if (url) { | ||
label = 'website'; | ||
value = getUrl(url); | ||
} | ||
// If the field is a URL, display it as a link | ||
if (url) { | ||
label = 'website'; | ||
value = getUrl(url); | ||
} | ||
return value ? `<li class="p-3 flex flex-col flex-wrap gap-1"> | ||
return value ? `<li class="p-3 flex flex-col flex-wrap gap-1"> | ||
<div><span class="text-gray-500">${designation || name || title || label}:</span></div> | ||
@@ -97,14 +92,11 @@ <div class="flex items-center gap-3">${value}</div> | ||
export function getItemHtml({state, details, overview, createdAt, updatedAt}) { | ||
const loginSection = { | ||
fields: [ | ||
...details.loginFields.filter(item => item.designation === 'password' || item.designation === 'username'), | ||
...overview.urls || [] | ||
] | ||
}; | ||
const loginSection = { | ||
fields: [ | ||
...details.loginFields.filter(item => item.designation === "password" || item.designation === 'username'), | ||
...overview.urls || [] | ||
] | ||
} | ||
return state === "active" ? ` | ||
return state === 'active' ? ` | ||
<article class="border rounded-lg p-6 print:p-4 my-4 break-inside-avoid flex flex-col gap-3"> | ||
@@ -117,3 +109,3 @@ <h2 class="text-3xl print:text-lg font-semibold">${overview.title}</h2> | ||
<!-- other sections --> | ||
${details.sections.map(getSection).join("")} | ||
${details.sections.map(getSection).join('')} | ||
@@ -124,2 +116,2 @@ <!-- notes --> | ||
` : ''; | ||
} | ||
} |
@@ -1,17 +0,16 @@ | ||
import {getHtml} from "./get-html.js"; | ||
import {getItemHtml} from "./get-item-html.js"; | ||
import {getHtml} from './get-html.js'; | ||
import {getItemHtml} from './get-item-html.js'; | ||
export function getVaultHtml({attrs, items}) { | ||
items.sort((a, b) => b.updatedAt - a.updatedAt); | ||
items.sort((a, b) => b.updatedAt - a.updatedAt); | ||
return getHtml( | ||
{ | ||
title: `Vault export ${attrs.name}`, | ||
content: ` | ||
return getHtml( | ||
{ | ||
title: `Vault export ${attrs.name}`, | ||
content: ` | ||
<h1 class="text-2xl">Export of <em>${attrs.name}</em> vault</h1> | ||
${items.map(getItemHtml).join("\n")} | ||
${items.map(getItemHtml).join('\n')} | ||
` | ||
} | ||
) | ||
} | ||
); | ||
} |
@@ -1,29 +0,28 @@ | ||
import slugify from "@sindresorhus/slugify"; | ||
import {getVaultHtml} from "./get-vault-html.js"; | ||
import {writeFile} from "./write-file.js"; | ||
import slugify from '@sindresorhus/slugify'; | ||
import {getVaultHtml} from './get-vault-html.js'; | ||
import {writeFile} from './write-file.js'; | ||
import {join} from 'node:path'; | ||
import AdmZip from 'adm-zip'; | ||
// import templates | ||
// Import templates | ||
export async function puxToHtml(input, output) { | ||
const pux = new AdmZip(input); | ||
const pux = new AdmZip(input); | ||
const attributes = JSON.parse(pux.readAsText("export.attributes")); | ||
const data = JSON.parse(pux.readAsText("export.data")); | ||
const attributes = JSON.parse(pux.readAsText('export.attributes')); | ||
const data = JSON.parse(pux.readAsText('export.data')); | ||
for (const account of data.accounts) { | ||
// get directory name | ||
const dir = slugify(account.attrs.name); | ||
for (const account of data.accounts) { | ||
// Get directory name | ||
const dir = slugify(account.attrs.name); | ||
// iterate over vaults | ||
for (const vault of account.vaults) { | ||
const fileName = slugify(vault.attrs.name) + '.html'; | ||
const html = getVaultHtml(vault); | ||
// Iterate over vaults | ||
for (const vault of account.vaults) { | ||
const fileName = slugify(vault.attrs.name) + '.html'; | ||
const html = getVaultHtml(vault); | ||
// write HTML files | ||
await writeFile(join(output, dir, fileName), html); | ||
} | ||
} | ||
} | ||
// Write HTML files | ||
await writeFile(join(output, dir, fileName), html); | ||
} | ||
} | ||
} |
@@ -13,7 +13,7 @@ import {mkdir, writeFile as writeFileAsync} from 'node:fs/promises'; | ||
export async function writeFile(file, content) { | ||
if (!existsSync(dirname(file))) { | ||
await mkdir(dirname(file), {recursive: true}); | ||
} | ||
if (!existsSync(dirname(file))) { | ||
await mkdir(dirname(file), {recursive: true}); | ||
} | ||
return writeFileAsync(file, content); | ||
} | ||
return writeFileAsync(file, content); | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
40594
1006
38
0
1
12
1
+ Addedcommander@12.1.0(transitive)
+ Addedmeow@13.2.0(transitive)
- Removedcommander@10.0.1(transitive)
- Removedmeow@12.1.1(transitive)
Updatedadm-zip@^0.5.12
Updatedcommander@^12.0.0
Updatedmeow@^13.2.0