@web-to-figma/desktop 1.0.0
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 +93 -0
- package/assets/chrome-version.json +11 -0
- package/assets/extension/README.md +18 -0
- package/assets/extension/chrome-mv3-headed.crx +0 -0
- package/assets/extension/chrome-mv3-headless.crx +0 -0
- package/bin/web-to-figma.js +5 -0
- package/db/migrations/000001_create_artifact_tables.up.sql +69 -0
- package/db/migrations/000002_create_tag_tables.up.sql +39 -0
- package/db/migrations/000003_seed_tag_values.up.sql +97 -0
- package/dist/auth/captureSession.d.ts +14 -0
- package/dist/auth/captureSession.js +1 -0
- package/dist/auth/jwtSecret.d.ts +2 -0
- package/dist/auth/jwtSecret.js +1 -0
- package/dist/capture/browserPool.d.ts +24 -0
- package/dist/capture/browserPool.js +1 -0
- package/dist/capture/captureService.d.ts +25 -0
- package/dist/capture/captureService.js +1 -0
- package/dist/capture/captureStatus.d.ts +7 -0
- package/dist/capture/captureStatus.js +1 -0
- package/dist/capture/chromeDownloader.d.ts +21 -0
- package/dist/capture/chromeDownloader.js +1 -0
- package/dist/capture/crxUtils.d.ts +4 -0
- package/dist/capture/crxUtils.js +1 -0
- package/dist/capture/extensionPinPolicy.d.ts +10 -0
- package/dist/capture/extensionPinPolicy.js +1 -0
- package/dist/capture/headedBrowserState.d.ts +22 -0
- package/dist/capture/headedBrowserState.js +1 -0
- package/dist/capture/headedExtension.d.ts +7 -0
- package/dist/capture/headedExtension.js +1 -0
- package/dist/capture/manualLaunch.d.ts +2 -0
- package/dist/capture/manualLaunch.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +85 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +109 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +241 -0
- package/dist/loadEnv.d.ts +1 -0
- package/dist/loadEnv.js +14 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.js +32 -0
- package/dist/port.d.ts +4 -0
- package/dist/port.js +19 -0
- package/dist/security/redact.d.ts +3 -0
- package/dist/security/redact.js +34 -0
- package/dist/sentry.d.ts +3 -0
- package/dist/sentry.js +66 -0
- package/dist/server/app.d.ts +18 -0
- package/dist/server/app.js +82 -0
- package/dist/server/checkServer.d.ts +2 -0
- package/dist/server/checkServer.js +24 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +20 -0
- package/dist/server/middleware/desktopJwt.d.ts +10 -0
- package/dist/server/middleware/desktopJwt.js +1 -0
- package/dist/server/routes/capture.d.ts +4 -0
- package/dist/server/routes/capture.js +39 -0
- package/dist/server/routes/captures.d.ts +3 -0
- package/dist/server/routes/captures.js +75 -0
- package/dist/server/routes/collections.d.ts +3 -0
- package/dist/server/routes/collections.js +41 -0
- package/dist/server/routes/files.d.ts +3 -0
- package/dist/server/routes/files.js +18 -0
- package/dist/server/routes/health.d.ts +2 -0
- package/dist/server/routes/health.js +8 -0
- package/dist/server/routes/image.d.ts +2 -0
- package/dist/server/routes/image.js +58 -0
- package/dist/server/routes/proxy.d.ts +2 -0
- package/dist/server/routes/proxy.js +24 -0
- package/dist/server/routes/upload.d.ts +3 -0
- package/dist/server/routes/upload.js +102 -0
- package/dist/server/uploadBody.d.ts +1 -0
- package/dist/server/uploadBody.js +5 -0
- package/dist/storage/captureRepo.d.ts +21 -0
- package/dist/storage/captureRepo.js +172 -0
- package/dist/storage/collectionRepo.d.ts +14 -0
- package/dist/storage/collectionRepo.js +104 -0
- package/dist/storage/db.d.ts +10 -0
- package/dist/storage/db.js +30 -0
- package/dist/storage/fileStore.d.ts +26 -0
- package/dist/storage/fileStore.js +93 -0
- package/dist/storage/migrate.d.ts +8 -0
- package/dist/storage/migrate.js +61 -0
- package/dist/storage/seeds.d.ts +7 -0
- package/dist/storage/seeds.js +30 -0
- package/dist/storage/tagRepo.d.ts +7 -0
- package/dist/storage/tagRepo.js +31 -0
- package/dist/terminal/banner.d.ts +10 -0
- package/dist/terminal/banner.js +115 -0
- package/dist/terminal/chromeInstall.d.ts +3 -0
- package/dist/terminal/chromeInstall.js +45 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +1 -0
- package/dist/updater/installer.d.ts +2 -0
- package/dist/updater/installer.js +61 -0
- package/dist/updater/runtime.d.ts +25 -0
- package/dist/updater/runtime.js +153 -0
- package/dist/updater/types.d.ts +29 -0
- package/dist/updater/types.js +1 -0
- package/dist/updater/updateLog.d.ts +3 -0
- package/dist/updater/updateLog.js +7 -0
- package/dist/updater/versionCheck.d.ts +5 -0
- package/dist/updater/versionCheck.js +59 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +55 -0
- package/package.json +72 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type Size = {
|
|
2
|
+
name: string;
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
};
|
|
6
|
+
export type Theme = {
|
|
7
|
+
name: string;
|
|
8
|
+
};
|
|
9
|
+
export type Export = {
|
|
10
|
+
id?: number;
|
|
11
|
+
captureId?: number;
|
|
12
|
+
theme: string;
|
|
13
|
+
sizeName: string;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
url: string;
|
|
17
|
+
screenshotUrl: string;
|
|
18
|
+
thumbnailUrl: string;
|
|
19
|
+
};
|
|
20
|
+
export type Capture = {
|
|
21
|
+
id?: number;
|
|
22
|
+
externalId: string;
|
|
23
|
+
userId?: number;
|
|
24
|
+
url: string;
|
|
25
|
+
isFullPage: boolean;
|
|
26
|
+
collectionExternalId?: string;
|
|
27
|
+
exports: Export[];
|
|
28
|
+
};
|
|
29
|
+
export type Collection = {
|
|
30
|
+
id?: number;
|
|
31
|
+
externalId: string;
|
|
32
|
+
userId?: number;
|
|
33
|
+
name: string;
|
|
34
|
+
parentExternalId?: string | null;
|
|
35
|
+
active: boolean;
|
|
36
|
+
isDefault: boolean;
|
|
37
|
+
iconUrl?: string | null;
|
|
38
|
+
};
|
|
39
|
+
export type CollectionWithMetadata = Collection & {
|
|
40
|
+
captureCount: number;
|
|
41
|
+
};
|
|
42
|
+
export type CaptureUpdate = {
|
|
43
|
+
type: 'CaptureProgress' | 'CaptureCompleted' | 'CaptureFailed';
|
|
44
|
+
data: unknown;
|
|
45
|
+
};
|
|
46
|
+
export type TagValue = {
|
|
47
|
+
id: number;
|
|
48
|
+
tagTypeId: number;
|
|
49
|
+
name: string;
|
|
50
|
+
active: boolean;
|
|
51
|
+
};
|
|
52
|
+
export type CaptureUser = {
|
|
53
|
+
externalId: string;
|
|
54
|
+
name: string;
|
|
55
|
+
photoUrl: string;
|
|
56
|
+
token: string;
|
|
57
|
+
};
|
|
58
|
+
export type UploadUrlRequest = {
|
|
59
|
+
key: string;
|
|
60
|
+
contentType: string;
|
|
61
|
+
contentEncoding?: string | null;
|
|
62
|
+
};
|
|
63
|
+
export type MultipartUploadUrlRequest = UploadUrlRequest & {
|
|
64
|
+
numParts: number;
|
|
65
|
+
};
|
|
66
|
+
export type CompleteMultipartUploadRequest = {
|
|
67
|
+
key: string;
|
|
68
|
+
uploadId: string;
|
|
69
|
+
parts: Array<{
|
|
70
|
+
eTag: string;
|
|
71
|
+
partNumber: number;
|
|
72
|
+
}>;
|
|
73
|
+
};
|
|
74
|
+
export type CaptureRequestBody = {
|
|
75
|
+
url: string;
|
|
76
|
+
sizes: Size[];
|
|
77
|
+
themes: Theme[];
|
|
78
|
+
};
|
|
79
|
+
export type UpdateCaptureMetadataRequest = {
|
|
80
|
+
captureExternalId: string;
|
|
81
|
+
collectionExternalId: string;
|
|
82
|
+
collectionIconUrl: string;
|
|
83
|
+
tags: number[];
|
|
84
|
+
};
|
|
85
|
+
export type RemoveCaptureFromCollectionRequest = {
|
|
86
|
+
captureExternalId: string;
|
|
87
|
+
collectionExternalId?: string;
|
|
88
|
+
};
|
|
89
|
+
export type CancelCaptureRequest = {
|
|
90
|
+
requestId: string;
|
|
91
|
+
};
|
|
92
|
+
export type ImageResizeRequest = {
|
|
93
|
+
image: string;
|
|
94
|
+
type: 'jpeg' | 'jpg' | 'png' | 'webp';
|
|
95
|
+
width: number;
|
|
96
|
+
height: number;
|
|
97
|
+
};
|
|
98
|
+
export type ConvertImageRequest = {
|
|
99
|
+
image: string;
|
|
100
|
+
from_type: 'jpeg' | 'jpg' | 'png' | 'webp' | 'ico';
|
|
101
|
+
to_type: 'jpeg' | 'jpg' | 'png' | 'webp';
|
|
102
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { execFile, execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function isGloballyInstalled() {
|
|
6
|
+
try {
|
|
7
|
+
const scriptPath = process.argv[1];
|
|
8
|
+
if (!scriptPath) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
12
|
+
const prefix = path.resolve(execFileSync(npmCmd, ['prefix', '-g'], { encoding: 'utf8' }).trim());
|
|
13
|
+
const resolvedScript = path.resolve(scriptPath);
|
|
14
|
+
return resolvedScript.startsWith(prefix + path.sep) || resolvedScript === prefix;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function installUpdate(latestVersion, verbose) {
|
|
21
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
22
|
+
const spec = `@web-to-figma/desktop@${latestVersion}`;
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(npmCmd, ['install', '-g', spec], {
|
|
25
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
26
|
+
env: process.env,
|
|
27
|
+
shell: process.platform === 'win32',
|
|
28
|
+
});
|
|
29
|
+
child.on('error', reject);
|
|
30
|
+
child.on('close', (code) => {
|
|
31
|
+
if (code === 0) {
|
|
32
|
+
resolve();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
reject(new Error(`npm install exited with code ${code ?? 'unknown'}`));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
respawnFromGlobalBin();
|
|
40
|
+
}
|
|
41
|
+
function respawnFromGlobalBin() {
|
|
42
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
43
|
+
let binPath = 'web-to-figma';
|
|
44
|
+
try {
|
|
45
|
+
const resolved = execFileSync(npmCmd, ['bin', '-g', 'web-to-figma'], { encoding: 'utf8' }).trim();
|
|
46
|
+
if (resolved) {
|
|
47
|
+
binPath = resolved;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// fall back to PATH lookup
|
|
52
|
+
}
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const child = spawn(binPath, args.length > 0 ? args : ['start'], {
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
detached: true,
|
|
57
|
+
shell: process.platform === 'win32',
|
|
58
|
+
});
|
|
59
|
+
child.unref();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ResolvedPaths } from '../config.js';
|
|
2
|
+
import type { UpdateInfo } from './types.js';
|
|
3
|
+
export type UpdaterOptions = {
|
|
4
|
+
currentVersion: string;
|
|
5
|
+
port: number;
|
|
6
|
+
paths: ResolvedPaths;
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
isCaptureBusy: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare class UpdaterRuntime {
|
|
11
|
+
private readonly options;
|
|
12
|
+
private updateAvailable;
|
|
13
|
+
private installing;
|
|
14
|
+
private interval;
|
|
15
|
+
private keypressHandler;
|
|
16
|
+
constructor(options: UpdaterOptions);
|
|
17
|
+
get pendingUpdate(): UpdateInfo | null;
|
|
18
|
+
checkNow(): Promise<UpdateInfo | null>;
|
|
19
|
+
printBanner(): void;
|
|
20
|
+
startBackgroundChecks(): void;
|
|
21
|
+
attachKeyListener(onInterrupt?: () => void): void;
|
|
22
|
+
triggerUpdate(): Promise<void>;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function printUnsupportedVersionMessage(currentVersion: string, minSupportedVersion: string): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { formatServerBanner } from '../terminal/banner.js';
|
|
4
|
+
import { CHECK_INTERVAL_MS, checkForUpdate } from './versionCheck.js';
|
|
5
|
+
import { installUpdate, isGloballyInstalled } from './installer.js';
|
|
6
|
+
import { appendUpdateLog } from './updateLog.js';
|
|
7
|
+
export class UpdaterRuntime {
|
|
8
|
+
options;
|
|
9
|
+
updateAvailable = null;
|
|
10
|
+
installing = false;
|
|
11
|
+
interval = null;
|
|
12
|
+
keypressHandler = null;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
get pendingUpdate() {
|
|
17
|
+
return this.updateAvailable;
|
|
18
|
+
}
|
|
19
|
+
async checkNow() {
|
|
20
|
+
try {
|
|
21
|
+
const info = await checkForUpdate(this.options.currentVersion);
|
|
22
|
+
if (info) {
|
|
23
|
+
this.updateAvailable = info;
|
|
24
|
+
appendUpdateLog(this.options.paths, {
|
|
25
|
+
event: 'update_available',
|
|
26
|
+
from: this.options.currentVersion,
|
|
27
|
+
to: info.latest,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.updateAvailable = null;
|
|
32
|
+
}
|
|
33
|
+
return info;
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const error = err instanceof Error ? err.message : 'Unknown error';
|
|
37
|
+
appendUpdateLog(this.options.paths, { event: 'update_check_failed', error });
|
|
38
|
+
if (this.options.verbose) {
|
|
39
|
+
console.warn(`Update check failed: ${error}`);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
printBanner() {
|
|
45
|
+
const { currentVersion, port } = this.options;
|
|
46
|
+
const updateKey = os.platform() === 'darwin' ? 'Cmd+U' : 'Ctrl+U';
|
|
47
|
+
console.log(formatServerBanner({
|
|
48
|
+
currentVersion,
|
|
49
|
+
port,
|
|
50
|
+
updateAvailable: this.updateAvailable,
|
|
51
|
+
updateKey,
|
|
52
|
+
showNpxUpdateHint: !isGloballyInstalled(),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
startBackgroundChecks() {
|
|
56
|
+
if (this.interval) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.interval = setInterval(() => {
|
|
60
|
+
void this.checkNow().then((info) => {
|
|
61
|
+
if (info) {
|
|
62
|
+
this.printBanner();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}, CHECK_INTERVAL_MS);
|
|
66
|
+
this.interval.unref();
|
|
67
|
+
}
|
|
68
|
+
attachKeyListener(onInterrupt) {
|
|
69
|
+
if (!process.stdin.isTTY || this.installing) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
readline.emitKeypressEvents(process.stdin);
|
|
73
|
+
if (process.stdin.isTTY) {
|
|
74
|
+
process.stdin.setRawMode(true);
|
|
75
|
+
}
|
|
76
|
+
process.stdin.resume();
|
|
77
|
+
this.keypressHandler = (_str, key) => {
|
|
78
|
+
if (!key) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Raw mode stdin swallows Ctrl+C before it becomes SIGINT.
|
|
82
|
+
if ((key.ctrl && key.name === 'c') || _str === '\u0003') {
|
|
83
|
+
if (onInterrupt) {
|
|
84
|
+
onInterrupt();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
process.kill(process.pid, 'SIGINT');
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const isUpdateKey = (key.meta && key.name === 'u') || (key.ctrl && key.name === 'u');
|
|
92
|
+
if (!isUpdateKey || !this.updateAvailable || this.installing) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
void this.triggerUpdate();
|
|
96
|
+
};
|
|
97
|
+
process.stdin.on('keypress', this.keypressHandler);
|
|
98
|
+
}
|
|
99
|
+
async triggerUpdate() {
|
|
100
|
+
if (!this.updateAvailable || this.installing) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (this.options.isCaptureBusy()) {
|
|
104
|
+
console.log('Update deferred until capture completes.');
|
|
105
|
+
appendUpdateLog(this.options.paths, { event: 'update_deferred', reason: 'capture_in_progress' });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const { latest } = this.updateAvailable;
|
|
109
|
+
const from = this.options.currentVersion;
|
|
110
|
+
this.installing = true;
|
|
111
|
+
console.log(`\nUpdating @web-to-figma/desktop to v${latest}...`);
|
|
112
|
+
appendUpdateLog(this.options.paths, { event: 'update_started', from, to: latest, method: 'npm' });
|
|
113
|
+
try {
|
|
114
|
+
await installUpdate(latest, this.options.verbose);
|
|
115
|
+
appendUpdateLog(this.options.paths, { event: 'update_completed', from, to: latest });
|
|
116
|
+
console.log(`Updated to v${latest} successfully.`);
|
|
117
|
+
console.log('Restarting...');
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const error = err instanceof Error ? err.message : 'Unknown error';
|
|
121
|
+
appendUpdateLog(this.options.paths, { event: 'update_failed', from, to: latest, error });
|
|
122
|
+
console.error(`Update failed: ${error}`);
|
|
123
|
+
if (!isGloballyInstalled()) {
|
|
124
|
+
console.error('Try: npx @web-to-figma/desktop@latest start');
|
|
125
|
+
}
|
|
126
|
+
this.installing = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
dispose() {
|
|
130
|
+
if (this.interval) {
|
|
131
|
+
clearInterval(this.interval);
|
|
132
|
+
this.interval = null;
|
|
133
|
+
}
|
|
134
|
+
if (this.keypressHandler) {
|
|
135
|
+
process.stdin.off('keypress', this.keypressHandler);
|
|
136
|
+
this.keypressHandler = null;
|
|
137
|
+
}
|
|
138
|
+
if (process.stdin.isTTY) {
|
|
139
|
+
process.stdin.setRawMode(false);
|
|
140
|
+
process.stdin.pause();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function printUnsupportedVersionMessage(currentVersion, minSupportedVersion) {
|
|
145
|
+
const updateKey = os.platform() === 'darwin' ? 'Cmd+U' : 'Ctrl+U';
|
|
146
|
+
console.error('');
|
|
147
|
+
console.error('This version is no longer supported.');
|
|
148
|
+
console.error(`Installed: v${currentVersion} Minimum: v${minSupportedVersion}`);
|
|
149
|
+
console.error(`Press ${updateKey} to update, or run:`);
|
|
150
|
+
console.error(' npm install -g @web-to-figma/desktop@latest');
|
|
151
|
+
console.error('Web to Figma will not run until updated.');
|
|
152
|
+
console.error('');
|
|
153
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type UpdateInfo = {
|
|
2
|
+
latest: string;
|
|
3
|
+
integrity?: string;
|
|
4
|
+
};
|
|
5
|
+
export type UpdateLogEvent = {
|
|
6
|
+
event: 'update_check_failed';
|
|
7
|
+
error: string;
|
|
8
|
+
} | {
|
|
9
|
+
event: 'update_available';
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
} | {
|
|
13
|
+
event: 'update_started';
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
method: 'npm';
|
|
17
|
+
} | {
|
|
18
|
+
event: 'update_completed';
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
} | {
|
|
22
|
+
event: 'update_failed';
|
|
23
|
+
from: string;
|
|
24
|
+
to: string;
|
|
25
|
+
error: string;
|
|
26
|
+
} | {
|
|
27
|
+
event: 'update_deferred';
|
|
28
|
+
reason: 'capture_in_progress';
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function appendUpdateLog(paths, entry) {
|
|
4
|
+
fs.mkdirSync(paths.logsDir, { recursive: true });
|
|
5
|
+
const line = JSON.stringify({ ...entry, at: new Date().toISOString() }) + '\n';
|
|
6
|
+
fs.appendFileSync(path.join(paths.logsDir, 'update.log'), line);
|
|
7
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UpdateInfo } from './types.js';
|
|
2
|
+
export declare const REGISTRY_URL = "https://registry.npmjs.org/@web-to-figma/desktop/latest";
|
|
3
|
+
export declare const CHECK_INTERVAL_MS: number;
|
|
4
|
+
export declare function checkForUpdate(currentVersion: string): Promise<UpdateInfo | null>;
|
|
5
|
+
export declare function isBelowMinSupported(currentVersion: string, minSupportedVersion: string): boolean;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export const REGISTRY_URL = 'https://registry.npmjs.org/@web-to-figma/desktop/latest';
|
|
6
|
+
export const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
7
|
+
export async function checkForUpdate(currentVersion) {
|
|
8
|
+
const latest = await fetchLatestVersion();
|
|
9
|
+
if (!latest?.version || !semver.valid(latest.version) || !semver.valid(currentVersion)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (semver.gt(latest.version, currentVersion)) {
|
|
13
|
+
return { latest: latest.version, integrity: latest.dist?.integrity };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
async function fetchLatestVersion() {
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(REGISTRY_URL, {
|
|
20
|
+
headers: { Accept: 'application/json' },
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
return await fetchLatestViaNpmCli();
|
|
24
|
+
}
|
|
25
|
+
return (await response.json());
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return await fetchLatestViaNpmCli();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function fetchLatestViaNpmCli() {
|
|
32
|
+
try {
|
|
33
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
34
|
+
const { stdout } = await execFileAsync(npmCmd, [
|
|
35
|
+
'view',
|
|
36
|
+
'@web-to-figma/desktop',
|
|
37
|
+
'version',
|
|
38
|
+
'dist.integrity',
|
|
39
|
+
'--json',
|
|
40
|
+
]);
|
|
41
|
+
const parsed = JSON.parse(stdout);
|
|
42
|
+
if (typeof parsed === 'string') {
|
|
43
|
+
return { version: parsed };
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(parsed) && parsed.length >= 1) {
|
|
46
|
+
return { version: parsed[0], dist: { integrity: parsed[1] } };
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function isBelowMinSupported(currentVersion, minSupportedVersion) {
|
|
55
|
+
if (!semver.valid(currentVersion) || !semver.valid(minSupportedVersion)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return semver.lt(currentVersion, minSupportedVersion);
|
|
59
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const LOCAL_USER_ID = 1;
|
|
2
|
+
export declare const CUSTOM_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
3
|
+
export declare function nowIso(): string;
|
|
4
|
+
export declare function randSeq(n: number): string;
|
|
5
|
+
export declare function marshalString(data: unknown): string;
|
|
6
|
+
export declare function parseJsonSafe<T>(value: string | null | undefined, fallback: T): T;
|
|
7
|
+
export declare function isValidArtifactKey(key: string): boolean;
|
|
8
|
+
export declare function contentTypeForPath(filePath: string): string;
|
|
9
|
+
export declare function freeDiskBytes(dir: string): number;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
|
2
|
+
export const LOCAL_USER_ID = 1;
|
|
3
|
+
export const CUSTOM_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
4
|
+
export function nowIso() {
|
|
5
|
+
return new Date().toISOString();
|
|
6
|
+
}
|
|
7
|
+
export function randSeq(n) {
|
|
8
|
+
let result = '';
|
|
9
|
+
for (let i = 0; i < n; i++) {
|
|
10
|
+
result += LETTERS[Math.floor(Math.random() * LETTERS.length)];
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
export function marshalString(data) {
|
|
15
|
+
return JSON.stringify(data);
|
|
16
|
+
}
|
|
17
|
+
export function parseJsonSafe(value, fallback) {
|
|
18
|
+
if (!value) {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function isValidArtifactKey(key) {
|
|
29
|
+
return /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(key);
|
|
30
|
+
}
|
|
31
|
+
export function contentTypeForPath(filePath) {
|
|
32
|
+
if (filePath.endsWith('.json')) {
|
|
33
|
+
return 'application/json';
|
|
34
|
+
}
|
|
35
|
+
if (filePath.endsWith('.jpeg') || filePath.endsWith('.jpg')) {
|
|
36
|
+
return 'image/jpeg';
|
|
37
|
+
}
|
|
38
|
+
if (filePath.endsWith('.png')) {
|
|
39
|
+
return 'image/png';
|
|
40
|
+
}
|
|
41
|
+
if (filePath.endsWith('.webp')) {
|
|
42
|
+
return 'image/webp';
|
|
43
|
+
}
|
|
44
|
+
return 'application/octet-stream';
|
|
45
|
+
}
|
|
46
|
+
import { statfsSync } from 'node:fs';
|
|
47
|
+
export function freeDiskBytes(dir) {
|
|
48
|
+
try {
|
|
49
|
+
const stats = statfsSync(dir);
|
|
50
|
+
return stats.bavail * stats.bsize;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return Number.MAX_SAFE_INTEGER;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@web-to-figma/desktop",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Web to Figma Desktop - Convert any website or HTML code to design",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"web-to-figma": "./bin/web-to-figma.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"db/migrations",
|
|
14
|
+
"assets/extension",
|
|
15
|
+
"assets/chrome-version.json"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"registry": "https://registry.npmjs.org"
|
|
20
|
+
},
|
|
21
|
+
"wtfDesktop": {
|
|
22
|
+
"minSupportedVersion": "0.1.0",
|
|
23
|
+
"sentryDsn": "https://77e3bca116c40372a0c1a95a23f58eeb@o4511590414286848.ingest.us.sentry.io/4511590419070976"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"build:release": "npm run build && node scripts/inject-secret.js && node scripts/obfuscate.js",
|
|
28
|
+
"obfuscate": "node scripts/obfuscate.js",
|
|
29
|
+
"inject-secret": "node scripts/inject-secret.js",
|
|
30
|
+
"pack:check": "npm pack --dry-run",
|
|
31
|
+
"start": "npm run build && node bin/web-to-figma.js start",
|
|
32
|
+
"dev": "npm run build && node bin/web-to-figma.js start --verbose",
|
|
33
|
+
"doctor": "npm run build && node bin/web-to-figma.js doctor",
|
|
34
|
+
"test": "npm run build && npm run test:run",
|
|
35
|
+
"test:run": "node --test dist/test/versionCheck.test.js dist/test/captureStatus.test.js dist/test/desktopJwt.test.js dist/test/uploadBody.test.js dist/test/checkServer.test.js dist/test/headedBrowserState.test.js dist/test/crxUtils.test.js",
|
|
36
|
+
"smoke": "npm run build && npm run smoke:run",
|
|
37
|
+
"smoke:run": "node scripts/smoke-test.js",
|
|
38
|
+
"prepack": "node scripts/prepack.js",
|
|
39
|
+
"gen-tag-migration": "node scripts/gen-tag-migration.js",
|
|
40
|
+
"upload-chrome-s3": "node scripts/upload-chrome-to-s3.js",
|
|
41
|
+
"upload-chrome-s3:dry-run": "node scripts/upload-chrome-to-s3.js --dry-run"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@fastify/cors": "^9.0.1",
|
|
45
|
+
"@sentry/node": "^7.120.4",
|
|
46
|
+
"better-sqlite3": "^11.5.0",
|
|
47
|
+
"commander": "^12.1.0",
|
|
48
|
+
"dotenv": "^16.3.1",
|
|
49
|
+
"fastify": "^4.28.1",
|
|
50
|
+
"jsonwebtoken": "^9.0.2",
|
|
51
|
+
"ora": "^8.2.0",
|
|
52
|
+
"pino": "^9.4.0",
|
|
53
|
+
"selenium-webdriver": "^4.25.0",
|
|
54
|
+
"semver": "^7.6.3",
|
|
55
|
+
"sharp": "^0.33.5",
|
|
56
|
+
"uuid": "^10.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@aws-sdk/client-s3": "^3.758.0",
|
|
60
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
61
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
62
|
+
"@types/node": "^20.14.0",
|
|
63
|
+
"@types/selenium-webdriver": "^4.35.6",
|
|
64
|
+
"@types/semver": "^7.5.8",
|
|
65
|
+
"@types/uuid": "^10.0.0",
|
|
66
|
+
"javascript-obfuscator": "^4.1.1",
|
|
67
|
+
"typescript": "^5.5.0"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18"
|
|
71
|
+
}
|
|
72
|
+
}
|