New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

esbuild-plugin-scriptable

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

esbuild-plugin-scriptable - npm Package Compare versions

Comparing version 0.4.0 to 0.5.0

4

dist/scriptable-banner.d.ts

@@ -12,6 +12,6 @@ import { Plugin } from 'esbuild';

warnOnMissingEntry?: boolean;
/**glob matching pattern, supports ! prefix to exclude files */
patterns?: string | string[];
/** Custom manifest file path mapping function */
getManifestPath?: (entryPath: string) => string;
/**glob matching pattern, supports ! prefix to exclude files */
patterns?: string | string[];
}

@@ -18,0 +18,0 @@ declare function scriptableBanner(options?: ScriptablePluginOptions): Plugin;

@@ -23,4 +23,4 @@ import { Plugin } from 'esbuild';

* Examples:
* - '**\/*.widget.ts' // only deploy widget files
* - ['**\/*.ts', '!**\/*.test.ts'] // exclude test files
* -'**\/*.widget.ts' //only deploy widget files
* -['**\/*.ts', '!**\/*.test.ts'] //exclude test files
*/

@@ -27,0 +27,0 @@ patterns?: string | string[];

@@ -0,1 +1,3 @@

import micromatch from 'micromatch';
/**

@@ -7,3 +9,16 @@ * Creates a file pattern matcher function

declare function createPatternMatcher(patterns?: string | string[]): (fileName: string) => boolean;
/**
* Checks if a file name matches a pattern
*
* @param fileName - The file name to check
* @param pattern - The pattern to match
* @param options - The options to pass to micromatch
* @returns True if the file name matches the pattern
*/
declare function isMatch(fileName: string, pattern: string | readonly string[], options?: micromatch.Options): boolean;
declare function waitForFile(filePath: string, timeout?: number): Promise<void>;
declare function normalizePath(path: string): string;
declare const np: typeof normalizePath;
declare function isPathEqual(path1: string, path2: string): boolean;
export { createPatternMatcher };
export { createPatternMatcher, isMatch, isPathEqual, normalizePath, np, waitForFile };
{
"name": "esbuild-plugin-scriptable",
"description": "An ESBuild plugin for developing Scriptable iOS app scripts with TypeScript, manifest support and auto-deployment",
"version": "0.4.0",
"version": "0.5.0",
"keywords": [

@@ -28,6 +28,5 @@ "scriptable",

"dependencies": {
"esbuild": "^0.24.0",
"findicloud": "^0.1.3",
"micromatch": "^4.0.8",
"@scriptables/manifest": "^0.6.0"
"@scriptables/manifest": "0.7.0"
},

@@ -43,4 +42,2 @@ "devDependencies": {

"jest": "^29.7.0",
"memfs": "^4.14.0",
"mock-fs": "^5.4.1",
"tmp": "^0.2.3",

@@ -51,2 +48,5 @@ "tslib": "^2.8.1",

},
"peerDependencies": {
"esbuild": "^0.24.0"
},
"exports": {

@@ -53,0 +53,0 @@ ".": {

@@ -15,2 +15,3 @@ # esbuild-plugin-scriptable

- Supports manifest files for script configuration
- Uses manifest name as output filename (if specified)
- Handles deployment to iCloud Drive with auto-detection

@@ -90,2 +91,22 @@ - Full TypeScript support with type definitions

## Manifest Configuration
The manifest file can be used to configure both the script metadata and the output filename:
```json
{
"name": "weather-widget",
"alwaysRunInApp": true,
"shareSheetInputs": ["file-url", "url"],
"iconColor": "blue",
"iconGlyph": "cloud"
}
```
When a manifest includes a `name` property:
- The deploy plugin will use this name for the output file (e.g., `weather-widget.js`)
- If no name is specified, the original filename will be used
- The banner plugin will still use all manifest properties to generate the script metadata
## Advanced Usage

@@ -92,0 +113,0 @@

@@ -38,3 +38,3 @@ export type LogLevel = 'verbose' | 'info' | 'warn' | 'error';

if (this.shouldLog('info')) {
console.info(`[${this.namespace}]`, ...args);
console.log(`[${this.namespace}]`, ...args);
}

@@ -41,0 +41,0 @@ }

import type {ScriptableManifest} from '@scriptables/manifest';
import {generateScriptableBanner} from '@scriptables/manifest/generateScriptableBanner';
import {validateManifest} from '@scriptables/manifest/validateManifest';
import type {Plugin} from 'esbuild';
import {existsSync} from 'fs';
import {readFile, writeFile} from 'fs/promises';
import micromatch from 'micromatch';
import {basename, dirname, posix, resolve, sep} from 'path';
import {createLogger} from './logger';
import {isMatch, normalizePath, waitForFile} from './utils';

@@ -16,12 +17,17 @@ const DEFAULT_MANIFEST_EXTENSIONS = ['.manifest.json', '.manifest', '.json'];

manifestExtensions?: string[];
/** Show warning when manifest file is missing */
warnOnMissingManifest?: boolean;
/** Show warning when manifest file is invalid */
warnOnInvalidManifest?: boolean;
/** Show warning when no matching entry point is found */
warnOnMissingEntry?: boolean;
/**glob matching pattern, supports ! prefix to exclude files */
patterns?: string | string[];
/** Custom manifest file path mapping function */
getManifestPath?: (entryPath: string) => string;
/**glob matching pattern, supports ! prefix to exclude files */
patterns?: string | string[];
}

@@ -60,3 +66,3 @@

build.onResolve({filter: /.*/}, async args => {
if (!micromatch.isMatch(args.path, patterns)) {
if (!isMatch(args.path, patterns)) {
return null;

@@ -80,8 +86,11 @@ }

const manifest = JSON.parse(manifestContent);
const normalizedPath = posix.normalize(args.path.split(sep).join('/'));
const normalizedPath = normalizePath(args.path);
if (!manifest.name || !manifest.author || !manifest.version) {
if (warnOnInvalidManifest) {
logger.warn(`Invalid manifest file: ${manifestPath} - missing required fields`);
try {
validateManifest(manifest);
} catch (error) {
if (warnOnInvalidManifest && error instanceof Error) {
logger.warn(`Invalid manifest file: ${manifestPath} - ${error.message}`, error);
}
return null;

@@ -98,2 +107,3 @@ }

}
return null;

@@ -108,2 +118,3 @@ });

);
return;

@@ -114,7 +125,2 @@ }

for (const outputPath of Object.keys(outputs)) {
if (build.initialOptions.write !== false && !existsSync(outputPath)) {
logger.warn(`Output file does not exist: ${outputPath}`);
continue;
}
const outputMeta = outputs[outputPath];

@@ -136,2 +142,3 @@ const entryPoint = outputMeta?.entryPoint ?? '';

if (result.outputFiles) {
// Memory mode: Modify outputFiles directly
const outputFile = result.outputFiles.find(file => file.path === outputPath || file.path === '<stdout>');

@@ -142,5 +149,13 @@ if (outputFile) {

} else {
const content = await readFile(outputPath, 'utf-8');
const updatedContent = banner + content;
await writeFile(outputPath, updatedContent, 'utf8');
// File Write Mode: Wait for the file to be written for processing
try {
await waitForFile(outputPath);
const content = await readFile(outputPath, 'utf-8');
const updatedContent = banner + content.trimEnd();
await writeFile(outputPath, updatedContent, 'utf8');
} catch (error) {
logger.error(
`Failed to process file ${outputPath}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

@@ -147,0 +162,0 @@ } catch (error) {

@@ -5,7 +5,7 @@ import type {ScriptableManifest} from '@scriptables/manifest';

import {findICloudDrivePaths, PathType} from 'findicloud';
import {copyFile, readFile, writeFile} from 'fs/promises';
import {readFile, writeFile} from 'fs/promises';
import {basename, join} from 'path';
import {createLogger} from './logger';
import {createPatternMatcher} from './utils';
import {createPatternMatcher, isPathEqual as isSamePath, waitForFile} from './utils';

@@ -35,4 +35,4 @@ export interface ScriptableDeployOptions {

* Examples:
* - '**\/*.widget.ts' // only deploy widget files
* - ['**\/*.ts', '!**\/*.test.ts'] // exclude test files
* -'**\/*.widget.ts' //only deploy widget files
* -['**\/*.ts', '!**\/*.test.ts'] //exclude test files
*/

@@ -61,2 +61,3 @@ patterns?: string | string[];

const content = await readFile(manifestPath, 'utf-8');
return JSON.parse(content);

@@ -67,2 +68,3 @@ } catch {

}
return null;

@@ -94,3 +96,3 @@ }

if (!build.initialOptions.metafile) {
logger.verbose(verbose, 'Automatically enabling metafile option');
logger.verbose('Automatically enabling metafile option');
build.initialOptions.metafile = true;

@@ -118,5 +120,3 @@ }

if (verbose) {
console.log(`[scriptable-deploy] Target directory: ${targetDir}`);
}
logger.verbose(`Target directory: ${targetDir}`);
} catch (error) {

@@ -134,2 +134,3 @@ const message = `Failed to find Scriptable directory: ${(error as Error).message}`;

logger.error('Missing required configuration');
return;

@@ -141,2 +142,3 @@ }

const {entryPoint} = result.metafile!.outputs[file];
return entryPoint && filterFile(entryPoint);

@@ -146,33 +148,50 @@ });

for (const file of outputs) {
const fileName = basename(file);
const targetPath = join(targetDir, fileName);
const output = result.metafile.outputs[file];
if (addBanner) {
try {
// Get source file path
const inputs = Object.entries(result.metafile.outputs[file].inputs);
if (!inputs.length) {
throw new Error(`No input files found for ${file}`);
}
const sourceFile = inputs[0][0];
if (!sourceFile) {
throw new Error(`Invalid source file for ${file}`);
}
// Read compiled content
const content = await readFile(file, 'utf-8');
// 获取文件内容
let content: string;
if (result.outputFiles) {
const outputFile = result.outputFiles.find(f => isSamePath(f.path, file));
if (!outputFile) {
throw new Error(`Output file not found: ${file}`);
}
content = new TextDecoder().decode(outputFile.contents);
} else {
await waitForFile(file);
content = await readFile(file, 'utf-8');
}
// Find manifest based on source file path
const manifest = await tryReadManifest(sourceFile, manifestExtensions);
// 尝试读取 manifest
let targetFileName = basename(file);
let manifest: ScriptableManifest | null = null;
if (manifest) {
// Generate new content with banner
const banner = generateScriptableBanner(manifest);
const newContent = banner + '\n' + content;
try {
const inputs = Object.entries(output.inputs);
if (!inputs.length) {
throw new Error(`No input files found for ${file}`);
}
const sourceFile = inputs[0][0];
if (!sourceFile) {
throw new Error(`Invalid source file for ${file}`);
}
// Write to target file
await writeFile(targetPath, newContent);
manifest = await tryReadManifest(sourceFile, manifestExtensions);
console.log(`Deployed with banner: ${file} -> ${targetPath}`);
continue;
}
// 如果 manifest 中定义了名字,使用它作为目标文件名
if (manifest?.name) {
targetFileName = `${manifest.name}.js`;
}
} catch (error) {
logger.warn(`Failed to read manifest for ${file}: ${error}`);
}
const targetPath = join(targetDir, targetFileName);
if (addBanner && manifest) {
try {
const banner = generateScriptableBanner(manifest);
const newContent = banner + '\n' + content;
await writeFile(targetPath, newContent);
logger.info(`Deployed with banner: ${file} -> ${targetPath}`);
continue;
} catch (error) {

@@ -183,11 +202,10 @@ logger.warn(`Failed to add banner to ${file}: ${error}`);

// If the banner is not needed or the processing fails, copy the file directly
await copyFile(file, targetPath);
console.log(`Deployed: ${file} -> ${targetPath}`);
// 如果不需要 banner 或处理失败,直接写入内容
await writeFile(targetPath, content);
logger.info(`Deployed: ${file} -> ${targetPath}`);
}
console.log(`Successfully deployed ${outputs.length} files`);
logger.info(`Successfully deployed ${outputs.length} files`);
} catch (error) {
console.error(`Deploy failed: ${(error as Error).message}`);
logger.error(`Deploy failed: ${(error as Error).message}`);
if (!continueOnError) {

@@ -194,0 +212,0 @@ throw error;

@@ -0,2 +1,5 @@

import {existsSync} from 'fs';
import {readFile} from 'fs/promises';
import micromatch from 'micromatch';
import {resolve} from 'path';

@@ -22,1 +25,39 @@ /**

}
/**
* Checks if a file name matches a pattern
*
* @param fileName - The file name to check
* @param pattern - The pattern to match
* @param options - The options to pass to micromatch
* @returns True if the file name matches the pattern
*/
export function isMatch(fileName: string, pattern: string | readonly string[], options?: micromatch.Options): boolean {
return micromatch.isMatch(fileName, pattern, options);
}
// Delayed file check function
export async function waitForFile(filePath: string, timeout = 5000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout)
if (existsSync(filePath)) {
try {
await readFile(filePath, 'utf-8'); // Check if file is readable
return; // If the file is readable, exit the loop
} catch {
await new Promise(resolve => setTimeout(resolve, 100)); // Try again after a delay
}
}
throw new Error(`File not available after ${timeout}ms: ${filePath}`);
}
export function normalizePath(path: string): string {
return path.replace(/\\/g, '/');
}
export const np = normalizePath;
export function isPathEqual(path1: string, path2: string): boolean {
return resolve(path1) === resolve(path2);
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc