esbuild-plugin-scriptable
Advanced tools
Comparing version 0.5.4 to 0.5.5
@@ -1,5 +0,5 @@ | ||
export * from './scriptable-banner'; | ||
export * from './scriptable-deploy'; | ||
export * from './types'; | ||
export * from "./scriptable-banner.js"; | ||
export * from "./scriptable-deploy.js"; | ||
export * from "./types.js"; | ||
//# sourceMappingURL=index.js.map | ||
//# sourceMappingURL=index.js.map | ||
//# sourceMappingURL=index.js.map |
@@ -1,113 +0,104 @@ | ||
import { generateScriptableBanner } from '@scriptables/manifest/generateScriptableBanner'; | ||
import { validateManifest } from '@scriptables/manifest/validateManifest'; | ||
import { existsSync } from 'fs'; | ||
import { readFile, writeFile } from 'fs/promises'; | ||
import { dirname, basename, resolve, posix, sep } from 'path'; | ||
import { createLogger } from './logger'; | ||
import { isMatch, normalizePath, waitForFile } from './utils'; | ||
import { generateScriptableBanner } from "@scriptables/manifest/generateScriptableBanner"; | ||
import { validateManifest } from "@scriptables/manifest/validateManifest"; | ||
import { existsSync } from "fs"; | ||
import { readFile, writeFile } from "fs/promises"; | ||
import { dirname, basename, resolve, posix, sep } from "path"; | ||
import { createLogger } from "./logger.js"; | ||
import { isMatch, normalizePath, 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, | ||
getManifestPath, | ||
patterns = ["**/*.{ts,tsx,js,jsx}"] | ||
} = options; | ||
const logger = createLogger("scriptable-banner"); | ||
return { | ||
name: "scriptable", | ||
setup(build) { | ||
let manifestCache = /* @__PURE__ */ new Map(); | ||
if (!build.initialOptions.metafile) { | ||
logger.warn( | ||
'Warning: "metafile" option was not enabled. Automatically setting "metafile: true" to allow scriptable plugin to modify output files.' | ||
); | ||
build.initialOptions.metafile = true; | ||
} | ||
build.onStart(() => { | ||
manifestCache.clear(); | ||
}); | ||
build.onResolve({ filter: /.*/ }, async (args) => { | ||
if (!isMatch(args.path, patterns)) { | ||
return null; | ||
} | ||
if (Array.isArray(build.initialOptions.entryPoints) && build.initialOptions.entryPoints.map(String).includes(args.path)) { | ||
const dir = dirname(args.path); | ||
const baseFileName = basename(args.path).replace(/\.[tj]sx?$/, ""); | ||
const manifestPath = getManifestPath ? getManifestPath(args.path) : manifestExtensions.map((ext) => resolve(dir, `${baseFileName}${ext}`)).find(existsSync); | ||
if (manifestPath) { | ||
try { | ||
const manifestContent = await readFile(manifestPath, "utf8"); | ||
const manifest = JSON.parse(manifestContent); | ||
const normalizedPath = normalizePath(args.path); | ||
try { | ||
validateManifest(manifest); | ||
} catch (error) { | ||
if (warnOnInvalidManifest && error instanceof Error) { | ||
logger.warn(`Invalid manifest file: ${manifestPath} - ${error.message}`, error); | ||
const { manifestExtensions = DEFAULT_MANIFEST_EXTENSIONS, warnOnMissingManifest = true, warnOnInvalidManifest = true, warnOnMissingEntry = true, getManifestPath, patterns = ["**/*.{ts,tsx,js,jsx}"] } = options; | ||
const logger = createLogger("scriptable-banner"); | ||
return { | ||
name: "scriptable", | ||
setup(build) { | ||
let manifestCache = /* @__PURE__ */ new Map(); | ||
if (!build.initialOptions.metafile) { | ||
logger.warn("Warning: \"metafile\" option was not enabled. Automatically setting \"metafile: true\" to allow scriptable plugin to modify output files."); | ||
build.initialOptions.metafile = true; | ||
} | ||
build.onStart(() => { | ||
manifestCache.clear(); | ||
}); | ||
build.onResolve({ filter: /.*/ }, async (args) => { | ||
if (!isMatch(args.path, patterns)) { | ||
return null; | ||
} | ||
if (Array.isArray(build.initialOptions.entryPoints) && build.initialOptions.entryPoints.map(String).includes(args.path)) { | ||
const dir = dirname(args.path); | ||
const baseFileName = basename(args.path).replace(/\.[tj]sx?$/, ""); | ||
const manifestPath = getManifestPath ? getManifestPath(args.path) : manifestExtensions.map((ext) => resolve(dir, `${baseFileName}${ext}`)).find(existsSync); | ||
if (manifestPath) { | ||
try { | ||
const manifestContent = await readFile(manifestPath, "utf8"); | ||
const manifest = JSON.parse(manifestContent); | ||
const normalizedPath = normalizePath(args.path); | ||
try { | ||
validateManifest(manifest); | ||
} | ||
catch (error) { | ||
if (warnOnInvalidManifest && error instanceof Error) { | ||
logger.warn(`Invalid manifest file: ${manifestPath} - ${error.message}`, error); | ||
} | ||
return null; | ||
} | ||
manifestCache.set(normalizedPath, manifest); | ||
} | ||
catch (error) { | ||
logger.warn(`Failed to read manifest file: ${manifestPath}`, error); | ||
} | ||
} | ||
else if (warnOnMissingManifest) { | ||
logger.warn(`No manifest file found for entry point: ${args.path}`); | ||
} | ||
} | ||
return null; | ||
} | ||
manifestCache.set(normalizedPath, manifest); | ||
} catch (error) { | ||
logger.warn(`Failed to read manifest file: ${manifestPath}`, error); | ||
} | ||
} else if (warnOnMissingManifest) { | ||
logger.warn(`No manifest file found for entry point: ${args.path}`); | ||
} | ||
}); | ||
build.onEnd(async (result) => { | ||
if (!result.metafile || Object.keys(result.metafile.outputs).length === 0) { | ||
logger.warn("Skipping banner addition because \"metafile\" is not available or contains no outputs. Ensure \"metafile: true\" is set in esbuild options if write is enabled."); | ||
return; | ||
} | ||
const outputs = result.metafile.outputs; | ||
for (const outputPath of Object.keys(outputs)) { | ||
const outputMeta = outputs[outputPath]; | ||
const entryPoint = outputMeta?.entryPoint ?? ""; | ||
const normalizedEntryPath = entryPoint ? posix.normalize(entryPoint.split(sep).join("/")) : ""; | ||
if (!normalizedEntryPath || !manifestCache.has(normalizedEntryPath)) { | ||
if (warnOnMissingEntry) { | ||
logger.warn(`No matching entry point found for output file: ${outputPath}`); | ||
} | ||
continue; | ||
} | ||
try { | ||
const manifest = manifestCache.get(normalizedEntryPath) || {}; | ||
const banner = generateScriptableBanner(manifest); | ||
if (result.outputFiles) { | ||
const outputFile = result.outputFiles.find((file) => file.path === outputPath || file.path === "<stdout>"); | ||
if (outputFile) { | ||
outputFile.contents = Buffer.from(banner + outputFile.text.trimEnd()); | ||
} | ||
} | ||
else { | ||
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)}`); | ||
} | ||
} | ||
} | ||
catch (error) { | ||
logger.error(`Failed to process output file: ${outputPath}`, error); | ||
} | ||
} | ||
}); | ||
} | ||
return null; | ||
}); | ||
build.onEnd(async (result) => { | ||
if (!result.metafile || Object.keys(result.metafile.outputs).length === 0) { | ||
logger.warn( | ||
'Skipping banner addition because "metafile" is not available or contains no outputs. Ensure "metafile: true" is set in esbuild options if write is enabled.' | ||
); | ||
return; | ||
} | ||
const outputs = result.metafile.outputs; | ||
for (const outputPath of Object.keys(outputs)) { | ||
const outputMeta = outputs[outputPath]; | ||
const entryPoint = outputMeta?.entryPoint ?? ""; | ||
const normalizedEntryPath = entryPoint ? posix.normalize(entryPoint.split(sep).join("/")) : ""; | ||
if (!normalizedEntryPath || !manifestCache.has(normalizedEntryPath)) { | ||
if (warnOnMissingEntry) { | ||
logger.warn(`No matching entry point found for output file: ${outputPath}`); | ||
} | ||
continue; | ||
} | ||
try { | ||
const manifest = manifestCache.get(normalizedEntryPath) || {}; | ||
const banner = generateScriptableBanner(manifest); | ||
if (result.outputFiles) { | ||
const outputFile = result.outputFiles.find((file) => file.path === outputPath || file.path === "<stdout>"); | ||
if (outputFile) { | ||
outputFile.contents = Buffer.from(banner + outputFile.text.trimEnd()); | ||
} | ||
} else { | ||
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)}` | ||
); | ||
} | ||
} | ||
} catch (error) { | ||
logger.error(`Failed to process output file: ${outputPath}`, error); | ||
} | ||
} | ||
}); | ||
} | ||
}; | ||
}; | ||
} | ||
export { scriptableBanner }; | ||
//# sourceMappingURL=scriptable-banner.js.map | ||
//# sourceMappingURL=scriptable-banner.js.map | ||
//# sourceMappingURL=scriptable-banner.js.map |
@@ -1,134 +0,130 @@ | ||
import { generateScriptableBanner } from '@scriptables/manifest/generateScriptableBanner'; | ||
import { findICloudDrivePaths, PathType } from 'findicloud'; | ||
import { readFile, writeFile } from 'fs/promises'; | ||
import { basename, join } from 'path'; | ||
import { createLogger } from './logger'; | ||
import { createPatternMatcher, isPathEqual, waitForFile } from './utils'; | ||
import { generateScriptableBanner } from "@scriptables/manifest/generateScriptableBanner"; | ||
import { findICloudDrivePaths, PathType } from "findicloud"; | ||
import { readFile, writeFile } from "fs/promises"; | ||
import { basename, join } from "path"; | ||
import { createLogger } from "./logger.js"; | ||
import { createPatternMatcher, isPathEqual, waitForFile } from "./utils.js"; | ||
const findICloudBugsUrl = "https://github.com/hexxspark/findicloud/issues"; | ||
async function tryReadManifest(filePath, extensions) { | ||
for (const ext of extensions) { | ||
try { | ||
const manifestPath = filePath.replace(/\.[^.]+$/, "") + ext; | ||
const content = await readFile(manifestPath, "utf-8"); | ||
return JSON.parse(content); | ||
} catch { | ||
continue; | ||
for (const ext of extensions) { | ||
try { | ||
const manifestPath = filePath.replace(/\.[^.]+$/, "") + ext; | ||
const content = await readFile(manifestPath, "utf-8"); | ||
return JSON.parse(content); | ||
} | ||
catch { | ||
continue; | ||
} | ||
} | ||
} | ||
return null; | ||
return null; | ||
} | ||
function scriptableDeploy(options = {}) { | ||
const { | ||
verbose = false, | ||
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; | ||
return { | ||
name: "scriptable-deploy", | ||
setup(build) { | ||
if (!build.initialOptions.metafile) { | ||
logger.verbose("Automatically enabling metafile option"); | ||
build.initialOptions.metafile = true; | ||
} | ||
build.onStart(async () => { | ||
try { | ||
if (scriptableDir) { | ||
targetDir = scriptableDir; | ||
} else { | ||
const [found] = await findICloudDrivePaths({ appNamePattern: "Scriptable", types: [PathType.APP_STORAGE] }); | ||
targetDir = found?.path; | ||
} | ||
if (!targetDir) { | ||
throw new Error( | ||
`Scriptable directory not found in iCloud Drive. Please ensure iCloud Drive is installed and Scriptable app is synced with iCloud. If iCloud Drive is not installed, you can download it from: https://support.apple.com/en-us/HT204283. If the issue persists, please report it at: ${findICloudBugsUrl}` | ||
); | ||
} | ||
logger.verbose(`Target directory: ${targetDir}`); | ||
} catch (error) { | ||
const message = `Failed to find Scriptable directory: ${error.message}`; | ||
if (!continueOnError) { | ||
throw new Error(message); | ||
} | ||
logger.error(message); | ||
} | ||
}); | ||
build.onEnd(async (result) => { | ||
if (!result.metafile || !targetDir) { | ||
logger.error("Missing required configuration"); | ||
return; | ||
} | ||
try { | ||
const outputs = Object.keys(result.metafile.outputs).filter((file) => { | ||
const { entryPoint } = result.metafile.outputs[file]; | ||
return entryPoint && filterFile(entryPoint); | ||
}); | ||
for (const file of outputs) { | ||
const output = result.metafile.outputs[file]; | ||
let content; | ||
if (result.outputFiles) { | ||
const outputFile = result.outputFiles.find((f) => isPathEqual(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"); | ||
const { verbose = false, 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; | ||
return { | ||
name: "scriptable-deploy", | ||
setup(build) { | ||
if (!build.initialOptions.metafile) { | ||
logger.verbose("Automatically enabling metafile option"); | ||
build.initialOptions.metafile = true; | ||
} | ||
let targetFileName = basename(file); | ||
let manifest = null; | ||
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}`); | ||
} | ||
manifest = await tryReadManifest(sourceFile, manifestExtensions); | ||
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) { | ||
logger.warn(`Failed to add banner to ${file}: ${error}`); | ||
} | ||
} | ||
await writeFile(targetPath, content); | ||
logger.info(`Deployed: ${file} -> ${targetPath}`); | ||
} | ||
logger.info(`Successfully deployed ${outputs.length} files`); | ||
} catch (error) { | ||
logger.error(`Deploy failed: ${error.message}`); | ||
if (!continueOnError) { | ||
throw error; | ||
} | ||
build.onStart(async () => { | ||
try { | ||
if (scriptableDir) { | ||
targetDir = scriptableDir; | ||
} | ||
else { | ||
const [found] = await findICloudDrivePaths({ appNamePattern: "Scriptable", types: [PathType.APP_STORAGE] }); | ||
targetDir = found?.path; | ||
} | ||
if (!targetDir) { | ||
throw new Error(`Scriptable directory not found in iCloud Drive. Please ensure iCloud Drive is installed and Scriptable app is synced with iCloud. If iCloud Drive is not installed, you can download it from: https://support.apple.com/en-us/HT204283. If the issue persists, please report it at: ${findICloudBugsUrl}`); | ||
} | ||
logger.verbose(`Target directory: ${targetDir}`); | ||
} | ||
catch (error) { | ||
const message = `Failed to find Scriptable directory: ${error.message}`; | ||
if (!continueOnError) { | ||
throw new Error(message); | ||
} | ||
logger.error(message); | ||
} | ||
}); | ||
build.onEnd(async (result) => { | ||
if (!result.metafile || !targetDir) { | ||
logger.error("Missing required configuration"); | ||
return; | ||
} | ||
try { | ||
const outputs = Object.keys(result.metafile.outputs).filter((file) => { | ||
const { entryPoint } = result.metafile.outputs[file]; | ||
return entryPoint && filterFile(entryPoint); | ||
}); | ||
for (const file of outputs) { | ||
const output = result.metafile.outputs[file]; | ||
let content; | ||
if (result.outputFiles) { | ||
const outputFile = result.outputFiles.find((f) => isPathEqual(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"); | ||
} | ||
let targetFileName = basename(file); | ||
let manifest = null; | ||
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}`); | ||
} | ||
manifest = await tryReadManifest(sourceFile, manifestExtensions); | ||
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) { | ||
logger.warn(`Failed to add banner to ${file}: ${error}`); | ||
} | ||
} | ||
await writeFile(targetPath, content); | ||
logger.info(`Deployed: ${file} -> ${targetPath}`); | ||
} | ||
logger.info(`Successfully deployed ${outputs.length} files`); | ||
} | ||
catch (error) { | ||
logger.error(`Deploy failed: ${error.message}`); | ||
if (!continueOnError) { | ||
throw error; | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}; | ||
}; | ||
} | ||
export { scriptableDeploy }; | ||
//# sourceMappingURL=scriptable-deploy.js.map | ||
//# sourceMappingURL=scriptable-deploy.js.map | ||
//# sourceMappingURL=scriptable-deploy.js.map |
{ | ||
"name": "esbuild-plugin-scriptable", | ||
"description": "An ESBuild plugin for developing Scriptable iOS app scripts with TypeScript, manifest support and auto-deployment", | ||
"version": "0.5.4", | ||
"version": "0.5.5", | ||
"keywords": [ | ||
@@ -31,3 +31,3 @@ "scriptable", | ||
"micromatch": "^4.0.8", | ||
"@scriptables/manifest": "0.7.3" | ||
"@scriptables/manifest": "0.7.4" | ||
}, | ||
@@ -44,2 +44,3 @@ "devDependencies": { | ||
"tmp": "^0.2.3", | ||
"ts-add-js-extension": "^1.6.5", | ||
"tslib": "^2.8.1", | ||
@@ -70,2 +71,3 @@ "tsup": "^8.3.5", | ||
"build": "tsup", | ||
"postbuild": "ts-add-js-extension --dir=dist", | ||
"clean": "del-cli dist *.tsbuildinfo", | ||
@@ -72,0 +74,0 @@ "test": "pnpm run jest", |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
117518
13
1275
+ Added@scriptables/manifest@0.7.4(transitive)
- Removed@scriptables/manifest@0.7.3(transitive)
Updated@scriptables/manifest@0.7.4