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
0
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.7.1 to 0.7.2

143

dist/cjs/logger.js

@@ -9,61 +9,102 @@ 'use strict';

const timers = /* @__PURE__ */ new Map();
function createLogger(prefix, options = {}) {
const { level = "info" } = options;
const formatPrefix = () => chalk__default.default.cyan(`[${prefix}]`);
const formatTime = (ms) => chalk__default.default.gray(`(${ms}ms)`);
return {
info: (message, ...args) => {
console.log(`${formatPrefix()} ${message}`, ...args);
},
warn: (message, ...args) => {
console.warn(`${formatPrefix()} ${chalk__default.default.yellow("warning")} ${message}`, ...args);
},
error: (message, ...args) => {
console.error(`${formatPrefix()} ${chalk__default.default.red("error")} ${message}`, ...args);
},
success: (message, ...args) => {
console.log(`${formatPrefix()} ${chalk__default.default.green("\u2713")} ${message}`, ...args);
},
report: (stats) => {
const { total, success, skipped, failed } = stats;
const hasIssues = failed > 0 || skipped > 0;
console.log("\n" + formatPrefix() + " " + chalk__default.default.bold("Build Summary:"));
const rows = [
[` ${chalk__default.default.bold("Total files:")}`, `${total}`],
[` ${chalk__default.default.bold("Processed:")}`, `${chalk__default.default.green(success)}`],
[` ${chalk__default.default.bold("Skipped:")}`, `${skipped > 0 ? chalk__default.default.yellow(skipped) : skipped}`],
[` ${chalk__default.default.bold("Failed:")}`, `${failed > 0 ? chalk__default.default.red(failed) : failed}`]
];
rows.forEach(([label, value]) => {
console.log(`${label.padEnd(20)}${value}`);
});
if (hasIssues) {
console.log("\n" + formatPrefix() + " " + chalk__default.default.yellow("\u26A0\uFE0F Some files were not processed successfully"));
} else {
console.log("\n" + formatPrefix() + " " + chalk__default.default.green("\u2728 All files processed successfully"));
}
console.log();
},
time: (label) => {
timers.set(label, performance.now());
},
timeEnd: (label) => {
const start = timers.get(label);
function convertEsbuildLogLevel(level) {
switch (level) {
case "debug":
return "verbose";
case "info":
return "info";
case "warning":
return "warn";
case "error":
return "error";
case "silent":
return "silent";
default:
return "info";
}
}
class ConsoleLogger {
constructor(prefix, level = "info") {
this.prefix = prefix;
this.level = level;
}
timers = /* @__PURE__ */ new Map();
LOG_LEVELS = {
silent: 0,
error: 1,
warn: 2,
info: 3,
verbose: 4
};
formatPrefix() {
return chalk__default.default.cyan(`[${this.prefix}]`);
}
formatTime(ms) {
return chalk__default.default.gray(`(${ms}ms)`);
}
shouldLog(messageLevel) {
if (this.level === "silent") {
return false;
}
return this.LOG_LEVELS[messageLevel] <= this.LOG_LEVELS[this.level];
}
info(message, ...args) {
if (this.shouldLog("info")) {
console.log(`${this.formatPrefix()} ${message}`, ...args);
}
}
warn(message, ...args) {
if (this.shouldLog("warn")) {
console.warn(`${this.formatPrefix()} ${chalk__default.default.yellow("warning")} ${message}`, ...args);
}
}
error(message, ...args) {
if (this.shouldLog("error")) {
console.error(`${this.formatPrefix()} ${chalk__default.default.red("error")} ${message}`, ...args);
}
}
success(message, ...args) {
if (this.shouldLog("info")) {
console.log(`${this.formatPrefix()} ${chalk__default.default.green("\u2713")} ${message}`, ...args);
}
}
verbose(message, ...args) {
if (this.shouldLog("verbose")) {
console.log(`${this.formatPrefix()} ${chalk__default.default.gray(message)}`, ...args);
}
}
time(label) {
if (this.shouldLog("verbose")) {
this.timers.set(label, performance.now());
}
}
timeEnd(label) {
if (this.shouldLog("verbose")) {
const start = this.timers.get(label);
if (start) {
const duration = Math.round(performance.now() - start);
console.log(`${formatPrefix()} ${label} ${formatTime(duration)}`);
timers.delete(label);
console.log(`${this.formatPrefix()} ${label} ${this.formatTime(duration)}`);
this.timers.delete(label);
}
},
verbose: (message, ...args) => {
if (level === "verbose") {
console.log(`${formatPrefix()} ${chalk__default.default.gray(message)}`, ...args);
}
}
};
}
report(stats) {
if (this.level === "silent") return;
const { total, success, skipped, failed } = stats;
const summary = [
`Total: ${total}`,
`\u2713 Success: ${chalk__default.default.green(success)}`,
skipped > 0 ? `\u26A0\uFE0F Skipped: ${chalk__default.default.yellow(skipped)}` : null,
failed > 0 ? `\u274C Failed: ${chalk__default.default.red(failed)}` : null
].filter(Boolean).join(" ");
console.log(`${this.formatPrefix()} Build Result: ${summary}`);
}
}
function createLogger(prefix, options = {}) {
return new ConsoleLogger(prefix, options.level ?? "info");
}
exports.convertEsbuildLogLevel = convertEsbuildLogLevel;
exports.createLogger = createLogger;
//# sourceMappingURL=logger.js.map
//# sourceMappingURL=logger.js.map

@@ -15,2 +15,3 @@ 'use strict';

const {
logLevel = "auto",
manifestExtensions = DEFAULT_MANIFEST_EXTENSIONS,

@@ -24,6 +25,7 @@ warnOnMissingManifest = true,

} = options;
const logger$1 = logger.createLogger("scriptable-banner");
return {
name: "scriptable-banner",
setup(build) {
const level = logLevel === "auto" ? logger.convertEsbuildLogLevel(build.initialOptions.logLevel || "info") : logLevel;
const logger$1 = logger.createLogger("scriptable-banner", { level });
let manifestCache = /* @__PURE__ */ new Map();

@@ -84,6 +86,9 @@ if (!build.initialOptions.metafile) {

};
const outputs = result.metafile.outputs;
for (const outputPath of Object.keys(outputs)) {
stats.total++;
const outputMeta = outputs[outputPath];
const outputs = Object.keys(result.metafile.outputs);
stats.total = outputs.length;
logger$1.info(`Processing ${stats.total} files...`);
for (const [index, outputPath] of outputs.entries()) {
const progress = Math.round((index + 1) / stats.total * 100);
logger$1.verbose(`[${progress}%] Processing: ${outputPath}`);
const outputMeta = result.metafile.outputs[outputPath];
const entryPoint = outputMeta?.entryPoint ?? "";

@@ -108,3 +113,3 @@ const normalizedEntryPath = entryPoint ? utils.normalizeAndResolvePath(entryPoint) : "";

stats.success++;
logger$1.success(`Added banner to ${outputPath}`);
logger$1.success(`[${progress}%] Added banner to ${outputPath}`);
}

@@ -118,8 +123,6 @@ } else {

stats.success++;
logger$1.success(`Added banner to ${outputPath}`);
logger$1.success(`[${progress}%] Added banner to ${outputPath}`);
} catch (error) {
stats.failed++;
logger$1.error(
`Failed to process ${outputPath}: ${error instanceof Error ? error.message : String(error)}`
);
logger$1.error(`[${progress}%] Failed to process ${outputPath}`);
}

@@ -129,3 +132,3 @@ }

stats.failed++;
logger$1.error(`Failed to process ${outputPath}`, error);
logger$1.error(`[${progress}%] Failed to process ${outputPath}`);
}

@@ -132,0 +135,0 @@ }

'use strict';
var generateScriptableBanner = require('@scriptables/manifest/generateScriptableBanner');
var mergeScriptableBanner = require('@scriptables/manifest/mergeScriptableBanner');
var findicloud = require('findicloud');

@@ -26,3 +26,3 @@ var promises = require('fs/promises');

const {
verbose = false,
logLevel = "auto",
continueOnError = true,

@@ -35,5 +35,2 @@ scriptableDir,

const filterFile = utils.createPatternMatcher(patterns);
const logger$1 = logger.createLogger("scriptable-deploy", {
level: verbose ? "verbose" : "info"
});
let targetDir;

@@ -43,2 +40,4 @@ return {

setup(build) {
const level = logLevel === "auto" ? logger.convertEsbuildLogLevel(build.initialOptions.logLevel || "info") : logLevel;
const logger$1 = logger.createLogger("scriptable-deploy", { level });
if (!build.initialOptions.metafile) {

@@ -92,3 +91,6 @@ logger$1.verbose("Automatically enabling metafile option");

stats.total = outputs.length;
for (const file of outputs) {
logger$1.info(`Deploying ${stats.total} files to ${targetDir}...`);
for (const [index, file] of outputs.entries()) {
const progress = Math.round((index + 1) / stats.total * 100);
logger$1.verbose(`[${progress}%] Processing: ${file}`);
try {

@@ -126,7 +128,6 @@ const output = result.metafile.outputs[file];

try {
const banner = generateScriptableBanner.generateScriptableBanner(manifest);
const newContent = banner + "\n" + content;
await promises.writeFile(targetPath, newContent);
const { banner: newBanner, content: scriptContent } = mergeScriptableBanner.mergeScriptableBanner(content, manifest);
await promises.writeFile(targetPath, newBanner + scriptContent);
stats.success++;
logger$1.success(`Deployed with banner: ${file} -> ${targetPath}`);
logger$1.success(`Deployed with updated banner: ${file} -> ${targetPath}`);
continue;

@@ -133,0 +134,0 @@ } catch (error) {

@@ -1,4 +0,5 @@

export { ScriptablePluginOptions, scriptableBanner } from './scriptable-banner.js';
export { ScriptableBannerOptions, scriptableBanner } from './scriptable-banner.js';
export { ScriptableDeployOptions, scriptableDeploy } from './scriptable-deploy.js';
export { DeployTarget, FileFilterOptions, FilterPattern } from './types.js';
import 'esbuild';
import './logger.js';

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

import { LogLevel as LogLevel$1 } from 'esbuild';
type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'verbose';
declare function convertEsbuildLogLevel(level: LogLevel$1): LogLevel;
interface Logger {

@@ -17,6 +21,6 @@ info: (message: string, ...args: any[]) => void;

interface LoggerOptions {
level?: 'verbose' | 'info';
level?: LogLevel;
}
declare function createLogger(prefix: string, options?: LoggerOptions): Logger;
export { type Logger, type LoggerOptions, createLogger };
export { type LogLevel, type Logger, type LoggerOptions, convertEsbuildLogLevel, createLogger };
import chalk from 'chalk';
const timers = /* @__PURE__ */ new Map();
function createLogger(prefix, options = {}) {
const { level = "info" } = options;
const formatPrefix = () => chalk.cyan(`[${prefix}]`);
const formatTime = (ms) => chalk.gray(`(${ms}ms)`);
return {
info: (message, ...args) => {
console.log(`${formatPrefix()} ${message}`, ...args);
},
warn: (message, ...args) => {
console.warn(`${formatPrefix()} ${chalk.yellow("warning")} ${message}`, ...args);
},
error: (message, ...args) => {
console.error(`${formatPrefix()} ${chalk.red("error")} ${message}`, ...args);
},
success: (message, ...args) => {
console.log(`${formatPrefix()} ${chalk.green("\u2713")} ${message}`, ...args);
},
report: (stats) => {
const { total, success, skipped, failed } = stats;
const hasIssues = failed > 0 || skipped > 0;
console.log("\n" + formatPrefix() + " " + chalk.bold("Build Summary:"));
const rows = [
[` ${chalk.bold("Total files:")}`, `${total}`],
[` ${chalk.bold("Processed:")}`, `${chalk.green(success)}`],
[` ${chalk.bold("Skipped:")}`, `${skipped > 0 ? chalk.yellow(skipped) : skipped}`],
[` ${chalk.bold("Failed:")}`, `${failed > 0 ? chalk.red(failed) : failed}`]
];
rows.forEach(([label, value]) => {
console.log(`${label.padEnd(20)}${value}`);
});
if (hasIssues) {
console.log("\n" + formatPrefix() + " " + chalk.yellow("\u26A0\uFE0F Some files were not processed successfully"));
} else {
console.log("\n" + formatPrefix() + " " + chalk.green("\u2728 All files processed successfully"));
}
console.log();
},
time: (label) => {
timers.set(label, performance.now());
},
timeEnd: (label) => {
const start = timers.get(label);
function convertEsbuildLogLevel(level) {
switch (level) {
case "debug":
return "verbose";
case "info":
return "info";
case "warning":
return "warn";
case "error":
return "error";
case "silent":
return "silent";
default:
return "info";
}
}
class ConsoleLogger {
constructor(prefix, level = "info") {
this.prefix = prefix;
this.level = level;
}
timers = /* @__PURE__ */ new Map();
LOG_LEVELS = {
silent: 0,
error: 1,
warn: 2,
info: 3,
verbose: 4
};
formatPrefix() {
return chalk.cyan(`[${this.prefix}]`);
}
formatTime(ms) {
return chalk.gray(`(${ms}ms)`);
}
shouldLog(messageLevel) {
if (this.level === "silent") {
return false;
}
return this.LOG_LEVELS[messageLevel] <= this.LOG_LEVELS[this.level];
}
info(message, ...args) {
if (this.shouldLog("info")) {
console.log(`${this.formatPrefix()} ${message}`, ...args);
}
}
warn(message, ...args) {
if (this.shouldLog("warn")) {
console.warn(`${this.formatPrefix()} ${chalk.yellow("warning")} ${message}`, ...args);
}
}
error(message, ...args) {
if (this.shouldLog("error")) {
console.error(`${this.formatPrefix()} ${chalk.red("error")} ${message}`, ...args);
}
}
success(message, ...args) {
if (this.shouldLog("info")) {
console.log(`${this.formatPrefix()} ${chalk.green("\u2713")} ${message}`, ...args);
}
}
verbose(message, ...args) {
if (this.shouldLog("verbose")) {
console.log(`${this.formatPrefix()} ${chalk.gray(message)}`, ...args);
}
}
time(label) {
if (this.shouldLog("verbose")) {
this.timers.set(label, performance.now());
}
}
timeEnd(label) {
if (this.shouldLog("verbose")) {
const start = this.timers.get(label);
if (start) {
const duration = Math.round(performance.now() - start);
console.log(`${formatPrefix()} ${label} ${formatTime(duration)}`);
timers.delete(label);
console.log(`${this.formatPrefix()} ${label} ${this.formatTime(duration)}`);
this.timers.delete(label);
}
},
verbose: (message, ...args) => {
if (level === "verbose") {
console.log(`${formatPrefix()} ${chalk.gray(message)}`, ...args);
}
}
};
}
report(stats) {
if (this.level === "silent") return;
const { total, success, skipped, failed } = stats;
const summary = [
`Total: ${total}`,
`\u2713 Success: ${chalk.green(success)}`,
skipped > 0 ? `\u26A0\uFE0F Skipped: ${chalk.yellow(skipped)}` : null,
failed > 0 ? `\u274C Failed: ${chalk.red(failed)}` : null
].filter(Boolean).join(" ");
console.log(`${this.formatPrefix()} Build Result: ${summary}`);
}
}
function createLogger(prefix, options = {}) {
return new ConsoleLogger(prefix, options.level ?? "info");
}
export { createLogger };
export { convertEsbuildLogLevel, createLogger };
//# sourceMappingURL=logger.js.map
//# sourceMappingURL=logger.js.map
import { Plugin } from 'esbuild';
import { LogLevel } from './logger.js';
interface ScriptablePluginOptions {
interface ScriptableBannerOptions {
/**
* Log level for the plugin
* 'auto' will use esbuild's logLevel
* Default: 'auto'
*/
logLevel?: LogLevel | 'auto';
/** Custom manifest file extensions */

@@ -19,4 +26,4 @@ manifestExtensions?: string[];

}
declare function scriptableBanner(options?: ScriptablePluginOptions): Plugin;
declare function scriptableBanner(options?: ScriptableBannerOptions): Plugin;
export { type ScriptablePluginOptions, scriptableBanner };
export { type ScriptableBannerOptions, scriptableBanner };

@@ -7,11 +7,12 @@ import { generateScriptableBanner } from "@scriptables/manifest/generateScriptableBanner";

import { dirname, basename, resolve } from "path";
import { createLogger } from "./logger.js";
import { convertEsbuildLogLevel, createLogger } from "./logger.js";
import { isMatch, normalizeAndResolvePath, isSamePath, waitForFile } from "./utils.js";
const DEFAULT_MANIFEST_EXTENSIONS = [".manifest.json", ".manifest", ".json"];
function scriptableBanner(options = {}) {
const { manifestExtensions = DEFAULT_MANIFEST_EXTENSIONS, warnOnMissingManifest = true, warnOnInvalidManifest = true, warnOnMissingEntry = true, allowExtraManifestKeys = true, getManifestPath, patterns = ["*.{ts,tsx,js,jsx}"] } = options;
const logger = createLogger("scriptable-banner");
const { logLevel = "auto", manifestExtensions = DEFAULT_MANIFEST_EXTENSIONS, warnOnMissingManifest = true, warnOnInvalidManifest = true, warnOnMissingEntry = true, allowExtraManifestKeys = true, getManifestPath, patterns = ["*.{ts,tsx,js,jsx}"] } = options;
return {
name: "scriptable-banner",
setup(build) {
const level = logLevel === "auto" ? convertEsbuildLogLevel(build.initialOptions.logLevel || "info") : logLevel;
const logger = createLogger("scriptable-banner", { level });
let manifestCache = /* @__PURE__ */ new Map();

@@ -71,6 +72,9 @@ if (!build.initialOptions.metafile) {

};
const outputs = result.metafile.outputs;
for (const outputPath of Object.keys(outputs)) {
stats.total++;
const outputMeta = outputs[outputPath];
const outputs = Object.keys(result.metafile.outputs);
stats.total = outputs.length;
logger.info(`Processing ${stats.total} files...`);
for (const [index, outputPath] of outputs.entries()) {
const progress = Math.round((index + 1) / stats.total * 100);
logger.verbose(`[${progress}%] Processing: ${outputPath}`);
const outputMeta = result.metafile.outputs[outputPath];
const entryPoint = outputMeta?.entryPoint ?? "";

@@ -93,3 +97,3 @@ const normalizedEntryPath = entryPoint ? normalizeAndResolvePath(entryPoint) : "";

stats.success++;
logger.success(`Added banner to ${outputPath}`);
logger.success(`[${progress}%] Added banner to ${outputPath}`);
}

@@ -104,7 +108,7 @@ }

stats.success++;
logger.success(`Added banner to ${outputPath}`);
logger.success(`[${progress}%] Added banner to ${outputPath}`);
}
catch (error) {
stats.failed++;
logger.error(`Failed to process ${outputPath}: ${error instanceof Error ? error.message : String(error)}`);
logger.error(`[${progress}%] Failed to process ${outputPath}`);
}

@@ -115,3 +119,3 @@ }

stats.failed++;
logger.error(`Failed to process ${outputPath}`, error);
logger.error(`[${progress}%] Failed to process ${outputPath}`);
}

@@ -118,0 +122,0 @@ }

import { Plugin } from 'esbuild';
import { LogLevel } from './logger.js';
interface ScriptableDeployOptions {
/**
* Whether to show detailed logs during deployment
* Default: false
* Log level for the plugin
* 'auto' will use esbuild's logLevel
* Default: 'auto'
*/
verbose?: boolean;
logLevel?: LogLevel | 'auto';
/**

@@ -10,0 +12,0 @@ * Whether to continue building when deployment fails

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

import { generateScriptableBanner } from "@scriptables/manifest/generateScriptableBanner";
import { mergeScriptableBanner } from "@scriptables/manifest/mergeScriptableBanner";
import { findICloudDrivePaths, PathType } from "findicloud";

@@ -6,3 +6,3 @@ import { readFile, writeFile } from "fs/promises";

import { join, basename } from "path";
import { createLogger } from "./logger.js";
import { convertEsbuildLogLevel, createLogger } from "./logger.js";
import { createPatternMatcher, normalizeAndResolvePath, isSamePath, waitForFile } from "./utils.js";

@@ -24,7 +24,4 @@ const findICloudBugsUrl = "https://github.com/hexxspark/findicloud/issues";

function scriptableDeploy(options = {}) {
const { verbose = false, continueOnError = true, scriptableDir, patterns = ["*.{ts,tsx,js,jsx}"], addBanner = true, manifestExtensions = [".manifest.json", ".manifest", ".json"] } = options;
const { logLevel = "auto", continueOnError = true, scriptableDir, patterns = ["*.{ts,tsx,js,jsx}"], addBanner = true, manifestExtensions = [".manifest.json", ".manifest", ".json"] } = options;
const filterFile = createPatternMatcher(patterns);
const logger = createLogger("scriptable-deploy", {
level: verbose ? "verbose" : "info"
});
let targetDir;

@@ -34,2 +31,4 @@ return {

setup(build) {
const level = logLevel === "auto" ? convertEsbuildLogLevel(build.initialOptions.logLevel || "info") : logLevel;
const logger = createLogger("scriptable-deploy", { level });
if (!build.initialOptions.metafile) {

@@ -84,3 +83,6 @@ logger.verbose("Automatically enabling metafile option");

stats.total = outputs.length;
for (const file of outputs) {
logger.info(`Deploying ${stats.total} files to ${targetDir}...`);
for (const [index, file] of outputs.entries()) {
const progress = Math.round((index + 1) / stats.total * 100);
logger.verbose(`[${progress}%] Processing: ${file}`);
try {

@@ -120,7 +122,6 @@ const output = result.metafile.outputs[file];

try {
const banner = generateScriptableBanner(manifest);
const newContent = banner + "\n" + content;
await writeFile(targetPath, newContent);
const { banner: newBanner, content: scriptContent } = mergeScriptableBanner(content, manifest);
await writeFile(targetPath, newBanner + scriptContent);
stats.success++;
logger.success(`Deployed with banner: ${file} -> ${targetPath}`);
logger.success(`Deployed with updated banner: ${file} -> ${targetPath}`);
continue;

@@ -127,0 +128,0 @@ }

{
"name": "esbuild-plugin-scriptable",
"description": "An ESBuild plugin for developing Scriptable iOS app scripts with TypeScript, manifest support and auto-deployment",
"version": "0.7.1",
"version": "0.7.2",
"keywords": [

@@ -6,0 +6,0 @@ "scriptable",

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

- Continues building even when deployment fails (configurable)
- Detailed deployment logging options

@@ -54,3 +55,3 @@ ## Installation

scriptableDeploy({
verbose: true,
logLevel: 'verbose',
patterns: '**/*.widget.ts',

@@ -77,6 +78,17 @@ }),

- `['**/*.ts', '!**/*.test.ts']` - exclude test files
- `logLevel`: Control logging detail (default: 'auto')
- 'auto': Use esbuild's logLevel
- 'silent': No logs
- 'error': Only errors
- 'warn': Errors and warnings
- 'info': Normal output
- 'verbose': Detailed output
### Deploy Plugin Options
- `verbose`: Show detailed logs during deployment (default: false)
- `logLevel`: Control deployment logging detail (default: 'normal')
- 'silent': No logs
- 'normal': Basic deployment info
- 'verbose': Detailed deployment info
- 'debug': All deployment details
- `continueOnError`: Continue building when deployment fails (default: true)

@@ -103,3 +115,5 @@ - `scriptableDir`: Custom Scriptable directory path (default: auto-detect in iCloud Drive)

"iconColor": "blue",
"iconGlyph": "cloud"
"iconGlyph": "cloud",
"version": "1.0.0",
"description": "A weather widget for Scriptable"
}

@@ -133,3 +147,3 @@ ```

scriptableDeploy({
verbose: true,
logLevel: 'verbose',
addBanner: true,

@@ -143,4 +157,56 @@ patterns: ['**/*.ts', '!**/*.test.ts'],

## Banner Behavior
The banner can be added in two stages:
1. Build stage (via `scriptableBanner` plugin):
- Adds banner to the build output files
- Banner is part of the compiled result
2. Deploy stage (via `scriptableDeploy` plugin's `addBanner` option):
- Does not affect build output files
- Only adds banner to the deployed files in Scriptable directory
- Will update/override any existing banner in the target file
When using both plugins:
- `scriptableBanner` will add banner to build output
- `scriptableDeploy` with `addBanner: true` will update/override the banner in deployed files
- `scriptableDeploy` with `addBanner: false` will keep the existing banner (if any)
```typescript
// Example 1: Banner in both build output and deployed files
plugins: [
scriptableBanner({...}), // Adds banner to build output
scriptableDeploy({
addBanner: true // Updates banner in deployed files
})
]
// Example 2: Banner only in deployed files
plugins: [
scriptableDeploy({
addBanner: true // Adds banner only to deployed files
})
]
// Example 3: Banner only in build output
plugins: [
scriptableBanner({...}), // Adds banner to build output
scriptableDeploy({
addBanner: false // Keeps existing banner in deployed files
})
]
```
The `addBanner` option in `scriptableDeploy`:
- Only affects the files in Scriptable directory
- Does not modify the build output files
- When `true`, will ensure the latest banner from manifest is used in deployed files
- When `false`, will preserve any existing banner in the files
## License
Apache-2.0
import chalk from 'chalk';
import type {LogLevel as EsbuildLogLevel} from 'esbuild';
export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'verbose';
export function convertEsbuildLogLevel(level: EsbuildLogLevel): LogLevel {
switch (level) {
case 'debug':
return 'verbose';
case 'info':
return 'info';
case 'warning':
return 'warn';
case 'error':
return 'error';
case 'silent':
return 'silent';
default:
return 'info';
}
}
export interface Logger {

@@ -14,69 +34,104 @@ info: (message: string, ...args: any[]) => void;

const timers = new Map<string, number>();
export interface LoggerOptions {
level?: 'verbose' | 'info';
level?: LogLevel;
}
export function createLogger(prefix: string, options: LoggerOptions = {}): Logger {
const {level = 'info'} = options;
const formatPrefix = () => chalk.cyan(`[${prefix}]`);
const formatTime = (ms: number) => chalk.gray(`(${ms}ms)`);
class ConsoleLogger implements Logger {
private readonly timers = new Map<string, number>();
private readonly LOG_LEVELS: Record<LogLevel, number> = {
silent: 0,
error: 1,
warn: 2,
info: 3,
verbose: 4,
};
return {
info: (message: string, ...args) => {
console.log(`${formatPrefix()} ${message}`, ...args);
},
warn: (message: string, ...args) => {
console.warn(`${formatPrefix()} ${chalk.yellow('warning')} ${message}`, ...args);
},
error: (message: string, ...args) => {
console.error(`${formatPrefix()} ${chalk.red('error')} ${message}`, ...args);
},
success: (message: string, ...args) => {
console.log(`${formatPrefix()} ${chalk.green('✓')} ${message}`, ...args);
},
report: stats => {
const {total, success, skipped, failed} = stats;
const hasIssues = failed > 0 || skipped > 0;
constructor(
private readonly prefix: string,
private readonly level: LogLevel = 'info',
) {}
console.log('\n' + formatPrefix() + ' ' + chalk.bold('Build Summary:'));
private formatPrefix() {
return chalk.cyan(`[${this.prefix}]`);
}
// 使用表格样式展示统计
const rows = [
[` ${chalk.bold('Total files:')}`, `${total}`],
[` ${chalk.bold('Processed:')}`, `${chalk.green(success)}`],
[` ${chalk.bold('Skipped:')}`, `${skipped > 0 ? chalk.yellow(skipped) : skipped}`],
[` ${chalk.bold('Failed:')}`, `${failed > 0 ? chalk.red(failed) : failed}`],
];
private formatTime(ms: number) {
return chalk.gray(`(${ms}ms)`);
}
rows.forEach(([label, value]) => {
console.log(`${label.padEnd(20)}${value}`);
});
private shouldLog(messageLevel: LogLevel): boolean {
if (this.level === 'silent') {
return false;
}
// 如果有问题,添加提示
if (hasIssues) {
console.log('\n' + formatPrefix() + ' ' + chalk.yellow('⚠️ Some files were not processed successfully'));
} else {
console.log('\n' + formatPrefix() + ' ' + chalk.green('✨ All files processed successfully'));
}
console.log(); // 空行
},
time: (label: string) => {
timers.set(label, performance.now());
},
timeEnd: (label: string) => {
const start = timers.get(label);
return this.LOG_LEVELS[messageLevel] <= this.LOG_LEVELS[this.level];
}
info(message: string, ...args: any[]) {
if (this.shouldLog('info')) {
console.log(`${this.formatPrefix()} ${message}`, ...args);
}
}
warn(message: string, ...args: any[]) {
if (this.shouldLog('warn')) {
console.warn(`${this.formatPrefix()} ${chalk.yellow('warning')} ${message}`, ...args);
}
}
error(message: string, ...args: any[]) {
if (this.shouldLog('error')) {
console.error(`${this.formatPrefix()} ${chalk.red('error')} ${message}`, ...args);
}
}
success(message: string, ...args: any[]) {
if (this.shouldLog('info')) {
console.log(`${this.formatPrefix()} ${chalk.green('✓')} ${message}`, ...args);
}
}
verbose(message: string, ...args: any[]) {
if (this.shouldLog('verbose')) {
console.log(`${this.formatPrefix()} ${chalk.gray(message)}`, ...args);
}
}
time(label: string) {
if (this.shouldLog('verbose')) {
this.timers.set(label, performance.now());
}
}
timeEnd(label: string) {
if (this.shouldLog('verbose')) {
const start = this.timers.get(label);
if (start) {
const duration = Math.round(performance.now() - start);
console.log(`${formatPrefix()} ${label} ${formatTime(duration)}`);
timers.delete(label);
console.log(`${this.formatPrefix()} ${label} ${this.formatTime(duration)}`);
this.timers.delete(label);
}
},
verbose: (message: string, ...args) => {
if (level === 'verbose') {
console.log(`${formatPrefix()} ${chalk.gray(message)}`, ...args);
}
},
};
}
}
report(stats: {total: number; success: number; skipped: number; failed: number}) {
if (this.level === 'silent') return;
const {total, success, skipped, failed} = stats;
const summary = [
`Total: ${total}`,
`✓ Success: ${chalk.green(success)}`,
skipped > 0 ? `⚠️ Skipped: ${chalk.yellow(skipped)}` : null,
failed > 0 ? `❌ Failed: ${chalk.red(failed)}` : null,
]
.filter(Boolean)
.join(' ');
console.log(`${this.formatPrefix()} Build Result: ${summary}`);
}
}
export function createLogger(prefix: string, options: LoggerOptions = {}): Logger {
return new ConsoleLogger(prefix, options.level ?? 'info');
}

@@ -10,3 +10,3 @@ import type {ScriptableManifest} from '@scriptables/manifest';

import {createLogger} from './logger';
import {convertEsbuildLogLevel, createLogger, LogLevel} from './logger';
import {isMatch, isSamePath, normalizeAndResolvePath, waitForFile} from './utils';

@@ -16,3 +16,10 @@

export interface ScriptablePluginOptions {
export interface ScriptableBannerOptions {
/**
* Log level for the plugin
* 'auto' will use esbuild's logLevel
* Default: 'auto'
*/
logLevel?: LogLevel | 'auto';
/** Custom manifest file extensions */

@@ -40,4 +47,5 @@ manifestExtensions?: string[];

export function scriptableBanner(options: ScriptablePluginOptions = {}): Plugin {
export function scriptableBanner(options: ScriptableBannerOptions = {}): Plugin {
const {
logLevel = 'auto',
manifestExtensions = DEFAULT_MANIFEST_EXTENSIONS,

@@ -52,7 +60,9 @@ warnOnMissingManifest = true,

const logger = createLogger('scriptable-banner');
return {
name: 'scriptable-banner',
setup(build) {
const level = logLevel === 'auto' ? convertEsbuildLogLevel(build.initialOptions.logLevel || 'info') : logLevel;
const logger = createLogger('scriptable-banner', {level});
let manifestCache = new Map<string, ScriptableManifest>();

@@ -133,7 +143,12 @@

const outputs = result.metafile.outputs;
for (const outputPath of Object.keys(outputs)) {
stats.total++;
const outputs = Object.keys(result.metafile.outputs);
stats.total = outputs.length;
const outputMeta = outputs[outputPath];
logger.info(`Processing ${stats.total} files...`);
for (const [index, outputPath] of outputs.entries()) {
const progress = Math.round(((index + 1) / stats.total) * 100);
logger.verbose(`[${progress}%] Processing: ${outputPath}`);
const outputMeta = result.metafile.outputs[outputPath];
const entryPoint = outputMeta?.entryPoint ?? '';

@@ -161,3 +176,3 @@ const normalizedEntryPath = entryPoint ? normalizeAndResolvePath(entryPoint) : '';

stats.success++;
logger.success(`Added banner to ${outputPath}`);
logger.success(`[${progress}%] Added banner to ${outputPath}`);
}

@@ -171,13 +186,13 @@ } else {

stats.success++;
logger.success(`Added banner to ${outputPath}`);
logger.success(`[${progress}%] Added banner to ${outputPath}`);
// eslint-disable-next-line unused-imports/no-unused-vars
} catch (error) {
stats.failed++;
logger.error(
`Failed to process ${outputPath}: ${error instanceof Error ? error.message : String(error)}`,
);
logger.error(`[${progress}%] Failed to process ${outputPath}`);
}
}
// eslint-disable-next-line unused-imports/no-unused-vars
} catch (error) {
stats.failed++;
logger.error(`Failed to process ${outputPath}`, error);
logger.error(`[${progress}%] Failed to process ${outputPath}`);
}

@@ -184,0 +199,0 @@ }

import type {ScriptableManifest} from '@scriptables/manifest';
import {generateScriptableBanner} from '@scriptables/manifest/generateScriptableBanner';
import {mergeScriptableBanner} from '@scriptables/manifest/mergeScriptableBanner';
import type {Plugin} from 'esbuild';

@@ -9,3 +9,3 @@ import {findICloudDrivePaths, PathType} from 'findicloud';

import {createLogger} from './logger';
import {convertEsbuildLogLevel, createLogger, LogLevel} from './logger';
import {createPatternMatcher, isSamePath, normalizeAndResolvePath, waitForFile} from './utils';

@@ -15,6 +15,7 @@

/**
* Whether to show detailed logs during deployment
* Default: false
* Log level for the plugin
* 'auto' will use esbuild's logLevel
* Default: 'auto'
*/
verbose?: boolean;
logLevel?: LogLevel | 'auto';

@@ -74,3 +75,3 @@ /**

const {
verbose = false,
logLevel = 'auto',
continueOnError = true,

@@ -84,7 +85,2 @@ scriptableDir,

const filterFile = createPatternMatcher(patterns);
const logger = createLogger('scriptable-deploy', {
level: verbose ? 'verbose' : 'info',
});
let targetDir: string;

@@ -95,2 +91,6 @@

setup(build) {
const level = logLevel === 'auto' ? convertEsbuildLogLevel(build.initialOptions.logLevel || 'info') : logLevel;
const logger = createLogger('scriptable-deploy', {level});
// Ensure metafile is enabled

@@ -159,4 +159,8 @@ if (!build.initialOptions.metafile) {

stats.total = outputs.length;
logger.info(`Deploying ${stats.total} files to ${targetDir}...`);
for (const file of outputs) {
for (const [index, file] of outputs.entries()) {
const progress = Math.round(((index + 1) / stats.total) * 100);
logger.verbose(`[${progress}%] Processing: ${file}`);
try {

@@ -202,7 +206,6 @@ const output = result.metafile.outputs[file];

try {
const banner = generateScriptableBanner(manifest);
const newContent = banner + '\n' + content;
await writeFile(targetPath, newContent);
const {banner: newBanner, content: scriptContent} = mergeScriptableBanner(content, manifest);
await writeFile(targetPath, newBanner + scriptContent);
stats.success++;
logger.success(`Deployed with banner: ${file} -> ${targetPath}`);
logger.success(`Deployed with updated banner: ${file} -> ${targetPath}`);
continue;

@@ -209,0 +212,0 @@ } catch (error) {

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