@rynt/extension-build 0.9.0 → 0.9.1
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 +1 -1
- package/dist/extension-dev-bridge.d.ts +17 -0
- package/dist/extension-dev-bridge.d.ts.map +1 -0
- package/dist/extension-dev-bridge.js +107 -0
- package/dist/extension-icon.d.ts +22 -0
- package/dist/extension-icon.d.ts.map +1 -0
- package/dist/extension-icon.js +93 -0
- package/dist/extension-manifest.d.ts +7 -0
- package/dist/extension-manifest.d.ts.map +1 -0
- package/dist/extension-manifest.js +129 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +223 -0
- package/package.json +11 -2
- package/scripts/write-host-shims.mjs +0 -68
- package/src/extension-dev-bridge.ts +0 -132
- package/src/extension-icon.ts +0 -120
- package/src/extension-manifest.ts +0 -187
- package/src/index.ts +0 -320
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/** URL dev-hub лаунчера по умолчанию (см. extension-dev-hub.ts). */
|
|
3
|
+
export declare const RYNT_LAUNCHER_DEV_HUB_DEFAULT = "http://127.0.0.1:39217";
|
|
4
|
+
declare function isDevBridgeActive(): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* При `RYNT_DEV=1` + `vite build --watch` регистрирует dist/ в dev-hub лаунчера
|
|
7
|
+
* после каждой сборки и снимает регистрацию при завершении процесса.
|
|
8
|
+
*
|
|
9
|
+
* Должен быть объявлен **до** `ryntExtensionEmitDistManifestPlugin`: Rollup вызывает
|
|
10
|
+
* `closeBundle` в обратном порядке, manifest пишется первым, регистрация — второй.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ryntExtensionDevBridgePlugin(options: {
|
|
13
|
+
root: string;
|
|
14
|
+
manifestFromVite?: Record<string, unknown>;
|
|
15
|
+
}): Plugin;
|
|
16
|
+
export { isDevBridgeActive };
|
|
17
|
+
//# sourceMappingURL=extension-dev-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-dev-bridge.d.ts","sourceRoot":"","sources":["../src/extension-dev-bridge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,oEAAoE;AACpE,eAAO,MAAM,6BAA6B,2BAA2B,CAAC;AAStE,iBAAS,iBAAiB,IAAI,OAAO,CAGpC;AAqDD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C,GAAG,MAAM,CA8CT;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { mergeExtensionManifestForDist } from './extension-manifest.js';
|
|
4
|
+
/** URL dev-hub лаунчера по умолчанию (см. extension-dev-hub.ts). */
|
|
5
|
+
export const RYNT_LAUNCHER_DEV_HUB_DEFAULT = 'http://127.0.0.1:39217';
|
|
6
|
+
function devHubBaseUrl() {
|
|
7
|
+
return (process.env.RYNT_LAUNCHER_DEV_HUB?.trim().replace(/\/+$/u, '') ||
|
|
8
|
+
RYNT_LAUNCHER_DEV_HUB_DEFAULT);
|
|
9
|
+
}
|
|
10
|
+
function isDevBridgeActive() {
|
|
11
|
+
if (process.env.RYNT_DEV_BRIDGE === '0')
|
|
12
|
+
return false;
|
|
13
|
+
return process.env.RYNT_DEV === '1' || process.env.RYNT_DEV === 'true';
|
|
14
|
+
}
|
|
15
|
+
async function postJson(url, body) {
|
|
16
|
+
const res = await fetch(url, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
19
|
+
body: JSON.stringify(body),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text().catch(() => '');
|
|
23
|
+
throw new Error(`HTTP ${res.status}${text ? `: ${text}` : ''}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function readManifestFromDist(distDir) {
|
|
27
|
+
try {
|
|
28
|
+
const manifestPath = path.join(distDir, 'manifest.json');
|
|
29
|
+
const raw = readFileSync(manifestPath, 'utf-8');
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* ignore */
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
let unregisterHookInstalled = false;
|
|
41
|
+
let lastRegisteredId = null;
|
|
42
|
+
function installUnregisterHook(extensionId) {
|
|
43
|
+
lastRegisteredId = extensionId;
|
|
44
|
+
if (unregisterHookInstalled)
|
|
45
|
+
return;
|
|
46
|
+
unregisterHookInstalled = true;
|
|
47
|
+
const unregister = () => {
|
|
48
|
+
if (!lastRegisteredId)
|
|
49
|
+
return;
|
|
50
|
+
const id = lastRegisteredId;
|
|
51
|
+
void postJson(`${devHubBaseUrl()}/api/v1/dev/extensions/unregister`, { id }).catch(() => {
|
|
52
|
+
/* hub может быть уже остановлен */
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
process.once('SIGINT', () => {
|
|
56
|
+
unregister();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
});
|
|
59
|
+
process.once('SIGTERM', unregister);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* При `RYNT_DEV=1` + `vite build --watch` регистрирует dist/ в dev-hub лаунчера
|
|
63
|
+
* после каждой сборки и снимает регистрацию при завершении процесса.
|
|
64
|
+
*
|
|
65
|
+
* Должен быть объявлен **до** `ryntExtensionEmitDistManifestPlugin`: Rollup вызывает
|
|
66
|
+
* `closeBundle` в обратном порядке, manifest пишется первым, регистрация — второй.
|
|
67
|
+
*/
|
|
68
|
+
export function ryntExtensionDevBridgePlugin(options) {
|
|
69
|
+
const distDir = path.join(options.root, 'dist');
|
|
70
|
+
let watchMode = false;
|
|
71
|
+
return {
|
|
72
|
+
name: 'rynt-extension-dev-bridge',
|
|
73
|
+
apply: 'build',
|
|
74
|
+
config(config) {
|
|
75
|
+
watchMode = Boolean(config.build?.watch);
|
|
76
|
+
},
|
|
77
|
+
async closeBundle() {
|
|
78
|
+
if (!isDevBridgeActive() && !watchMode)
|
|
79
|
+
return;
|
|
80
|
+
const manifest = readManifestFromDist(distDir) ??
|
|
81
|
+
(options.manifestFromVite
|
|
82
|
+
? mergeExtensionManifestForDist(options.root, options.manifestFromVite)
|
|
83
|
+
: null);
|
|
84
|
+
if (!manifest) {
|
|
85
|
+
console.warn('[rynt/extension-build] dev-bridge: dist/manifest.json не найден, пропуск');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const rootDir = path.resolve(distDir);
|
|
89
|
+
const hub = devHubBaseUrl();
|
|
90
|
+
try {
|
|
91
|
+
await postJson(`${hub}/api/v1/dev/extensions/register`, {
|
|
92
|
+
manifest,
|
|
93
|
+
rootDir,
|
|
94
|
+
});
|
|
95
|
+
const id = typeof manifest.id === 'string' ? manifest.id : '';
|
|
96
|
+
if (id)
|
|
97
|
+
installUnregisterHook(id);
|
|
98
|
+
console.info(`[rynt/extension-build] dev-bridge: зарегистрировано ${id} → ${hub}`);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
102
|
+
console.warn(`[rynt/extension-build] dev-bridge: не удалось связаться с лаунчером (${hub}): ${msg}`);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export { isDevBridgeActive };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Максимальная сторона иконки в px после сборки. */
|
|
2
|
+
export declare const EXTENSION_ICON_MAX_SIZE = 512;
|
|
3
|
+
/** Имя файла иконки в `dist/` (растровая). */
|
|
4
|
+
export declare const EXTENSION_ICON_DIST_PNG = "icon.png";
|
|
5
|
+
export declare function isRemoteOrDataIcon(value: string): boolean;
|
|
6
|
+
export declare function isBundledExtensionIconRef(value: string): boolean;
|
|
7
|
+
/** Локальный путь к исходнику иконки (относительно корня расширения). */
|
|
8
|
+
export declare function isLocalIconSource(value: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Берёт исходник по пути из vite-манифеста, кладёт в `dist/` и возвращает относительное имя
|
|
11
|
+
* для `manifest.icon` (например `icon.png`).
|
|
12
|
+
*
|
|
13
|
+
* Растровые форматы масштабируются до {@link EXTENSION_ICON_MAX_SIZE}px по длинной стороне.
|
|
14
|
+
* SVG копируется как есть (вектор не растеризуем — без native/wasm deps).
|
|
15
|
+
*/
|
|
16
|
+
export declare function processExtensionIconForDist(root: string, iconSource: string, distDir: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Если в манифесте указан локальный путь — обрабатывает иконку и подменяет поле на имя файла в dist.
|
|
19
|
+
* URL / data: URI остаются без изменений.
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyExtensionIconToManifest(root: string, manifest: Record<string, unknown>): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=extension-icon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-icon.d.ts","sourceRoot":"","sources":["../src/extension-icon.ts"],"names":[],"mappings":"AAKA,qDAAqD;AACrD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,8CAA8C;AAC9C,eAAO,MAAM,uBAAuB,aAAa,CAAC;AAalD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWhE;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMxD;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAwCjB;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Jimp } from 'jimp';
|
|
4
|
+
/** Максимальная сторона иконки в px после сборки. */
|
|
5
|
+
export const EXTENSION_ICON_MAX_SIZE = 512;
|
|
6
|
+
/** Имя файла иконки в `dist/` (растровая). */
|
|
7
|
+
export const EXTENSION_ICON_DIST_PNG = 'icon.png';
|
|
8
|
+
const RASTER_EXTENSIONS = new Set([
|
|
9
|
+
'.png',
|
|
10
|
+
'.jpg',
|
|
11
|
+
'.jpeg',
|
|
12
|
+
'.webp',
|
|
13
|
+
'.bmp',
|
|
14
|
+
'.gif',
|
|
15
|
+
'.tiff',
|
|
16
|
+
'.tif',
|
|
17
|
+
]);
|
|
18
|
+
export function isRemoteOrDataIcon(value) {
|
|
19
|
+
return /^(https?:|data:)/i.test(value.trim());
|
|
20
|
+
}
|
|
21
|
+
export function isBundledExtensionIconRef(value) {
|
|
22
|
+
const normalized = value.trim().replace(/\\/g, '/');
|
|
23
|
+
if (normalized.includes('/'))
|
|
24
|
+
return false;
|
|
25
|
+
const base = normalized.toLowerCase();
|
|
26
|
+
return (base === EXTENSION_ICON_DIST_PNG
|
|
27
|
+
|| base === 'icon.jpg'
|
|
28
|
+
|| base === 'icon.jpeg'
|
|
29
|
+
|| base === 'icon.webp'
|
|
30
|
+
|| base === 'icon.svg');
|
|
31
|
+
}
|
|
32
|
+
/** Локальный путь к исходнику иконки (относительно корня расширения). */
|
|
33
|
+
export function isLocalIconSource(value) {
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
if (!trimmed)
|
|
36
|
+
return false;
|
|
37
|
+
if (isRemoteOrDataIcon(trimmed))
|
|
38
|
+
return false;
|
|
39
|
+
if (isBundledExtensionIconRef(trimmed))
|
|
40
|
+
return false;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Берёт исходник по пути из vite-манифеста, кладёт в `dist/` и возвращает относительное имя
|
|
45
|
+
* для `manifest.icon` (например `icon.png`).
|
|
46
|
+
*
|
|
47
|
+
* Растровые форматы масштабируются до {@link EXTENSION_ICON_MAX_SIZE}px по длинной стороне.
|
|
48
|
+
* SVG копируется как есть (вектор не растеризуем — без native/wasm deps).
|
|
49
|
+
*/
|
|
50
|
+
export async function processExtensionIconForDist(root, iconSource, distDir) {
|
|
51
|
+
const trimmed = iconSource.trim();
|
|
52
|
+
if (!isLocalIconSource(trimmed)) {
|
|
53
|
+
throw new Error(`[rynt/extension-build] icon «${iconSource}» — не локальный путь к файлу`);
|
|
54
|
+
}
|
|
55
|
+
const absSource = path.resolve(root, trimmed);
|
|
56
|
+
if (!existsSync(absSource)) {
|
|
57
|
+
throw new Error(`[rynt/extension-build] icon file not found: ${absSource}`);
|
|
58
|
+
}
|
|
59
|
+
mkdirSync(distDir, { recursive: true });
|
|
60
|
+
const ext = path.extname(absSource).toLowerCase();
|
|
61
|
+
if (ext === '.svg') {
|
|
62
|
+
const destName = 'icon.svg';
|
|
63
|
+
copyFileSync(absSource, path.join(distDir, destName));
|
|
64
|
+
return destName;
|
|
65
|
+
}
|
|
66
|
+
if (!RASTER_EXTENSIONS.has(ext)) {
|
|
67
|
+
throw new Error(`[rynt/extension-build] unsupported icon format «${ext}» (${absSource}); ` +
|
|
68
|
+
'use PNG, JPEG, WebP, GIF, BMP, TIFF or SVG');
|
|
69
|
+
}
|
|
70
|
+
const image = await Jimp.read(absSource);
|
|
71
|
+
if (image.width > EXTENSION_ICON_MAX_SIZE
|
|
72
|
+
|| image.height > EXTENSION_ICON_MAX_SIZE) {
|
|
73
|
+
image.scaleToFit({ w: EXTENSION_ICON_MAX_SIZE, h: EXTENSION_ICON_MAX_SIZE });
|
|
74
|
+
}
|
|
75
|
+
const destPath = path.join(distDir, EXTENSION_ICON_DIST_PNG);
|
|
76
|
+
await image.write(destPath);
|
|
77
|
+
return EXTENSION_ICON_DIST_PNG;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Если в манифесте указан локальный путь — обрабатывает иконку и подменяет поле на имя файла в dist.
|
|
81
|
+
* URL / data: URI остаются без изменений.
|
|
82
|
+
*/
|
|
83
|
+
export async function applyExtensionIconToManifest(root, manifest) {
|
|
84
|
+
const raw = manifest.icon;
|
|
85
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
86
|
+
return;
|
|
87
|
+
if (isRemoteOrDataIcon(raw) || isBundledExtensionIconRef(raw)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const distDir = path.join(root, 'dist');
|
|
91
|
+
const distIcon = await processExtensionIconForDist(root, raw, distDir);
|
|
92
|
+
manifest.icon = distIcon;
|
|
93
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export declare function mergeExtensionManifestForDist(root: string, manifestFromVite: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
export declare function ryntExtensionEmitDistManifestPlugin(options: {
|
|
4
|
+
root: string;
|
|
5
|
+
manifestFromVite: Record<string, unknown>;
|
|
6
|
+
}): Plugin;
|
|
7
|
+
//# sourceMappingURL=extension-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-manifest.d.ts","sourceRoot":"","sources":["../src/extension-manifest.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AA4EnC,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACxC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCzB;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C,GAAG,MAAM,CA6DT"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { applyExtensionIconToManifest } from './extension-icon.js';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
/** Валидация манифеста через SDK (devDependency extension-build, не бандлится в расширение). */
|
|
7
|
+
async function loadManifestValidators() {
|
|
8
|
+
const { createJiti } = await import('jiti');
|
|
9
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
10
|
+
const extEntry = require.resolve('@rynt/sdk/extension');
|
|
11
|
+
const srcRoot = path.join(path.dirname(extEntry), '..');
|
|
12
|
+
const versionMod = jiti(path.join(srcRoot, 'extensions/version.ts'));
|
|
13
|
+
const ryntMod = jiti(path.join(srcRoot, 'extensions/registries/manifest-rynt.ts'));
|
|
14
|
+
return {
|
|
15
|
+
normalizeExtensionApiInManifest: versionMod.normalizeExtensionApiInManifest,
|
|
16
|
+
applyValidatedRyntContributesToManifest: ryntMod.applyValidatedRyntContributesToManifest,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function readJsonObject(filePath) {
|
|
20
|
+
if (!existsSync(filePath)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
27
|
+
? parsed
|
|
28
|
+
: {};
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.warn(`[rynt/extension-build] Failed to read JSON at ${filePath}:`, e);
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function versionFromPackageJson(pkg) {
|
|
36
|
+
const v = pkg.version;
|
|
37
|
+
if (v == null)
|
|
38
|
+
return null;
|
|
39
|
+
const s = String(v).trim();
|
|
40
|
+
return s.length > 0 ? s : null;
|
|
41
|
+
}
|
|
42
|
+
function manifestVersionString(value) {
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const t = value.trim();
|
|
45
|
+
return t.length > 0 ? t : null;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
export function mergeExtensionManifestForDist(root, manifestFromVite) {
|
|
53
|
+
const pkg = readJsonObject(path.join(root, 'package.json'));
|
|
54
|
+
const merged = { ...manifestFromVite };
|
|
55
|
+
if (typeof merged.name !== 'string' || !merged.name) {
|
|
56
|
+
if (typeof pkg.name === 'string' && pkg.name)
|
|
57
|
+
merged.name = pkg.name;
|
|
58
|
+
}
|
|
59
|
+
const fromManifest = manifestVersionString(merged.version);
|
|
60
|
+
const fromPkg = versionFromPackageJson(pkg);
|
|
61
|
+
if (fromManifest) {
|
|
62
|
+
merged.version = fromManifest;
|
|
63
|
+
}
|
|
64
|
+
else if (fromPkg) {
|
|
65
|
+
merged.version = fromPkg;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
merged.version = '0.0.0';
|
|
69
|
+
console.warn('[rynt/extension-build] В manifest и package.json нет version — подставлено 0.0.0.');
|
|
70
|
+
}
|
|
71
|
+
if (merged.description === undefined || merged.description === null) {
|
|
72
|
+
if (typeof pkg.description === 'string' && pkg.description) {
|
|
73
|
+
merged.description = pkg.description;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (typeof merged.id !== 'string' || !merged.id) {
|
|
77
|
+
if (typeof pkg.name === 'string' && pkg.name)
|
|
78
|
+
merged.id = pkg.name;
|
|
79
|
+
}
|
|
80
|
+
if (typeof merged.main !== 'string' || !merged.main) {
|
|
81
|
+
merged.main = 'index.js';
|
|
82
|
+
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
export function ryntExtensionEmitDistManifestPlugin(options) {
|
|
86
|
+
const { root, manifestFromVite } = options;
|
|
87
|
+
return {
|
|
88
|
+
name: 'rynt-extension-emit-dist-manifest',
|
|
89
|
+
apply: 'build',
|
|
90
|
+
async closeBundle() {
|
|
91
|
+
const merged = mergeExtensionManifestForDist(root, manifestFromVite);
|
|
92
|
+
if (typeof merged.id !== 'string' || !merged.id) {
|
|
93
|
+
console.warn('[rynt/extension-build] manifest.id обязателен; skip writing dist/manifest.json');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const { normalizeExtensionApiInManifest, applyValidatedRyntContributesToManifest } = await loadManifestValidators();
|
|
98
|
+
const apiErr = normalizeExtensionApiInManifest(merged, { extensionId: merged.id });
|
|
99
|
+
if (apiErr) {
|
|
100
|
+
console.warn(`[rynt/extension-build] extensionApi для «${merged.id}»: ${apiErr}`);
|
|
101
|
+
}
|
|
102
|
+
const contribErr = applyValidatedRyntContributesToManifest({
|
|
103
|
+
id: merged.id,
|
|
104
|
+
contributes: merged.contributes &&
|
|
105
|
+
typeof merged.contributes === 'object' &&
|
|
106
|
+
!Array.isArray(merged.contributes)
|
|
107
|
+
? merged.contributes
|
|
108
|
+
: undefined,
|
|
109
|
+
});
|
|
110
|
+
if (contribErr) {
|
|
111
|
+
console.warn(`[rynt/extension-build] contributes.rynt для «${merged.id}»: ${contribErr}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.warn('[rynt/extension-build] Пропуск валидации manifest через SDK:', e);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
await applyExtensionIconToManifest(root, merged);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
122
|
+
throw new Error(msg);
|
|
123
|
+
}
|
|
124
|
+
const outPath = path.join(root, 'dist', 'manifest.json');
|
|
125
|
+
mkdirSync(path.dirname(outPath), { recursive: true });
|
|
126
|
+
writeFileSync(outPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf-8');
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { UserConfig } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Параметры для `ryntExtensionViteConfig`.
|
|
4
|
+
* - `root` — абсолютный путь к корню расширения
|
|
5
|
+
* - `manifest` — объект манифеста расширения из `vite.config.ts`
|
|
6
|
+
*/
|
|
7
|
+
export interface RyntExtensionViteConfigOptions {
|
|
8
|
+
root: string;
|
|
9
|
+
manifest: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare const RYNT_EXTENSION_SHIMMED_SDK_SPECIFIERS: Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Только ядро TipTap с хоста (один экземпляр с MarkdownEditor).
|
|
14
|
+
* Пакеты `@tiptap/extension-*` ставит автор расширения и собирает в свой dist.
|
|
15
|
+
*/
|
|
16
|
+
export declare const RYNT_EXTENSION_HOST_TIPTAP_CORE = "@tiptap/core";
|
|
17
|
+
export declare const RYNT_EXTENSION_HOST_TIPTAP_SPECIFIERS: Set<string>;
|
|
18
|
+
export declare function ryntExtensionViteConfig(options: RyntExtensionViteConfigOptions): UserConfig;
|
|
19
|
+
export { EXTENSION_ICON_DIST_PNG, EXTENSION_ICON_MAX_SIZE, applyExtensionIconToManifest, isBundledExtensionIconRef, isLocalIconSource, isRemoteOrDataIcon, processExtensionIconForDist, } from './extension-icon.js';
|
|
20
|
+
export { mergeExtensionManifestForDist, ryntExtensionEmitDistManifestPlugin, } from './extension-manifest.js';
|
|
21
|
+
export { RYNT_LAUNCHER_DEV_HUB_DEFAULT, isDevBridgeActive, ryntExtensionDevBridgePlugin, } from './extension-dev-bridge.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,MAAM,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,qCAAqC,aAKhD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,+BAA+B,iBAAiB,CAAC;AAE9D,eAAO,MAAM,qCAAqC,aAEhD,CAAC;AAmNH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,UAAU,CA+CZ;AAED,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,4BAA4B,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,6BAA6B,EAC7B,mCAAmC,GACpC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { mergeExtensionManifestForDist, ryntExtensionEmitDistManifestPlugin, } from './extension-manifest.js';
|
|
5
|
+
import { ryntExtensionDevBridgePlugin } from './extension-dev-bridge.js';
|
|
6
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
7
|
+
import vuePlugin from '@vitejs/plugin-vue';
|
|
8
|
+
export const RYNT_EXTENSION_SHIMMED_SDK_SPECIFIERS = new Set([
|
|
9
|
+
'@rynt/sdk',
|
|
10
|
+
'@rynt/sdk/extension',
|
|
11
|
+
'@rynt/sdk/host',
|
|
12
|
+
'@rynt/sdk/session', // Session token access for extensions
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Только ядро TipTap с хоста (один экземпляр с MarkdownEditor).
|
|
16
|
+
* Пакеты `@tiptap/extension-*` ставит автор расширения и собирает в свой dist.
|
|
17
|
+
*/
|
|
18
|
+
export const RYNT_EXTENSION_HOST_TIPTAP_CORE = '@tiptap/core';
|
|
19
|
+
export const RYNT_EXTENSION_HOST_TIPTAP_SPECIFIERS = new Set([
|
|
20
|
+
RYNT_EXTENSION_HOST_TIPTAP_CORE,
|
|
21
|
+
]);
|
|
22
|
+
const RYNT_EXTENSION_BUNDLED_SDK_SPECIFIERS = new Set([]);
|
|
23
|
+
const shimsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../shims');
|
|
24
|
+
function tiptapHostShimFileName(specifier) {
|
|
25
|
+
return `${specifier.replace('@tiptap/', 'tiptap-')}.host-shim.js`;
|
|
26
|
+
}
|
|
27
|
+
const TIPTAP_CORE_PKG_PATH = /[/\\]@tiptap[/\\]core[/\\]/u;
|
|
28
|
+
function ryntExtensionTiptapCoreHostShimPlugin(shimsAbsoluteRoot) {
|
|
29
|
+
const coreShim = path.join(shimsAbsoluteRoot, tiptapHostShimFileName('@tiptap/core'));
|
|
30
|
+
return {
|
|
31
|
+
name: 'rynt-extension-tiptap-core-host-shim',
|
|
32
|
+
enforce: 'pre',
|
|
33
|
+
resolveId(source) {
|
|
34
|
+
const clean = source.split('\0')[0].split('#')[0].split('?')[0];
|
|
35
|
+
if (clean === '@tiptap/core' || clean.startsWith('@tiptap/core/')) {
|
|
36
|
+
return coreShim;
|
|
37
|
+
}
|
|
38
|
+
const posix = clean.replace(/\\/g, '/');
|
|
39
|
+
if (TIPTAP_CORE_PKG_PATH.test(posix)) {
|
|
40
|
+
return coreShim;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function ryntExtensionRollupHostShimResolvePlugin(shimsAbsoluteRoot) {
|
|
47
|
+
const specToShimFile = {};
|
|
48
|
+
for (const spec of RYNT_EXTENSION_SHIMMED_SDK_SPECIFIERS) {
|
|
49
|
+
const fileName = `rynt-sdk${spec === '@rynt/sdk' ? '' : spec.replace('@rynt/sdk/', '-')}.host-shim.js`;
|
|
50
|
+
specToShimFile[spec] = path.join(shimsAbsoluteRoot, fileName);
|
|
51
|
+
}
|
|
52
|
+
for (const spec of RYNT_EXTENSION_HOST_TIPTAP_SPECIFIERS) {
|
|
53
|
+
specToShimFile[spec] = path.join(shimsAbsoluteRoot, tiptapHostShimFileName(spec));
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
name: 'rynt-extension-rollup-host-shim-resolve',
|
|
57
|
+
resolveId(id) {
|
|
58
|
+
const clean = id.split('\0')[0].split('#')[0].split('?')[0];
|
|
59
|
+
if (specToShimFile[clean]) {
|
|
60
|
+
return specToShimFile[clean];
|
|
61
|
+
}
|
|
62
|
+
const posix = clean.replace(/\\/g, '/');
|
|
63
|
+
if (specToShimFile['@tiptap/core']
|
|
64
|
+
&& TIPTAP_CORE_PKG_PATH.test(posix)) {
|
|
65
|
+
return specToShimFile['@tiptap/core'];
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function ryntRollupMergeExternalPlugin(shimsAbsoluteRoot) {
|
|
72
|
+
return {
|
|
73
|
+
name: 'rynt-extension-merge-external-with-shims',
|
|
74
|
+
apply: 'build',
|
|
75
|
+
enforce: 'post',
|
|
76
|
+
options(optionList) {
|
|
77
|
+
const previousExternal = optionList.external;
|
|
78
|
+
optionList.external = (src, importer, resolved) => {
|
|
79
|
+
const raw = typeof src === 'string' ? src : String(src);
|
|
80
|
+
const trimmed = raw.split('\0')[0].split('#')[0].split('?')[0];
|
|
81
|
+
const posix = trimmed.replace(/\\/g, '/');
|
|
82
|
+
const shimsPosix = shimsAbsoluteRoot.replace(/\\/g, '/');
|
|
83
|
+
if (posix.startsWith(`${shimsPosix}/`)
|
|
84
|
+
|| posix.includes('/extension-build/shims/')) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (trimmed === 'vue'
|
|
88
|
+
|| trimmed === 'vue-router'
|
|
89
|
+
|| RYNT_EXTENSION_SHIMMED_SDK_SPECIFIERS.has(trimmed)
|
|
90
|
+
|| RYNT_EXTENSION_HOST_TIPTAP_SPECIFIERS.has(trimmed)
|
|
91
|
+
|| RYNT_EXTENSION_BUNDLED_SDK_SPECIFIERS.has(trimmed)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (trimmed === '@rynt/sdk'
|
|
95
|
+
|| (typeof trimmed === 'string' && trimmed.startsWith('@rynt/sdk/'))) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
// @tiptap/extension-* и прочие подпакеты — в бандл расширения (не с хоста).
|
|
99
|
+
if (trimmed.startsWith('@tiptap/')
|
|
100
|
+
&& trimmed !== RYNT_EXTENSION_HOST_TIPTAP_CORE) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const prevMarks = coerceExternal(previousExternal, trimmed, importer, resolved);
|
|
104
|
+
return Boolean(prevMarks);
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function coerceExternal(previous, id, importer, resolved) {
|
|
110
|
+
if (previous == null) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (typeof previous === 'function') {
|
|
114
|
+
return Boolean(previous(id, importer, resolved ?? false));
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(previous)) {
|
|
117
|
+
return previous.some((entry) => typeof entry === 'string' ? entry === id
|
|
118
|
+
: entry instanceof RegExp ? entry.test(id)
|
|
119
|
+
: false);
|
|
120
|
+
}
|
|
121
|
+
return previous instanceof RegExp ? previous.test(id) : false;
|
|
122
|
+
}
|
|
123
|
+
const RYNT_EXT_TAILWIND_VIRTUAL = '\0rynt-extension:tailwind-entry.css';
|
|
124
|
+
const RYNT_EXT_TAILWIND_PUBLIC = 'virtual:rynt-extension/tailwind-entry.css';
|
|
125
|
+
function ryntExtensionVirtualTailwindPlugin(root) {
|
|
126
|
+
const entryAbs = path.resolve(root, 'src/index.ts');
|
|
127
|
+
const manualTailwindCss = path.join(root, 'src/tailwind.css');
|
|
128
|
+
return {
|
|
129
|
+
name: 'rynt-extension-virtual-tailwind-entry',
|
|
130
|
+
enforce: 'pre',
|
|
131
|
+
resolveId(id) {
|
|
132
|
+
if (id === RYNT_EXT_TAILWIND_PUBLIC)
|
|
133
|
+
return RYNT_EXT_TAILWIND_VIRTUAL;
|
|
134
|
+
return undefined;
|
|
135
|
+
},
|
|
136
|
+
load(id) {
|
|
137
|
+
if (id === RYNT_EXT_TAILWIND_VIRTUAL) {
|
|
138
|
+
return '@import "tailwindcss";\n';
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
},
|
|
142
|
+
transform(code, id) {
|
|
143
|
+
if (existsSync(manualTailwindCss))
|
|
144
|
+
return null;
|
|
145
|
+
if (path.resolve(id) !== entryAbs)
|
|
146
|
+
return null;
|
|
147
|
+
if (/import\s+['"][^'"]*tailwind\.css['"]/u.test(code))
|
|
148
|
+
return null;
|
|
149
|
+
return `import '${RYNT_EXT_TAILWIND_PUBLIC}';\n${code}`;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function hostShimAliases() {
|
|
154
|
+
return [
|
|
155
|
+
{ find: /^vue$/u, replacement: path.join(shimsRoot, 'vue.host-shim.js') },
|
|
156
|
+
{
|
|
157
|
+
find: /^vue-router$/u,
|
|
158
|
+
replacement: path.join(shimsRoot, 'vue-router.host-shim.js'),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
find: /^@rynt\/sdk$/u,
|
|
162
|
+
replacement: path.join(shimsRoot, 'rynt-sdk.host-shim.js'),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
find: /^@rynt\/sdk\/extension$/u,
|
|
166
|
+
replacement: path.join(shimsRoot, 'rynt-sdk-extension.host-shim.js'),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
find: /^@rynt\/sdk\/host$/u,
|
|
170
|
+
replacement: path.join(shimsRoot, 'rynt-sdk-host.host-shim.js'),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
find: /^@tiptap\/core(\/.*)?$/u,
|
|
174
|
+
replacement: path.join(shimsRoot, tiptapHostShimFileName('@tiptap/core')),
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
export function ryntExtensionViteConfig(options) {
|
|
179
|
+
const { root, manifest } = options;
|
|
180
|
+
const merged = mergeExtensionManifestForDist(root, manifest);
|
|
181
|
+
const manifestId = typeof merged.id === 'string' && merged.id.length > 0 ? merged.id : null;
|
|
182
|
+
if (!manifestId) {
|
|
183
|
+
console.warn('[rynt/extension-build] Не удалось определить id (manifest.id и package.json name пусты); registry writes will fall under "rynt" (host).');
|
|
184
|
+
}
|
|
185
|
+
const resolveAliases = [
|
|
186
|
+
...hostShimAliases(),
|
|
187
|
+
{ find: /^@\//u, replacement: `${path.join(root, 'src')}/` },
|
|
188
|
+
];
|
|
189
|
+
return {
|
|
190
|
+
plugins: [
|
|
191
|
+
ryntExtensionTiptapCoreHostShimPlugin(shimsRoot),
|
|
192
|
+
ryntExtensionVirtualTailwindPlugin(root),
|
|
193
|
+
tailwindcss(),
|
|
194
|
+
vuePlugin(),
|
|
195
|
+
ryntRollupMergeExternalPlugin(shimsRoot),
|
|
196
|
+
ryntExtensionDevBridgePlugin({ root, manifestFromVite: manifest }),
|
|
197
|
+
ryntExtensionEmitDistManifestPlugin({ root, manifestFromVite: manifest }),
|
|
198
|
+
],
|
|
199
|
+
resolve: {
|
|
200
|
+
alias: resolveAliases,
|
|
201
|
+
dedupe: ['@tiptap/core'],
|
|
202
|
+
},
|
|
203
|
+
define: {
|
|
204
|
+
__RYNT_EXTENSION_ID__: JSON.stringify(manifestId ?? 'rynt'),
|
|
205
|
+
},
|
|
206
|
+
build: {
|
|
207
|
+
lib: {
|
|
208
|
+
entry: path.join(root, 'src/index.ts'),
|
|
209
|
+
name: 'rynt-extension',
|
|
210
|
+
formats: ['es'],
|
|
211
|
+
fileName: 'index',
|
|
212
|
+
},
|
|
213
|
+
outDir: path.join(root, 'dist'),
|
|
214
|
+
emptyOutDir: true,
|
|
215
|
+
rollupOptions: {
|
|
216
|
+
plugins: [ryntExtensionRollupHostShimResolvePlugin(shimsRoot)],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export { EXTENSION_ICON_DIST_PNG, EXTENSION_ICON_MAX_SIZE, applyExtensionIconToManifest, isBundledExtensionIconRef, isLocalIconSource, isRemoteOrDataIcon, processExtensionIconForDist, } from './extension-icon.js';
|
|
222
|
+
export { mergeExtensionManifestForDist, ryntExtensionEmitDistManifestPlugin, } from './extension-manifest.js';
|
|
223
|
+
export { RYNT_LAUNCHER_DEV_HUB_DEFAULT, isDevBridgeActive, ryntExtensionDevBridgePlugin, } from './extension-dev-bridge.js';
|