mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: plugin-sdk safety all around (#29323)
This commit is contained in:
parent
cb1af3a8ec
commit
688241a462
11 changed files with 200 additions and 184 deletions
|
|
@ -5,8 +5,8 @@
|
|||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm build:tsc && pnpm build:wasm",
|
||||
"build:tsc": "mkdir -p dist && echo \"type Manifest = $(cat manifest.json); \nexport default Manifest;\" > dist/manifest.d.ts && tsc --noEmit && node esbuild.js",
|
||||
"build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm"
|
||||
"build:tsc": "plugin-sdk prepareBuild && tsc --noEmit && node esbuild.js",
|
||||
"build:wasm": "extism-js dist/index.js -i dist/index.d.ts -o dist/plugin.wasm"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
|
|||
27
packages/plugin-core/src/index.d.ts
vendored
27
packages/plugin-core/src/index.d.ts
vendored
|
|
@ -1,27 +0,0 @@
|
|||
// keep in sync with plugin-sdk/host-functions.ts';
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
searchAlbums(ptr: PTR): I64;
|
||||
createAlbum(ptr: PTR): I64;
|
||||
addAssetsToAlbum(ptr: PTR): I64;
|
||||
addAssetsToAlbums(ptr: PTR): I64;
|
||||
}
|
||||
}
|
||||
|
||||
// keep in sync with manifest.json
|
||||
declare module 'main' {
|
||||
// filters
|
||||
export function assetFileFilter(): I32;
|
||||
export function assetMissingTimeZoneFilter(): I32;
|
||||
export function assetLocationFilter(): I32;
|
||||
export function assetTypeFilter(): I32;
|
||||
|
||||
// updates
|
||||
export function assetFavorite(): I32;
|
||||
export function assetVisibility(): I32;
|
||||
export function assetArchive(): I32;
|
||||
export function assetLock(): I32;
|
||||
export function assetTimeline(): I32;
|
||||
// export function assetTrash(): I32;
|
||||
export function assetAddToAlbums(): I32;
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
import { getWrapper } from '@immich/plugin-sdk';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import type manifestType from '../dist/manifest';
|
||||
import type { Manifest } from '../dist/index.d.ts';
|
||||
|
||||
const wrapper = getWrapper<manifestType>();
|
||||
const wrapper = getWrapper<Manifest>();
|
||||
|
||||
export const assetFileFilter = () => {
|
||||
return wrapper<'assetFileFilter'>(({ data, config }) => {
|
||||
export const assetFileFilter = wrapper<'assetFileFilter'>(({ data, config }) => {
|
||||
const { pattern, matchType = 'contains', caseSensitive = false } = config;
|
||||
|
||||
const { asset } = data;
|
||||
|
|
@ -38,18 +37,14 @@ export const assetFileFilter = () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const assetMissingTimeZoneFilter = () => {
|
||||
return wrapper<'assetMissingTimeZoneFilter'>(({ config, data }) => {
|
||||
export const assetMissingTimeZoneFilter = wrapper<'assetMissingTimeZoneFilter'>(({ config, data }) => {
|
||||
const hasTimeZone = !!data.asset?.exifInfo?.timeZone;
|
||||
const needsTimeZone = config.inverse ? true : false;
|
||||
return { workflow: { continue: hasTimeZone === needsTimeZone } };
|
||||
});
|
||||
};
|
||||
|
||||
export const assetLocationFilter = () => {
|
||||
return wrapper<'assetLocationFilter'>(({ config, data }) => {
|
||||
export const assetLocationFilter = wrapper<'assetLocationFilter'>(({ config, data }) => {
|
||||
if (
|
||||
(config.region?.country && config.region.country !== data.asset.exifInfo?.country) ||
|
||||
(config.region?.state && config.region.state !== data.asset.exifInfo?.state) ||
|
||||
|
|
@ -85,16 +80,12 @@ export const assetLocationFilter = () => {
|
|||
|
||||
return { workflow: { continue: earthDiameter * delta <= (config.coordinate?.radius ?? 0) } };
|
||||
});
|
||||
};
|
||||
|
||||
export const assetTypeFilter = () => {
|
||||
return wrapper<'assetTypeFilter'>(({ config, data }) => {
|
||||
export const assetTypeFilter = wrapper<'assetTypeFilter'>(({ config, data }) => {
|
||||
return { workflow: { continue: config.allowedTypes.includes(data.asset.type) } };
|
||||
});
|
||||
};
|
||||
|
||||
export const assetFavorite = () => {
|
||||
return wrapper<'assetFavorite'>(({ config, data }) => {
|
||||
export const assetFavorite = wrapper<'assetFavorite'>(({ config, data }) => {
|
||||
const target = config.inverse ? false : true;
|
||||
if (target !== data.asset.isFavorite) {
|
||||
return {
|
||||
|
|
@ -104,16 +95,12 @@ export const assetFavorite = () => {
|
|||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const assetVisibility = () => {
|
||||
return wrapper<'assetVisibility'>(({ config }) => ({
|
||||
export const assetVisibility = wrapper<'assetVisibility'>(({ config }) => ({
|
||||
changes: { asset: { visibility: config.visibility as AssetVisibility } },
|
||||
}));
|
||||
};
|
||||
|
||||
export const assetArchive = () => {
|
||||
return wrapper<'assetArchive'>(({ config, data }) => {
|
||||
export const assetArchive = wrapper<'assetArchive'>(({ config, data }) => {
|
||||
if (!config.inverse && data.asset.visibility !== AssetVisibility.Archive) {
|
||||
return { changes: { asset: { visibility: AssetVisibility.Archive } } };
|
||||
}
|
||||
|
|
@ -124,10 +111,8 @@ export const assetArchive = () => {
|
|||
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
export const assetLock = () => {
|
||||
return wrapper<'assetLock'>(({ config, data }) => {
|
||||
export const assetLock = wrapper<'assetLock'>(({ config, data }) => {
|
||||
if (!config.inverse && data.asset.visibility !== AssetVisibility.Locked) {
|
||||
return { changes: { asset: { visibility: AssetVisibility.Locked } } };
|
||||
}
|
||||
|
|
@ -138,15 +123,13 @@ export const assetLock = () => {
|
|||
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
// export const assetTrash = () => {
|
||||
// // TODO use trash/untrash host functions
|
||||
// return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(() => ({}));
|
||||
// };
|
||||
|
||||
export const assetAddToAlbums = () => {
|
||||
return wrapper<'assetAddToAlbums'>(({ config, data, functions }) => {
|
||||
export const assetAddToAlbums = wrapper<'assetAddToAlbums'>(({ config, data, functions }) => {
|
||||
const assetId = data.asset.id;
|
||||
|
||||
if (config.albumIds.length === 0) {
|
||||
|
|
@ -172,4 +155,3 @@ export const assetAddToAlbums = () => {
|
|||
functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [assetId] });
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"skipLibCheck": true, // Skip type checking of declaration files
|
||||
"strict": true, // Enable all strict type-checking options
|
||||
"target": "es2020", // Specify ECMAScript target version
|
||||
"types": ["./src/index.d.ts", "./node_modules/@extism/js-pdk"] // Specify a list of type definition files to be included in the compilation
|
||||
"types": ["./dist/index.d.ts", "./node_modules/@extism/js-pdk"] // Specify a list of type definition files to be included in the compilation
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules" // Exclude the node_modules directory
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import esbuild from 'esbuild';
|
||||
|
||||
esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
entryPoints: ['src/index.ts', 'src/cli.ts'],
|
||||
outdir: 'dist',
|
||||
bundle: true,
|
||||
sourcemap: false,
|
||||
minify: false,
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
target: ['es2020'],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@
|
|||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"plugin-sdk": "./plugin-sdk.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
|
|
@ -35,5 +38,8 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@extism/js-pdk": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^15.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
packages/plugin-sdk/plugin-sdk.mjs
Executable file
2
packages/plugin-sdk/plugin-sdk.mjs
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
import "./dist/cli.js";
|
||||
43
packages/plugin-sdk/src/cli.ts
Normal file
43
packages/plugin-sdk/src/cli.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Command } from 'commander';
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
import { availableFunctions } from 'src/host-functions.js';
|
||||
|
||||
const program = new Command('plugin-sdk');
|
||||
|
||||
program
|
||||
.command('prepareBuild')
|
||||
.description('Generate .d.ts file required for extism')
|
||||
.argument(
|
||||
'[manifest]',
|
||||
"Path to the plugins's manifest file",
|
||||
'manifest.json',
|
||||
)
|
||||
.option('-o --output', 'Output file for generated types', 'dist/index.d.ts')
|
||||
.action((manifest: string, { output }) => {
|
||||
const content = readFileSync(manifest, { encoding: 'utf-8' });
|
||||
const methods = (
|
||||
JSON.parse(content) as { methods: { name: string }[] }
|
||||
).methods.map(({ name }) => name);
|
||||
mkdirSync(dirname(output), { recursive: true });
|
||||
|
||||
writeFileSync(
|
||||
output,
|
||||
`
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
${availableFunctions.map((functionName) => ` ${functionName}(ptr: PTR): I64;`).join('\n')}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'main' {
|
||||
${methods.map((method) => ` export function ${method}(): I32;`).join('\n')}
|
||||
}
|
||||
|
||||
export type Manifest = ${content};
|
||||
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
|
|
@ -6,14 +6,11 @@ import {
|
|||
type CreateAlbumDto,
|
||||
} from '@immich/sdk';
|
||||
|
||||
// keep in sync with plugin-core/src/index.d.ts';
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
searchAlbums(ptr: PTR): I64;
|
||||
createAlbum(ptr: PTR): I64;
|
||||
addAssetsToAlbum(ptr: PTR): I64;
|
||||
addAssetsToAlbums(ptr: PTR): I64;
|
||||
}
|
||||
interface user extends Record<
|
||||
(typeof availableFunctions)[number],
|
||||
(ptr: PTR) => I64
|
||||
> {}
|
||||
}
|
||||
|
||||
type AlbumsToAssets = {
|
||||
|
|
@ -34,6 +31,13 @@ type HostFunctionResult<T> =
|
|||
type QueryParams<T extends (...args: any) => any> = Parameters<T>[0];
|
||||
type AlbumSearchDto = QueryParams<typeof getAllAlbums>;
|
||||
|
||||
export const availableFunctions = [
|
||||
'searchAlbums',
|
||||
'createAlbum',
|
||||
'addAssetsToAlbum',
|
||||
'addAssetsToAlbums',
|
||||
] as const;
|
||||
|
||||
export const hostFunctions = (authToken: string) => {
|
||||
const host = Host.getFunctions();
|
||||
type HostFunctionName = keyof typeof host;
|
||||
|
|
@ -75,5 +79,5 @@ export const hostFunctions = (authToken: string) => {
|
|||
),
|
||||
addAssetsToAlbums: ({ assetIds, albumIds }: AlbumsToAssets) =>
|
||||
call('addAssetsToAlbums', authToken, [{ albumIds, assetIds }]),
|
||||
};
|
||||
} satisfies Record<(typeof availableFunctions)[number], unknown>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ export const getWrapper =
|
|||
functions: ReturnType<typeof hostFunctions>;
|
||||
},
|
||||
) => WorkflowResponse<L> | undefined,
|
||||
) => {
|
||||
) =>
|
||||
() => {
|
||||
const input = Host.inputString();
|
||||
|
||||
try {
|
||||
|
|
|
|||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
|
|
@ -336,6 +336,10 @@ importers:
|
|||
version: 6.0.3
|
||||
|
||||
packages/plugin-sdk:
|
||||
dependencies:
|
||||
commander:
|
||||
specifier: ^15.0.0
|
||||
version: 15.0.0
|
||||
devDependencies:
|
||||
'@extism/js-pdk':
|
||||
specifier: ^1.1.1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue