From 43d3a8c96992b03cb8224c804f4f19f0c44fa2f0 Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:42:43 -0400 Subject: [PATCH 1/8] resolve all paths relative to launchpad config directory --- .changeset/solid-webs-slide.md | 8 ++ docs/src/guides/running-applications.md | 2 +- docs/src/recipes/custom-content-plugin.md | 1 + docs/src/reference/content/content-config.md | 6 ++ docs/src/reference/content/plugins/index.md | 5 ++ docs/src/reference/monitor/plugins.md | 5 ++ packages/cli/src/commands/content.ts | 23 +++--- packages/cli/src/commands/monitor.ts | 47 ++++++------ packages/cli/src/commands/start.ts | 63 +++++++-------- packages/cli/src/utils/command-utils.ts | 10 +-- .../__tests__/content-plugin-driver.test.ts | 10 +-- .../src/__tests__/launchpad-content.test.ts | 20 +++++ packages/content/src/launchpad-content.ts | 22 ++++-- .../src/__tests__/launchpad-monitor.test.ts | 38 +++++++++- .../src/core/__tests__/app-manager.test.ts | 76 +++++++++++++------ .../__tests__/monitor-plugin-driver.test.ts | 2 +- packages/monitor/src/core/app-manager.ts | 25 +++++- packages/monitor/src/launchpad-monitor.ts | 11 ++- packages/testing/src/setup.ts | 4 +- .../utils/src/__tests__/log-manager.test.ts | 15 ++-- .../utils/src/__tests__/plugin-driver.test.ts | 20 ++--- packages/utils/src/log-manager.ts | 10 ++- packages/utils/src/plugin-driver.ts | 8 +- 23 files changed, 292 insertions(+), 139 deletions(-) create mode 100644 .changeset/solid-webs-slide.md diff --git a/.changeset/solid-webs-slide.md b/.changeset/solid-webs-slide.md new file mode 100644 index 00000000..66ce617a --- /dev/null +++ b/.changeset/solid-webs-slide.md @@ -0,0 +1,8 @@ +--- +"@bluecadet/launchpad-content": minor +"@bluecadet/launchpad-monitor": minor +"@bluecadet/launchpad-utils": minor +"@bluecadet/launchpad-cli": minor +--- + +Resolve all paths relative to the launchpad config directory when run from the CLI diff --git a/docs/src/guides/running-applications.md b/docs/src/guides/running-applications.md index ef0ab567..8526139f 100644 --- a/docs/src/guides/running-applications.md +++ b/docs/src/guides/running-applications.md @@ -58,7 +58,7 @@ npx launchpad monitor start - `name`: Unique identifier for your application - `script`: Path to your executable or script -- `cwd`: Working directory for your application +- `cwd`: Working directory for your application. Relative paths are resolved against the current working directory of the launchpad configuration. ### Advanced Settings diff --git a/docs/src/recipes/custom-content-plugin.md b/docs/src/recipes/custom-content-plugin.md index 3e82a419..1d4a07a8 100644 --- a/docs/src/recipes/custom-content-plugin.md +++ b/docs/src/recipes/custom-content-plugin.md @@ -92,6 +92,7 @@ const myPlugin = { logger, // Plugin-specific logging paths, // Helper functions for paths abortSignal // Check if process is stopping + cwd, // The launchpad configuration directory } = ctx; // Example: Log number of documents diff --git a/docs/src/reference/content/content-config.md b/docs/src/reference/content/content-config.md index ef25e233..aa725f97 100644 --- a/docs/src/reference/content/content-config.md +++ b/docs/src/reference/content/content-config.md @@ -29,6 +29,8 @@ See [Content Plugin Reference](./plugins/index.md) for available plugins and usa Base directory path where downloaded files are stored. Can be absolute or relative path. +Relative paths are resolved against the directory of the launchpad configuration. + ### `tempPath` - **Type:** `string` @@ -36,6 +38,8 @@ Base directory path where downloaded files are stored. Can be absolute or relati Temporary directory path used during content processing. The `%TIMESTAMP%` token is replaced with current timestamp. +Relative paths are resolved against the directory of the launchpad configuration. + ### `backupPath` - **Type:** `string` @@ -43,6 +47,8 @@ Temporary directory path used during content processing. The `%TIMESTAMP%` token Directory path where existing content is backed up before processing new downloads. Critical for recovery if downloads fail. +Relative paths are resolved against the directory of the launchpad configuration. + ### `keep` - **Type:** `string[]` diff --git a/docs/src/reference/content/plugins/index.md b/docs/src/reference/content/plugins/index.md index ea6978d6..6a16b11f 100644 --- a/docs/src/reference/content/plugins/index.md +++ b/docs/src/reference/content/plugins/index.md @@ -67,6 +67,7 @@ type CombinedContentHookContext = { contentOptions: ResolvedContentConfig; logger: Logger; abortSignal: AbortSignal; + cwd: string; paths: { getDownloadPath: (source?: string) => string; getTempPath: (source?: string) => string; @@ -97,3 +98,7 @@ A plugin-specific logger. ### `abortSignal` Signals the launchpad process is aborting. Triggered on exception or manual quit. + +### `cwd` + +The current working directory of the launchpad configuration. This is useful for resolving paths relative to the configuration files. \ No newline at end of file diff --git a/docs/src/reference/monitor/plugins.md b/docs/src/reference/monitor/plugins.md index b4ff1aac..4869a85c 100644 --- a/docs/src/reference/monitor/plugins.md +++ b/docs/src/reference/monitor/plugins.md @@ -85,6 +85,7 @@ type CombinedMonitorHookContext = { monitor: LaunchpadMonitor; logger: Logger; abortSignal: AbortSignal; + cwd: string; }; ``` @@ -99,3 +100,7 @@ A plugin-specific logger for recording events and errors. ### `abortSignal` Signals when the monitor process is shutting down, allowing plugins to handle cleanup. + +### `cwd` + +The current working directory of the monitor configuration. This is useful for resolving paths relative to the configuration files. \ No newline at end of file diff --git a/packages/cli/src/commands/content.ts b/packages/cli/src/commands/content.ts index 8e110f0a..fc462cb5 100644 --- a/packages/cli/src/commands/content.ts +++ b/packages/cli/src/commands/content.ts @@ -6,18 +6,19 @@ import { handleFatalError, initializeLogger, loadConfigAndEnv } from "../utils/c export function content(argv: LaunchpadArgv) { return loadConfigAndEnv(argv) .mapErr((error) => handleFatalError(error, console)) - .andThen(initializeLogger) - .andThen(({ config, rootLogger }) => { - return importLaunchpadContent() - .andThen(({ default: LaunchpadContent }) => { - if (!config.content) { - return err(new ConfigError("No content config found in your config file.")); - } + .andThen(({ dir, config }) => { + return initializeLogger(config, dir).asyncAndThen((rootLogger) => { + return importLaunchpadContent() + .andThen(({ default: LaunchpadContent }) => { + if (!config.content) { + return err(new ConfigError("No content config found in your config file.")); + } - const contentInstance = new LaunchpadContent(config.content, rootLogger); - return contentInstance.download(); - }) - .orElse((error) => handleFatalError(error, rootLogger)); + const contentInstance = new LaunchpadContent(config.content, rootLogger, dir); + return contentInstance.download(); + }) + .orElse((error) => handleFatalError(error, rootLogger)); + }); }); } diff --git a/packages/cli/src/commands/monitor.ts b/packages/cli/src/commands/monitor.ts index ba4f56cf..e1773a67 100644 --- a/packages/cli/src/commands/monitor.ts +++ b/packages/cli/src/commands/monitor.ts @@ -6,30 +6,31 @@ import { handleFatalError, initializeLogger, loadConfigAndEnv } from "../utils/c export function monitor(argv: LaunchpadArgv) { return loadConfigAndEnv(argv) .mapErr((error) => handleFatalError(error, console)) - .andThen(initializeLogger) - .andThen(({ config, rootLogger }) => { - return importLaunchpadMonitor() - .andThen(({ default: LaunchpadMonitor }) => { - if (!config.monitor) { - return err(new ConfigError("No monitor config found in your config file.")); - } + .andThen(({ dir, config }) => { + return initializeLogger(config, dir).asyncAndThen((rootLogger) => { + return importLaunchpadMonitor() + .andThen(({ default: LaunchpadMonitor }) => { + if (!config.monitor) { + return err(new ConfigError("No monitor config found in your config file.")); + } - const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger); - return ok(monitorInstance); - }) - .andThrough((monitorInstance) => { - return ResultAsync.fromPromise( - monitorInstance.connect(), - (e) => new MonitorError("Failed to connect to monitor", { cause: e }), - ); - }) - .andThrough((monitorInstance) => { - return ResultAsync.fromPromise( - monitorInstance.start(), - (e) => new MonitorError("Failed to start monitor", { cause: e }), - ); - }) - .orElse((error) => handleFatalError(error, rootLogger)); + const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger, dir); + return ok(monitorInstance); + }) + .andThrough((monitorInstance) => { + return ResultAsync.fromPromise( + monitorInstance.connect(), + (e) => new MonitorError("Failed to connect to monitor", { cause: e }), + ); + }) + .andThrough((monitorInstance) => { + return ResultAsync.fromPromise( + monitorInstance.start(), + (e) => new MonitorError("Failed to start monitor", { cause: e }), + ); + }) + .orElse((error) => handleFatalError(error, rootLogger)); + }); }); } diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 94ed3080..14c5d9fa 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -8,38 +8,39 @@ import { importLaunchpadMonitor } from "./monitor.js"; export async function start(argv: LaunchpadArgv) { return loadConfigAndEnv(argv) .mapErr((error) => handleFatalError(error, console)) - .andThen(initializeLogger) - .andThen(({ config, rootLogger }) => { - return importLaunchpadContent() - .andThen(({ default: LaunchpadContent }) => { - if (!config.content) { - return err(new ConfigError("No content config found in your config file.")); - } + .andThen(({ dir, config }) => { + return initializeLogger(config, dir).asyncAndThen((rootLogger) => { + return importLaunchpadContent() + .andThen(({ default: LaunchpadContent }) => { + if (!config.content) { + return err(new ConfigError("No content config found in your config file.")); + } - const contentInstance = new LaunchpadContent(config.content, rootLogger); - return contentInstance.start(); - }) - .andThen(() => importLaunchpadMonitor()) - .andThen(({ default: LaunchpadMonitor }) => { - if (!config.monitor) { - return err(new ConfigError("No monitor config found in your config file.")); - } + const contentInstance = new LaunchpadContent(config.content, rootLogger); + return contentInstance.start(); + }) + .andThen(() => importLaunchpadMonitor()) + .andThen(({ default: LaunchpadMonitor }) => { + if (!config.monitor) { + return err(new ConfigError("No monitor config found in your config file.")); + } - const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger); - return ok(monitorInstance); - }) - .andThrough((monitorInstance) => { - return ResultAsync.fromPromise( - monitorInstance.connect(), - (e) => new MonitorError("Failed to connect to monitor", { cause: e }), - ); - }) - .andThrough((monitorInstance) => { - return ResultAsync.fromPromise( - monitorInstance.start(), - (e) => new MonitorError("Failed to start monitor", { cause: e }), - ); - }) - .orElse((error) => handleFatalError(error, rootLogger)); + const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger); + return ok(monitorInstance); + }) + .andThrough((monitorInstance) => { + return ResultAsync.fromPromise( + monitorInstance.connect(), + (e) => new MonitorError("Failed to connect to monitor", { cause: e }), + ); + }) + .andThrough((monitorInstance) => { + return ResultAsync.fromPromise( + monitorInstance.start(), + (e) => new MonitorError("Failed to start monitor", { cause: e }), + ); + }) + .orElse((error) => handleFatalError(error, rootLogger)); + }); }); } diff --git a/packages/cli/src/utils/command-utils.ts b/packages/cli/src/utils/command-utils.ts index 5ce8cd9e..6ca5ba14 100644 --- a/packages/cli/src/utils/command-utils.ts +++ b/packages/cli/src/utils/command-utils.ts @@ -11,7 +11,7 @@ import { resolveEnv } from "./env.js"; export function loadConfigAndEnv( argv: LaunchpadArgv, -): ResultAsync { +): ResultAsync<{ dir: string; config: ResolvedLaunchpadOptions }, ConfigError> { const configPath = argv.config ?? findConfig(); if (!configPath) { @@ -45,13 +45,13 @@ export function loadConfigAndEnv( new ConfigError(`Failed to load config file at path: ${chalk.white(configPath)}`, { cause: e, }), - ).map((config) => resolveLaunchpadConfig(config)); + ).map((config) => ({ dir: configDir, config: resolveLaunchpadConfig(config) })); } -export function initializeLogger(config: ResolvedLaunchpadOptions) { - const rootLogger = LogManager.configureRootLogger(config.logging); +export function initializeLogger(config: ResolvedLaunchpadOptions, cwd?: string) { + const rootLogger = LogManager.configureRootLogger(config.logging, cwd); - return ok({ config, rootLogger }); + return ok(rootLogger); } export function handleFatalError(error: Error, rootLogger: Logger | Console): never { diff --git a/packages/content/src/__tests__/content-plugin-driver.test.ts b/packages/content/src/__tests__/content-plugin-driver.test.ts index a67b24d2..26858542 100644 --- a/packages/content/src/__tests__/content-plugin-driver.test.ts +++ b/packages/content/src/__tests__/content-plugin-driver.test.ts @@ -33,7 +33,7 @@ describe("ContentPluginDriver", () => { it("should provide correct context to plugins", async () => { const { dataStore, options, paths } = createMockContext(); const baseLogger = createMockLogger(); - const driver = new PluginDriver(baseLogger); + const driver = new PluginDriver({ logger: baseLogger }); const contentDriver = new ContentPluginDriver(driver, { dataStore, options, @@ -62,7 +62,7 @@ describe("ContentPluginDriver", () => { it("should handle plugin-specific temp paths correctly", async () => { const { dataStore, options, paths } = createMockContext(); const baseLogger = createMockLogger(); - const driver = new PluginDriver(baseLogger); + const driver = new PluginDriver({ logger: baseLogger }); const contentDriver = new ContentPluginDriver(driver, { dataStore, options, @@ -98,7 +98,7 @@ describe("ContentPluginDriver", () => { it("should handle setup errors with ContentError", async () => { const { dataStore, options, paths } = createMockContext(); const baseLogger = createMockLogger(); - const driver = new PluginDriver(baseLogger); + const driver = new PluginDriver({ logger: baseLogger }); const contentDriver = new ContentPluginDriver(driver, { dataStore, options, @@ -125,7 +125,7 @@ describe("ContentPluginDriver", () => { it("should handle fetch errors with ContentError", async () => { const { dataStore, options, paths } = createMockContext(); const baseLogger = createMockLogger(); - const driver = new PluginDriver(baseLogger); + const driver = new PluginDriver({ logger: baseLogger }); const contentDriver = new ContentPluginDriver(driver, { dataStore, options, @@ -154,7 +154,7 @@ describe("ContentPluginDriver", () => { it("should call hooks in correct order", async () => { const { dataStore, options, paths } = createMockContext(); const baseLogger = createMockLogger(); - const driver = new PluginDriver(baseLogger); + const driver = new PluginDriver({ logger: baseLogger }); const contentDriver = new ContentPluginDriver(driver, { dataStore, options, diff --git a/packages/content/src/__tests__/launchpad-content.test.ts b/packages/content/src/__tests__/launchpad-content.test.ts index 8ab8d40b..8ada4791 100644 --- a/packages/content/src/__tests__/launchpad-content.test.ts +++ b/packages/content/src/__tests__/launchpad-content.test.ts @@ -160,4 +160,24 @@ describe("LaunchpadContent", () => { vi.useRealTimers(); }); }); + + describe("cwd parameter", () => { + it("should use the provided cwd for path resolution", () => { + const content = new LaunchpadContent(createBasicConfig(), createMockLogger(), "/some/cwd"); + expect(content.getDownloadPath("/path/to/file")).toBe("/some/cwd/downloads/path/to/file"); + expect(content.getTempPath("/path/to/file")).toBe("/some/cwd/temp/path/to/file"); + expect(content.getBackupPath("/path/to/file")).toBe("/some/cwd/backups/path/to/file"); + }); + + it("should default to process.cwd() if no cwd is provided", () => { + const cwd = process.cwd(); + + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + expect(content.getDownloadPath("/path/to/file")).toBe( + path.join(cwd, "downloads/path/to/file"), + ); + expect(content.getTempPath("/path/to/file")).toBe(path.join(cwd, "temp/path/to/file")); + expect(content.getBackupPath("/path/to/file")).toBe(path.join(cwd, "backups/path/to/file")); + }); + }); }); diff --git a/packages/content/src/launchpad-content.ts b/packages/content/src/launchpad-content.ts index 81b8deaf..9aecbabb 100644 --- a/packages/content/src/launchpad-content.ts +++ b/packages/content/src/launchpad-content.ts @@ -24,10 +24,13 @@ class LaunchpadContent { _rawSources: ConfigContentSource[]; _startDatetime = new Date(); _dataStore: DataStore; + _cwd: string; - constructor(config: ContentConfig, parentLogger: Logger) { + constructor(config: ContentConfig, parentLogger: Logger, cwd = process.cwd()) { this._config = contentConfigSchema.parse(config); + this._cwd = cwd; + this._logger = LogManager.getLogger("content", parentLogger); this._dataStore = new DataStore(this._config.downloadPath); @@ -35,7 +38,10 @@ class LaunchpadContent { // create all sources this._rawSources = this._config.sources; - const basePluginDriver = new PluginDriver(this._logger, this._config.plugins); + const basePluginDriver = new PluginDriver( + { logger: this._logger, cwd: this._cwd }, + this._config.plugins, + ); this._pluginDriver = new ContentPluginDriver(basePluginDriver, { dataStore: this._dataStore, @@ -221,9 +227,9 @@ class LaunchpadContent { getDownloadPath(sourceId?: string): string { if (sourceId) { - return path.resolve(path.join(this._config.downloadPath, sourceId)); + return path.resolve(path.join(this._cwd, this._config.downloadPath, sourceId)); } - return path.resolve(this._config.downloadPath); + return path.resolve(path.join(this._cwd, this._config.downloadPath)); } getTempPath(sourceId?: string, pluginName?: string): string { @@ -232,11 +238,11 @@ class LaunchpadContent { let detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); if (pluginName) { - detokenizedPath = path.join(detokenizedPath, pluginName); + detokenizedPath = path.resolve(path.join(this._cwd, detokenizedPath, pluginName)); } if (sourceId) { - detokenizedPath = path.join(detokenizedPath, sourceId); + detokenizedPath = path.resolve(path.join(this._cwd, detokenizedPath, sourceId)); } return detokenizedPath; @@ -247,9 +253,9 @@ class LaunchpadContent { const tokenizedPath = this._config.backupPath; const detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); if (sourceId) { - return path.join(detokenizedPath, sourceId); + return path.resolve(path.join(this._cwd, detokenizedPath, sourceId)); } - return detokenizedPath; + return path.resolve(path.join(this._cwd, detokenizedPath)); } _createSourcesFromConfig( diff --git a/packages/monitor/src/__tests__/launchpad-monitor.test.ts b/packages/monitor/src/__tests__/launchpad-monitor.test.ts index 2ea79e93..50202cce 100644 --- a/packages/monitor/src/__tests__/launchpad-monitor.test.ts +++ b/packages/monitor/src/__tests__/launchpad-monitor.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it, vi } from "vitest"; import { AppManager } from "../core/app-manager.js"; import type { MonitorPlugin } from "../core/monitor-plugin-driver.js"; import LaunchpadMonitor from "../launchpad-monitor.js"; +import type { MonitorConfig } from "../monitor-config.js"; // Mock process.exit to prevent tests from actually exiting // @ts-expect-error - mockImplementation returns undefined @@ -30,7 +31,7 @@ const mockPlugin = { } as MonitorPlugin; function createTestMonitor( - config = { + config: MonitorConfig = { apps: [ { pm2: { @@ -41,9 +42,10 @@ function createTestMonitor( ], plugins: [mockPlugin], }, + cwd?: string, ) { const rootLogger = createMockLogger(); - const monitor = new LaunchpadMonitor(config, rootLogger); + const monitor = new LaunchpadMonitor(config, rootLogger, cwd); const monitorLogger = rootLogger.children.get("monitor"); if (!monitorLogger) { @@ -54,7 +56,7 @@ function createTestMonitor( monitor, rootLogger, monitorLogger, - plugin: config.plugins[0] as MonitorPlugin, + plugin: config.plugins![0] as MonitorPlugin, }; } @@ -247,4 +249,34 @@ describe("LaunchpadMonitor", () => { }); }); }); + + describe("cwd handling", () => { + it("should use provided cwd for app paths", () => { + const cwd = "/test/cwd"; + const { monitor } = createTestMonitor( + { + apps: [{ pm2: { name: "test-app", script: "test.js", cwd: "app/cwd" } }], + plugins: [mockPlugin], + }, + cwd, + ); + + expect(monitor._cwd).toBe(cwd); + expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatch( + "/test/cwd/app/cwd", + ); + }); + + it("should default to process.cwd() if no cwd is provided", () => { + const { monitor } = createTestMonitor({ + apps: [{ pm2: { name: "test-app", script: "test.js", cwd: "app/cwd" } }], + plugins: [mockPlugin], + }); + + expect(monitor._cwd).toBe(process.cwd()); + expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatch( + "/app/cwd", + ); + }); + }); }); diff --git a/packages/monitor/src/core/__tests__/app-manager.test.ts b/packages/monitor/src/core/__tests__/app-manager.test.ts index 362fb909..56f06d53 100644 --- a/packages/monitor/src/core/__tests__/app-manager.test.ts +++ b/packages/monitor/src/core/__tests__/app-manager.test.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import { ok, okAsync } from "neverthrow"; import { describe, expect, it, vi } from "vitest"; @@ -5,7 +6,29 @@ import type { ResolvedMonitorConfig } from "../../monitor-config.js"; import { AppManager } from "../app-manager.js"; import { ProcessManager } from "../process-manager.js"; -function setupTestAppManager() { +function setupTestAppManager( + apps: ResolvedMonitorConfig["apps"] = [ + { + pm2: { + name: "test-app", + script: "test.js", + cwd: "app/cwd", + }, + windows: { + foreground: false, + minimize: false, + hide: false, + }, + logging: { + logToLaunchpadDir: true, + mode: "bus", + showStdout: true, + showStderr: true, + }, + }, + ], + cwd = process.cwd(), +) { const mockLogger = createMockLogger(); const processManager = new ProcessManager(mockLogger); @@ -26,25 +49,7 @@ function setupTestAppManager() { vi.spyOn(processManager, "deleteAllProcesses"); const mockConfig = { - apps: [ - { - pm2: { - name: "test-app", - script: "test.js", - }, - windows: { - foreground: false, - minimize: false, - hide: false, - }, - logging: { - logToLaunchpadDir: true, - mode: "bus", - showStdout: true, - showStderr: true, - }, - }, - ], + apps, windowsApi: { debounceDelay: 3000, }, @@ -53,7 +58,7 @@ function setupTestAppManager() { shutdownOnExit: true, } as ResolvedMonitorConfig; - const appManager = new AppManager(mockLogger, processManager, mockConfig); + const appManager = new AppManager(mockLogger, processManager, mockConfig, cwd); return { appManager, @@ -148,7 +153,18 @@ describe("AppManager", () => { const { appManager, mockConfig } = setupTestAppManager(); const result = await appManager.getAppOptions("test-app"); expect(result.isOk()).toBe(true); - expect(result._unsafeUnwrap()).toEqual(mockConfig.apps[0]); + + const resolvedCWD = path.resolve(process.cwd(), mockConfig.apps[0]?.pm2.cwd!); + + const expectedOptions = { + ...mockConfig.apps[0], + pm2: { + ...mockConfig.apps[0]!.pm2, + cwd: resolvedCWD, + }, + }; + + expect(result._unsafeUnwrap()).toEqual(expectedOptions); }); it("should throw for invalid app name", async () => { @@ -160,4 +176,20 @@ describe("AppManager", () => { ); }); }); + + describe("cwd handling", () => { + it("should resolve pm2 cwd relative to the manager's cwd", () => { + const { appManager } = setupTestAppManager(undefined, "/passed/cwd"); + const appOptions = appManager.getAppOptions("test-app")._unsafeUnwrap(); + expect(appOptions.pm2.cwd).toBeDefined(); + expect(appOptions.pm2.cwd).toEqual(path.resolve("/passed/cwd", "app/cwd")); + }); + + it("should use default cwd if not specified in config", () => { + const { appManager } = setupTestAppManager(); + const appOptions = appManager.getAppOptions("test-app")._unsafeUnwrap(); + expect(appOptions.pm2.cwd).toBeDefined(); + expect(appOptions.pm2.cwd).toEqual(path.resolve(process.cwd(), "app/cwd")); + }); + }); }); diff --git a/packages/monitor/src/core/__tests__/monitor-plugin-driver.test.ts b/packages/monitor/src/core/__tests__/monitor-plugin-driver.test.ts index 8097ac2d..ecf28e04 100644 --- a/packages/monitor/src/core/__tests__/monitor-plugin-driver.test.ts +++ b/packages/monitor/src/core/__tests__/monitor-plugin-driver.test.ts @@ -38,7 +38,7 @@ describe("MonitorPluginDriver", () => { }, }; - const basePluginDriver = new PluginDriver(mockLogger, [mockPlugin]); + const basePluginDriver = new PluginDriver({ logger: mockLogger }, [mockPlugin]); monitorPluginDriver = new MonitorPluginDriver(basePluginDriver, { monitor: mockMonitor }); }); diff --git a/packages/monitor/src/core/app-manager.ts b/packages/monitor/src/core/app-manager.ts index ee44fad7..e49c9828 100644 --- a/packages/monitor/src/core/app-manager.ts +++ b/packages/monitor/src/core/app-manager.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import type { Logger } from "@bluecadet/launchpad-utils"; import { type Result, ResultAsync, err, errAsync, ok, okAsync } from "neverthrow"; import type pm2 from "pm2"; @@ -10,11 +11,18 @@ export class AppManager { #logger: Logger; #processManager: ProcessManager; #config: ResolvedMonitorConfig; - - constructor(logger: Logger, processManager: ProcessManager, config: ResolvedMonitorConfig) { + #cwd: string; + + constructor( + logger: Logger, + processManager: ProcessManager, + config: ResolvedMonitorConfig, + cwd: string = process.cwd(), + ) { this.#logger = logger; this.#processManager = processManager; this.#config = config; + this.#cwd = cwd; this.applyWindowSettings = debounceResultAsync( this.applyWindowSettings.bind(this), @@ -80,7 +88,18 @@ export class AppManager { if (!options) { return err(new Error(`No app found with the name '${appName}'`)); } - return ok(options); + return ok(this.#updateConfigCWD(options)); + } + + #updateConfigCWD(appConfig: ResolvedAppConfig): ResolvedAppConfig { + return { + ...appConfig, + pm2: { + ...appConfig.pm2, + // Ensure the cwd is resolved relative to the manager's cwd + cwd: appConfig.pm2.cwd ? path.resolve(this.#cwd, appConfig.pm2.cwd) : undefined, + }, + }; } applyWindowSettings(appNames: string[] = []): ResultAsync { diff --git a/packages/monitor/src/launchpad-monitor.ts b/packages/monitor/src/launchpad-monitor.ts index 7f4d3141..fa35306c 100644 --- a/packages/monitor/src/launchpad-monitor.ts +++ b/packages/monitor/src/launchpad-monitor.ts @@ -22,15 +22,17 @@ class LaunchpadMonitor { _appManager: AppManager; _pluginDriver: MonitorPluginDriver; _isShuttingDown = false; + _cwd: string; - constructor(config: MonitorConfig, parentLogger: Logger) { + constructor(config: MonitorConfig, parentLogger: Logger, cwd = process.cwd()) { autoBind(this); this._logger = LogManager.getLogger("monitor", parentLogger); this._config = monitorConfigSchema.parse(config); + this._cwd = cwd; this._processManager = new ProcessManager(this._logger); this._busManager = new BusManager(this._logger); - this._appManager = new AppManager(this._logger, this._processManager, this._config); + this._appManager = new AppManager(this._logger, this._processManager, this._config, cwd); for (const appConf of this._config.apps) { this._busManager.initAppLogging(appConf); @@ -42,7 +44,10 @@ class LaunchpadMonitor { }); } - const basePluginDriver = new PluginDriver(this._logger, this._config.plugins); + const basePluginDriver = new PluginDriver( + { logger: this._logger, cwd: this._cwd }, + this._config.plugins, + ); this._pluginDriver = new MonitorPluginDriver(basePluginDriver, { monitor: this }); } diff --git a/packages/testing/src/setup.ts b/packages/testing/src/setup.ts index dfb07a3d..b2b433b0 100644 --- a/packages/testing/src/setup.ts +++ b/packages/testing/src/setup.ts @@ -1,7 +1,7 @@ import * as posixPath from "node:path/posix"; import { fs, vol } from "memfs"; import { type Result, err, ok } from "neverthrow"; -import { afterEach, expect, vi } from "vitest"; +import { afterEach, beforeAll, expect, vi } from "vitest"; import type { LogEntry } from "winston"; // Mocking the `path` module to use posix paths for consistency across platforms @@ -108,6 +108,8 @@ vi.mock("winston-daily-rotate-file", async () => { }; }); +process.chdir("/"); + // neverthrow expect helpers expect.extend({ toBeOk: (result: Result) => { diff --git a/packages/utils/src/__tests__/log-manager.test.ts b/packages/utils/src/__tests__/log-manager.test.ts index d61568ff..04a42e63 100644 --- a/packages/utils/src/__tests__/log-manager.test.ts +++ b/packages/utils/src/__tests__/log-manager.test.ts @@ -117,17 +117,20 @@ describe("LogManager", () => { describe("getFilePath", () => { it("should generate correct file paths", () => { - const manager = new LogManager({ - fileOptions: { - dirname: "test-logs", - extension: ".log", + const manager = new LogManager( + { + fileOptions: { + dirname: "test-logs", + extension: ".log", + }, }, - }); + "/some/cwd", + ); const dateStr = moment().format("YYYY-MM-DD"); const filePath = manager.getFilePath("test-type"); - expect(filePath).toBe(path.join("test-logs", `${dateStr}-test-type.log`)); + expect(filePath).toBe(path.resolve("/some/cwd/test-logs", `${dateStr}-test-type.log`)); }); it("should return templated paths when requested", () => { diff --git a/packages/utils/src/__tests__/plugin-driver.test.ts b/packages/utils/src/__tests__/plugin-driver.test.ts index 20ba5fe3..30b27d14 100644 --- a/packages/utils/src/__tests__/plugin-driver.test.ts +++ b/packages/utils/src/__tests__/plugin-driver.test.ts @@ -14,13 +14,13 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin]); expect(driver.plugins).toContain(plugin); }); it("should add plugins after initialization", () => { const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger); + const driver = new PluginDriver({ logger: mockLogger }); const plugin = { name: "test-plugin", hooks: { @@ -57,7 +57,7 @@ describe("PluginDriver", () => { const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); await driver.runHookSequential("testHook"); expect(order).toEqual([1, 2]); @@ -84,7 +84,7 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); const result = await driver._runHookSequentialWithCtx("testHook", () => ({}), []); expect(result.isErr()).toBe(true); @@ -117,7 +117,7 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); const result = await driver._runHookParallelWithCtx("testHook", () => ({}), []); expect(result.isOk()).toBe(true); @@ -147,7 +147,7 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); const result = await driver._runHookParallelWithCtx("testHook", () => ({}), []); expect(result.isErr()).toBe(true); @@ -179,7 +179,7 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); const result = await driver._runHookParallelWithCtx("testHook", () => ({}), []); expect(result.isErr()).toBe(true); @@ -200,7 +200,7 @@ describe("PluginDriver", () => { }; const mockLogger = createMockLogger(); - const driver = new PluginDriver(mockLogger, [plugin]); + const driver = new PluginDriver({ logger: mockLogger }, [plugin]); await driver.runHookSequential("testHook"); }); }); @@ -224,7 +224,7 @@ describe("HookContextProvider", () => { }; const mockLogger = createMockLogger(); - const baseDriver = new PluginDriver(mockLogger, [plugin]); + const baseDriver = new PluginDriver({ logger: mockLogger }, [plugin]); const provider = new TestContextProvider(baseDriver); const result = await provider.runHookSequential("testHook"); @@ -258,7 +258,7 @@ describe("HookContextProvider", () => { }; const mockLogger = createMockLogger(); - const baseDriver = new PluginDriver(mockLogger, [plugin1, plugin2]); + const baseDriver = new PluginDriver({ logger: mockLogger }, [plugin1, plugin2]); const provider = new TestContextProvider(baseDriver); // Test sequential diff --git a/packages/utils/src/log-manager.ts b/packages/utils/src/log-manager.ts index ad820be3..9be1eba8 100644 --- a/packages/utils/src/log-manager.ts +++ b/packages/utils/src/log-manager.ts @@ -89,9 +89,11 @@ export class LogManager { static _instance: LogManager | null = null; private _config: ResolvedLogConfig; private _rootLogger: WinstonLogger; + private _cwd: string; - constructor(config: LogConfig = {}) { + constructor(config: LogConfig = {}, cwd: string = process.cwd()) { this._config = logConfigSchema.parse(config); + this._cwd = cwd; const { format: consoleFormat, ...rest } = this._config; @@ -144,9 +146,9 @@ export class LogManager { return LogManager._instance; } - static configureRootLogger(config?: LogConfig): WinstonLogger { + static configureRootLogger(config?: LogConfig, cwd?: string): WinstonLogger { if (LogManager._instance === null) { - LogManager._instance = new LogManager(config); + LogManager._instance = new LogManager(config, cwd); } else { LogManager._instance._rootLogger.warn("Root logger already configured. Ignoring."); } @@ -177,7 +179,7 @@ export class LogManager { output = output.replace(DATE_KEY, dateStr); output = slugify(output); output = output + this._config.fileOptions.extension; - output = path.join(this._config.fileOptions.dirname, output); + output = path.resolve(this._cwd, this._config.fileOptions.dirname, output); } return output; } diff --git a/packages/utils/src/plugin-driver.ts b/packages/utils/src/plugin-driver.ts index 09130f14..3d187a44 100644 --- a/packages/utils/src/plugin-driver.ts +++ b/packages/utils/src/plugin-driver.ts @@ -24,6 +24,7 @@ export class PluginError extends Error { export interface BaseHookContext { logger: Logger; abortSignal: AbortSignal; + cwd: string; } export type HookSet = Record< @@ -65,13 +66,15 @@ export default class PluginDriver { readonly #baseHookContexts = new Map, BaseHookContext>(); readonly #baseLogger: Logger; readonly #abortController = new AbortController(); + readonly #cwd: string; get plugins(): ReadonlyArray> { return this.#plugins; } - constructor(baseLogger: Logger, plugins?: Plugin[]) { - this.#baseLogger = baseLogger; + constructor({ logger, cwd }: { logger: Logger; cwd?: string }, plugins?: Plugin[]) { + this.#baseLogger = logger; + this.#cwd = cwd || process.cwd(); if (plugins) { this.add(plugins); @@ -90,6 +93,7 @@ export default class PluginDriver { this.#baseHookContexts.set(plugin, { logger: this.#baseLogger.child({ module: `plugin:${plugin.name}` }), abortSignal: this.#abortController.signal, + cwd: this.#cwd, }); } } From 3fbbc82db457e86a587a847a7baffb9e0f43fde6 Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:27:02 -0400 Subject: [PATCH 2/8] Support absolute or relative paths in launchpad config. Makes more consistent use of path.resolve instead of path.join. This also reverts some test changes that forced the use of posix paths for consistent test results. Instead we're running path.resolve on test matches before comparing (via the new toMatchPath expect extension). --- .changeset/social-pianos-rest.md | 8 +++ docs/src/recipes/static-web-monitor.md | 2 +- packages/cli/src/utils/config.ts | 2 +- .../src/__tests__/content-integration.test.ts | 12 ++-- .../src/__tests__/launchpad-content.test.ts | 68 +++++++++++++------ packages/content/src/launchpad-content.ts | 16 ++--- .../__tests__/media-downloader.test.ts | 8 +-- .../plugins/__tests__/plugins.test-utils.ts | 7 +- .../src/plugins/__tests__/sharp.test.ts | 3 +- .../content/src/plugins/media-downloader.ts | 4 +- packages/content/src/plugins/sharp.ts | 9 ++- .../src/utils/__tests__/data-store.test.ts | 22 +++--- packages/content/src/utils/data-store.ts | 4 +- packages/content/src/utils/file-utils.ts | 6 +- .../src/__tests__/launchpad-monitor.test.ts | 4 +- packages/testing/src/setup.ts | 34 +++++++--- packages/testing/src/vitest.d.ts | 1 + .../utils/src/__tests__/log-manager.test.ts | 2 +- 18 files changed, 136 insertions(+), 76 deletions(-) create mode 100644 .changeset/social-pianos-rest.md diff --git a/.changeset/social-pianos-rest.md b/.changeset/social-pianos-rest.md new file mode 100644 index 00000000..c98cf03f --- /dev/null +++ b/.changeset/social-pianos-rest.md @@ -0,0 +1,8 @@ +--- +"@bluecadet/launchpad-content": minor +"@bluecadet/launchpad-monitor": minor +"@bluecadet/launchpad-utils": minor +"@bluecadet/launchpad-cli": minor +--- + +Support absolute or relative paths in launchpad config. diff --git a/docs/src/recipes/static-web-monitor.md b/docs/src/recipes/static-web-monitor.md index 342ee425..83c28bbc 100644 --- a/docs/src/recipes/static-web-monitor.md +++ b/docs/src/recipes/static-web-monitor.md @@ -32,7 +32,7 @@ export default defineConfig({ pm2: { name: "webapp-browser", // Path to Chromium (adjust for your environment) - cwd: path.join(homedir(), "AppData/Local/Chromium/Application"), + cwd: path.resolve(homedir(), "AppData/Local/Chromium/Application"), script: "chrome.exe", args: "--kiosk --incognito --disable-pinch --overscroll-history-navgation=0 --enable-auto-reload --autoplay-policy=no-user-gesture-required http://localhost:8080" }, diff --git a/packages/cli/src/utils/config.ts b/packages/cli/src/utils/config.ts index faad0a2e..3b660adf 100644 --- a/packages/cli/src/utils/config.ts +++ b/packages/cli/src/utils/config.ts @@ -36,7 +36,7 @@ function findAllConfigsRecursive() { for (let i = 0; i < maxDepth; i++) { for (const defaultPath of DEFAULT_CONFIG_PATHS) { - const candidatePath = path.join(currentDir, defaultPath); + const candidatePath = path.resolve(currentDir, defaultPath); if (fs.existsSync(candidatePath)) { foundConfigs.push(candidatePath); } diff --git a/packages/content/src/__tests__/content-integration.test.ts b/packages/content/src/__tests__/content-integration.test.ts index d977659c..a2270c41 100644 --- a/packages/content/src/__tests__/content-integration.test.ts +++ b/packages/content/src/__tests__/content-integration.test.ts @@ -1,4 +1,4 @@ -import path from "node:path/posix"; +import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import { vol } from "memfs"; import { http, HttpResponse } from "msw"; @@ -66,7 +66,7 @@ describe("Content Integration", () => { expect(result).toBeOk(); // Check if markdown was converted to HTML - const articlePath = path.join("/downloads", "blog", "article.json"); + const articlePath = path.resolve("/downloads", "blog", "article.json"); expect(vol.existsSync(articlePath)).toBe(true); const article = JSON.parse(vol.readFileSync(articlePath, "utf8").toString()); expect(article.content).toContain("

Hello World

"); @@ -75,7 +75,7 @@ describe("Content Integration", () => { // Check if media was downloaded const mediaFiles = ["gallery1.jpg", "gallery2.jpg"]; for (const file of mediaFiles) { - const mediaPath = path.join("/downloads", "blog", file); + const mediaPath = path.resolve("/downloads", "blog", file); expect(vol.existsSync(mediaPath)).toBe(true); expect(vol.readFileSync(mediaPath, "utf8")).toBe("fake image data"); } @@ -146,7 +146,7 @@ describe("Content Integration", () => { expect(result).toBeOk(); - const articlePath = path.join("/downloads", "cms", "article.json"); + const articlePath = path.resolve("/downloads", "cms", "article.json"); expect(vol.existsSync(articlePath)).toBe(true); const article = JSON.parse(vol.readFileSync(articlePath, "utf8").toString()); @@ -213,8 +213,8 @@ describe("Content Integration", () => { expect(result).toBeOk(); // Check if shared media was downloaded only once and is accessible from both sources - const mediaPath1 = path.join("/downloads", "source1", "shared.jpg"); - const mediaPath2 = path.join("/downloads", "source2", "shared.jpg"); + const mediaPath1 = path.resolve("/downloads", "source1", "shared.jpg"); + const mediaPath2 = path.resolve("/downloads", "source2", "shared.jpg"); expect(vol.existsSync(mediaPath1)).toBe(true); expect(vol.existsSync(mediaPath2)).toBe(true); diff --git a/packages/content/src/__tests__/launchpad-content.test.ts b/packages/content/src/__tests__/launchpad-content.test.ts index 8ada4791..a5f2a073 100644 --- a/packages/content/src/__tests__/launchpad-content.test.ts +++ b/packages/content/src/__tests__/launchpad-content.test.ts @@ -1,8 +1,7 @@ -import path from "node:path/posix"; +import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import { vol } from "memfs"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { contentConfigSchema } from "../content-config.js"; import { ContentError, type ContentPlugin } from "../content-plugin-driver.js"; import LaunchpadContent from "../launchpad-content.js"; import { defineSource } from "../sources/source.js"; @@ -15,9 +14,9 @@ describe("LaunchpadContent", () => { const createBasicConfig = (plugins: ContentPlugin[] = []) => { return { - downloadPath: "/downloads", - tempPath: "/temp", - backupPath: "/backups", + downloadPath: "downloads", + tempPath: "temp", + backupPath: "backups", sources: [ defineSource({ id: "test", @@ -44,7 +43,7 @@ describe("LaunchpadContent", () => { expect(result).toBeOk(); - const filePath = path.join("/downloads", "test", "doc1.json"); + const filePath = path.resolve("/downloads", "test", "doc1.json"); expect(vol.existsSync(filePath)).toBe(true); expect(vol.readFileSync(filePath, "utf8")).toBe(JSON.stringify({ hello: "world" })); }); @@ -146,7 +145,7 @@ describe("LaunchpadContent", () => { it("should handle download path token replacement", () => { const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); const path = content._getDetokenizedPath("/path/to/%DOWNLOAD_PATH%/file", "/downloads"); - expect(path).toBe("/path/to/downloads/file"); + expect(path).toMatchPath("/path/to/downloads/file"); }); it("should handle timestamp token replacement", () => { @@ -156,28 +155,59 @@ describe("LaunchpadContent", () => { const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); const path = content._getDetokenizedPath("/path/to/%TIMESTAMP%/file", "/downloads"); - expect(path).toMatch("/path/to/2024-01-02_00-00-00/file"); + expect(path).toMatchPath("/path/to/2024-01-02_00-00-00/file"); vi.useRealTimers(); }); - }); - describe("cwd parameter", () => { it("should use the provided cwd for path resolution", () => { const content = new LaunchpadContent(createBasicConfig(), createMockLogger(), "/some/cwd"); - expect(content.getDownloadPath("/path/to/file")).toBe("/some/cwd/downloads/path/to/file"); - expect(content.getTempPath("/path/to/file")).toBe("/some/cwd/temp/path/to/file"); - expect(content.getBackupPath("/path/to/file")).toBe("/some/cwd/backups/path/to/file"); + expect(content.getDownloadPath()).toMatchPath("/some/cwd/downloads"); + expect(content.getDownloadPath("source-id")).toMatchPath("/some/cwd/downloads/source-id"); + expect(content.getTempPath()).toMatchPath("/some/cwd/temp"); + expect(content.getTempPath("source-id")).toMatchPath("/some/cwd/temp/source-id"); + expect(content.getTempPath("source-id", "plugin-name")).toMatchPath( + "/some/cwd/temp/plugin-name/source-id", + ); + expect(content.getBackupPath("source-id")).toMatchPath("/some/cwd/backups/source-id"); + expect(content.getBackupPath()).toMatchPath("/some/cwd/backups"); }); it("should default to process.cwd() if no cwd is provided", () => { - const cwd = process.cwd(); - const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); - expect(content.getDownloadPath("/path/to/file")).toBe( - path.join(cwd, "downloads/path/to/file"), + + expect(content.getDownloadPath()).toMatchPath("downloads"); + expect(content.getDownloadPath("source-id")).toMatchPath("downloads/source-id"); + expect(content.getTempPath()).toMatchPath("temp"); + expect(content.getTempPath("source-id")).toMatchPath("temp/source-id"); + expect(content.getTempPath("source-id", "plugin-name")).toMatchPath( + "temp/plugin-name/source-id", + ); + expect(content.getBackupPath("source-id")).toMatchPath("backups/source-id"); + expect(content.getBackupPath()).toMatchPath("backups"); + }); + + it("should support absolute path parameters", () => { + // even though cwd is set, absolute paths should still work + const content = new LaunchpadContent( + { + downloadPath: "/absolute/downloads", + tempPath: "/absolute/temp", + backupPath: "/absolute/backups", + sources: [], + }, + createMockLogger(), + "/some/cwd", + ); + + expect(content.getDownloadPath()).toMatchPath("/absolute/downloads"); + expect(content.getDownloadPath("source-id")).toMatchPath("/absolute/downloads/source-id"); + expect(content.getTempPath()).toMatchPath("/absolute/temp"); + expect(content.getTempPath("source-id")).toMatchPath("/absolute/temp/source-id"); + expect(content.getTempPath("source-id", "plugin-name")).toMatchPath( + "/absolute/temp/plugin-name/source-id", ); - expect(content.getTempPath("/path/to/file")).toBe(path.join(cwd, "temp/path/to/file")); - expect(content.getBackupPath("/path/to/file")).toBe(path.join(cwd, "backups/path/to/file")); + expect(content.getBackupPath("source-id")).toMatchPath("/absolute/backups/source-id"); + expect(content.getBackupPath()).toMatchPath("/absolute/backups"); }); }); }); diff --git a/packages/content/src/launchpad-content.ts b/packages/content/src/launchpad-content.ts index 9aecbabb..1ca3b048 100644 --- a/packages/content/src/launchpad-content.ts +++ b/packages/content/src/launchpad-content.ts @@ -227,9 +227,9 @@ class LaunchpadContent { getDownloadPath(sourceId?: string): string { if (sourceId) { - return path.resolve(path.join(this._cwd, this._config.downloadPath, sourceId)); + return path.resolve(this._cwd, this._config.downloadPath, sourceId); } - return path.resolve(path.join(this._cwd, this._config.downloadPath)); + return path.resolve(this._cwd, this._config.downloadPath); } getTempPath(sourceId?: string, pluginName?: string): string { @@ -238,14 +238,14 @@ class LaunchpadContent { let detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); if (pluginName) { - detokenizedPath = path.resolve(path.join(this._cwd, detokenizedPath, pluginName)); + detokenizedPath = path.resolve(this._cwd, detokenizedPath, pluginName); } if (sourceId) { - detokenizedPath = path.resolve(path.join(this._cwd, detokenizedPath, sourceId)); + detokenizedPath = path.resolve(this._cwd, detokenizedPath, sourceId); } - return detokenizedPath; + return path.resolve(this._cwd, detokenizedPath); } getBackupPath(sourceId?: string): string { @@ -253,9 +253,9 @@ class LaunchpadContent { const tokenizedPath = this._config.backupPath; const detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); if (sourceId) { - return path.resolve(path.join(this._cwd, detokenizedPath, sourceId)); + return path.resolve(path.resolve(this._cwd, detokenizedPath, sourceId)); } - return path.resolve(path.join(this._cwd, detokenizedPath)); + return path.resolve(path.resolve(this._cwd, detokenizedPath)); } _createSourcesFromConfig( @@ -359,7 +359,7 @@ class LaunchpadContent { if (innerTokenizedPath.includes(DOWNLOAD_PATH_TOKEN)) { innerTokenizedPath = innerTokenizedPath.replace(DOWNLOAD_PATH_TOKEN, downloadPath); } - return path.resolve(innerTokenizedPath); + return path.join(innerTokenizedPath); } } diff --git a/packages/content/src/plugins/__tests__/media-downloader.test.ts b/packages/content/src/plugins/__tests__/media-downloader.test.ts index c30276ea..768552b4 100644 --- a/packages/content/src/plugins/__tests__/media-downloader.test.ts +++ b/packages/content/src/plugins/__tests__/media-downloader.test.ts @@ -1,4 +1,4 @@ -import path from "node:path/posix"; +import path from "node:path"; import { vol } from "memfs"; import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; @@ -316,7 +316,7 @@ describe("mediaDownloader", () => { // Check all files were downloaded const expectedFiles = ["1.jpg", "2.jpg", "3.jpg"]; for (const file of expectedFiles) { - const filePath = path.join(ctx.paths.getDownloadPath(), "test", file); + const filePath = path.resolve(ctx.paths.getDownloadPath(), "test", file); expect(vol.existsSync(filePath)).toBe(true); expect(vol.readFileSync(filePath, "utf8")).toBe("media content"); } @@ -349,11 +349,11 @@ describe("mediaDownloader", () => { await plugin.hooks.onContentFetchDone!(ctx); // Success file should exist - const successPath = path.join(ctx.paths.getDownloadPath(), "test", "success.jpg"); + const successPath = path.resolve(ctx.paths.getDownloadPath(), "test", "success.jpg"); expect(vol.existsSync(successPath)).toBe(true); // Error file should not exist - const errorPath = path.join(ctx.paths.getDownloadPath(), "test", "error.jpg"); + const errorPath = path.resolve(ctx.paths.getDownloadPath(), "test", "error.jpg"); expect(vol.existsSync(errorPath)).toBe(false); }); }); diff --git a/packages/content/src/plugins/__tests__/plugins.test-utils.ts b/packages/content/src/plugins/__tests__/plugins.test-utils.ts index da550545..4456043c 100644 --- a/packages/content/src/plugins/__tests__/plugins.test-utils.ts +++ b/packages/content/src/plugins/__tests__/plugins.test-utils.ts @@ -24,16 +24,17 @@ export async function createTestPluginContext({ data, logger, abortSignal: new AbortController().signal, + cwd: "/", paths: { getDownloadPath: vi .fn() - .mockImplementation((sourceId?: string) => path.join("/download", sourceId || "")), + .mockImplementation((sourceId?: string) => path.resolve("download", sourceId || "")), getTempPath: vi .fn() - .mockImplementation((sourceId?: string) => path.join("/temp", sourceId || "")), + .mockImplementation((sourceId?: string) => path.resolve("temp", sourceId || "")), getBackupPath: vi .fn() - .mockImplementation((sourceId?: string) => path.join("/backup", sourceId || "")), + .mockImplementation((sourceId?: string) => path.resolve("backup", sourceId || "")), }, contentOptions: contentConfigSchema.parse(baseOptions), }; diff --git a/packages/content/src/plugins/__tests__/sharp.test.ts b/packages/content/src/plugins/__tests__/sharp.test.ts index 1f9e508b..ba5e1b4c 100644 --- a/packages/content/src/plugins/__tests__/sharp.test.ts +++ b/packages/content/src/plugins/__tests__/sharp.test.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import { vol } from "memfs"; import type Sharp from "sharp"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; @@ -89,7 +90,7 @@ describe("sharp", () => { }); await expect(plugin.hooks.onContentFetchDone!(ctx)).rejects.toThrow( - "Input file '/download/test/nonexistent.jpg' does not exist", + `Input file '${path.resolve("/download/test/nonexistent.jpg")}' does not exist`, ); }); diff --git a/packages/content/src/plugins/media-downloader.ts b/packages/content/src/plugins/media-downloader.ts index 6ee22856..915c66af 100644 --- a/packages/content/src/plugins/media-downloader.ts +++ b/packages/content/src/plugins/media-downloader.ts @@ -354,8 +354,8 @@ export default function mediaDownloader(config: z.input { const destDir = ctx.paths.getTempPath(mediaItem.sourceId); const backupDir = ctx.paths.getBackupPath(mediaItem.sourceId); - const destPath = path.join(destDir, mediaItem.localPath); - const backupPath = path.join(backupDir, mediaItem.localPath); + const destPath = path.resolve(destDir, mediaItem.localPath); + const backupPath = path.resolve(backupDir, mediaItem.localPath); const task = ({ signal }: { signal?: AbortSignal }) => downloadMedia( diff --git a/packages/content/src/plugins/sharp.ts b/packages/content/src/plugins/sharp.ts index ad1dc19f..557643f2 100644 --- a/packages/content/src/plugins/sharp.ts +++ b/packages/content/src/plugins/sharp.ts @@ -158,12 +158,15 @@ export default function sharp(options: z.input) { if (!sourceUrls.has(val)) { sourceUrls.add(val); - const fullInputPath = path.join(ctx.paths.getDownloadPath(source.namespaceId), val); - const fullOutputPath = path.join( + const fullInputPath = path.resolve( + ctx.paths.getDownloadPath(source.namespaceId), + val, + ); + const fullOutputPath = path.resolve( ctx.paths.getTempPath(source.namespaceId), newLocalPath, ); - const fullBackupPath = path.join( + const fullBackupPath = path.resolve( ctx.paths.getBackupPath(source.namespaceId), newLocalPath, ); diff --git a/packages/content/src/utils/__tests__/data-store.test.ts b/packages/content/src/utils/__tests__/data-store.test.ts index 5df9c0e7..72ee0db1 100644 --- a/packages/content/src/utils/__tests__/data-store.test.ts +++ b/packages/content/src/utils/__tests__/data-store.test.ts @@ -1,4 +1,4 @@ -import path from "node:path/posix"; +import path from "node:path"; import { vol } from "memfs"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { DataStore } from "../data-store.js"; @@ -27,7 +27,7 @@ describe("SingleDocument", () => { expect(docResult).toBeOk(); const fileContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.json"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.json"), "utf-8", ); expect(JSON.parse(fileContent.toString())).toEqual({ content: "test content" }); @@ -51,13 +51,13 @@ describe("SingleDocument", () => { })); const originalContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.original.json"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.original.json"), "utf-8", ); expect(JSON.parse(originalContent.toString())).toEqual({ content: "original content" }); const modifiedContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.json"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.json"), "utf-8", ); expect(JSON.parse(modifiedContent.toString())).toEqual({ content: "modified content" }); @@ -80,7 +80,7 @@ describe("SingleDocument", () => { ); const fileContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.json"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.json"), "utf-8", ); expect(JSON.parse(fileContent.toString())).toEqual({ @@ -101,13 +101,13 @@ describe("SingleDocument", () => { ); const fileContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.json"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.json"), "utf-8", ); expect(JSON.parse(fileContent.toString())).toMatchObject({ content: "test content A" }); const extensionFileContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.extension"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.extension"), "utf-8", ); expect(JSON.parse(extensionFileContent.toString())).toMatchObject({ @@ -115,7 +115,7 @@ describe("SingleDocument", () => { }); const extensionExtensionFileContent = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", "test-doc.extension.extension"), + path.resolve(TEST_DIR, "test-namespace", "test-doc.extension.extension"), "utf-8", ); expect(JSON.parse(extensionExtensionFileContent.toString())).toMatchObject({ @@ -161,7 +161,7 @@ describe("BatchDocument", () => { for (let i = 0; i < items.length; i++) { const filename = `test-doc-${i.toString().padStart(2, "0")}.json`; const content = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", filename), + path.resolve(TEST_DIR, "test-namespace", filename), "utf-8", ); expect(JSON.parse(content.toString())).toEqual(items[i]); @@ -192,7 +192,7 @@ describe("BatchDocument", () => { for (let i = 0; i < items.length; i++) { const filename = `test-doc-${i.toString().padStart(2, "0")}.json`; const content = await vol.readFileSync( - path.join(TEST_DIR, "test-namespace", filename), + path.resolve(TEST_DIR, "test-namespace", filename), "utf-8", ); expect(JSON.parse(content.toString())).toEqual({ @@ -219,7 +219,7 @@ describe("DataStore", () => { const result = await store.createNamespace("test-namespace"); expect(result).toBeOk(); - const exists = await vol.existsSync(path.join(TEST_DIR, "test-namespace")); + const exists = await vol.existsSync(path.resolve(TEST_DIR, "test-namespace")); expect(exists).toBe(true); }); diff --git a/packages/content/src/utils/data-store.ts b/packages/content/src/utils/data-store.ts index d399cb37..a7af4fc4 100644 --- a/packages/content/src/utils/data-store.ts +++ b/packages/content/src/utils/data-store.ts @@ -135,7 +135,7 @@ class SingleDocument extends Document { constructor(directory: string, id: string) { super(id); const filename = id.includes(".") ? id : `${id}.json`; - this.#path = path.join(directory, filename); + this.#path = path.resolve(directory, filename); } async initialize(data: T | Promise) { @@ -340,7 +340,7 @@ class Namespace { constructor(parentDirectory: string, id: string) { this.#id = id; - this.#directory = path.join(parentDirectory, id); + this.#directory = path.resolve(parentDirectory, id); } get id() { diff --git a/packages/content/src/utils/file-utils.ts b/packages/content/src/utils/file-utils.ts index 64ae4374..d5821294 100644 --- a/packages/content/src/utils/file-utils.ts +++ b/packages/content/src/utils/file-utils.ts @@ -43,9 +43,9 @@ export function removeFilesFromDir( exclude: string[] = [], ): ResultAsync { // Glob expects posix paths - const globPath = path.join(dirPath, "**/*").replaceAll(path.sep, path.posix.sep); + const globPath = path.resolve(dirPath, "**/*").replaceAll(path.sep, path.posix.sep); const excludePaths = exclude.map((pattern) => - path.join(dirPath, pattern).replaceAll(path.sep, path.posix.sep), + path.resolve(dirPath, pattern).replaceAll(path.sep, path.posix.sep), ); return ResultAsync.fromPromise( @@ -200,7 +200,7 @@ export function copyDir( ) .andThen((entries) => ResultAsync.combine( - entries.map((entry) => copy(path.join(src, entry), path.join(dest, entry), options)), + entries.map((entry) => copy(path.resolve(src, entry), path.resolve(dest, entry), options)), ), ) .map(() => undefined); diff --git a/packages/monitor/src/__tests__/launchpad-monitor.test.ts b/packages/monitor/src/__tests__/launchpad-monitor.test.ts index 50202cce..68e465bd 100644 --- a/packages/monitor/src/__tests__/launchpad-monitor.test.ts +++ b/packages/monitor/src/__tests__/launchpad-monitor.test.ts @@ -262,7 +262,7 @@ describe("LaunchpadMonitor", () => { ); expect(monitor._cwd).toBe(cwd); - expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatch( + expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatchPath( "/test/cwd/app/cwd", ); }); @@ -274,7 +274,7 @@ describe("LaunchpadMonitor", () => { }); expect(monitor._cwd).toBe(process.cwd()); - expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatch( + expect(monitor._appManager.getAppOptions("test-app")._unsafeUnwrap().pm2.cwd).toMatchPath( "/app/cwd", ); }); diff --git a/packages/testing/src/setup.ts b/packages/testing/src/setup.ts index b2b433b0..d9ba656f 100644 --- a/packages/testing/src/setup.ts +++ b/packages/testing/src/setup.ts @@ -1,15 +1,9 @@ -import * as posixPath from "node:path/posix"; -import { fs, vol } from "memfs"; +import path from "node:path"; +import { fs } from "memfs"; import { type Result, err, ok } from "neverthrow"; -import { afterEach, beforeAll, expect, vi } from "vitest"; +import { expect, vi } from "vitest"; import type { LogEntry } from "winston"; -// Mocking the `path` module to use posix paths for consistency across platforms -vi.mock("node:path", () => ({ - ...posixPath, - default: posixPath, -})); - vi.mock("fs", () => ({ ...fs, default: fs, @@ -143,4 +137,26 @@ expect.extend({ actual: result, }; }, + + toMatchPath: (received: string, expected: string) => { + // resolve both paths to ensure they are normalized for the current platform + const normalizedReceived = path.resolve(received); + const normalizedExpected = path.resolve(expected); + + if (normalizedReceived === normalizedExpected) { + return { + pass: true, + message: () => + `Expected paths not to match:\n Received: ${normalizedReceived}\n Expected: ${normalizedExpected}`, + }; + } + + return { + pass: false, + message: () => + `Expected paths to match:\n Received: ${normalizedReceived}\n Expected: ${normalizedExpected}\n Original received: ${received}\n Original expected: ${expected}`, + expected: normalizedExpected, + actual: normalizedReceived, + }; + }, }); diff --git a/packages/testing/src/vitest.d.ts b/packages/testing/src/vitest.d.ts index 7e31ff1b..df862110 100644 --- a/packages/testing/src/vitest.d.ts +++ b/packages/testing/src/vitest.d.ts @@ -3,6 +3,7 @@ import "vitest"; interface CustomMatchers { toBeOk: () => R; toBeErr: () => R; + toMatchPath: (expected: string) => R; } declare module "vitest" { diff --git a/packages/utils/src/__tests__/log-manager.test.ts b/packages/utils/src/__tests__/log-manager.test.ts index 04a42e63..e5df6692 100644 --- a/packages/utils/src/__tests__/log-manager.test.ts +++ b/packages/utils/src/__tests__/log-manager.test.ts @@ -1,4 +1,4 @@ -import path from "node:path/posix"; +import path from "node:path"; import moment from "moment"; import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import winston from "winston"; From 64e127849bbebb290d4fd6db01730750f6ad940f Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:29:15 -0400 Subject: [PATCH 3/8] run github tests on more platforms and node versions --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fafb067c..0065fba1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,18 @@ jobs: ci: name: CI - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + node: [18, 20, 22] + + runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version-file: 'package.json' + node-version: ${{ matrix.node }} cache: 'npm' - name: Install dependencies From 6c82c54012dcb5ded7a988f402c98676d67cd304 Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:58:42 -0400 Subject: [PATCH 4/8] disable strapi tests in node 18 - expected to fail throws an error on instantiation with more info for users also adds `engines` property to package.jsons --- .github/workflows/ci.yml | 4 +- package-lock.json | 19 +++++---- packages/content/package.json | 3 ++ .../sources/__tests__/strapi-source.test.ts | 40 +++++++++++++++++-- packages/content/src/sources/strapi-source.ts | 8 ++++ packages/launchpad/package.json | 3 +- packages/monitor/package.json | 3 ++ 7 files changed, 66 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0065fba1..c74e8a7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - node: [18, 20, 22] - + node: [18, 24] + runs-on: ${{ matrix.os }} steps: diff --git a/package-lock.json b/package-lock.json index f7169301..0189ad10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,9 +37,9 @@ "vue": "^3.5.12" }, "devDependencies": { - "@bluecadet/launchpad": "2.0.11", + "@bluecadet/launchpad": "2.0.12", "@bluecadet/launchpad-cli": "2.1.1", - "@bluecadet/launchpad-content": "2.1.2", + "@bluecadet/launchpad-content": "2.1.3", "@bluecadet/launchpad-monitor": "2.0.5", "@bluecadet/launchpad-scaffold": "2.0.0" } @@ -10399,7 +10399,7 @@ }, "packages/content": { "name": "@bluecadet/launchpad-content", - "version": "2.1.2", + "version": "2.1.3", "license": "ISC", "dependencies": { "@bluecadet/launchpad-utils": "~2.0.1", @@ -10431,6 +10431,9 @@ "sharp": "^0.33.5", "vitest": "^3.0.7" }, + "engines": { + "node": ">=18" + }, "peerDependencies": { "@portabletext/to-html": "2.0.0", "@sanity/block-content-to-markdown": "^0.0.5", @@ -10486,11 +10489,11 @@ }, "packages/launchpad": { "name": "@bluecadet/launchpad", - "version": "2.0.11", + "version": "2.0.12", "license": "ISC", "dependencies": { "@bluecadet/launchpad-cli": "2.1.1", - "@bluecadet/launchpad-content": "2.1.2", + "@bluecadet/launchpad-content": "2.1.3", "@bluecadet/launchpad-dashboard": "2.0.0", "@bluecadet/launchpad-monitor": "2.0.5", "@bluecadet/launchpad-scaffold": "2.0.0" @@ -10499,8 +10502,7 @@ "@bluecadet/launchpad-tsconfig": "0.1.0" }, "engines": { - "node": ">=17.5.0", - "npm": ">=8.5.1" + "node": ">=18" } }, "packages/monitor": { @@ -10528,6 +10530,9 @@ "@types/tail": "2.2", "axon": "^2.0.3", "vitest": "^3.0.7" + }, + "engines": { + "node": ">=18" } }, "packages/monitor/node_modules/chalk": { diff --git a/packages/content/package.json b/packages/content/package.json index 1a7716cd..908dcdde 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -21,6 +21,9 @@ "type": "git", "url": "git+https://github.com/bluecadet/launchpad.git" }, + "engines": { + "node": ">=18" + }, "author": "Bluecadet", "license": "ISC", "bugs": { diff --git a/packages/content/src/sources/__tests__/strapi-source.test.ts b/packages/content/src/sources/__tests__/strapi-source.test.ts index f18f2ca7..227394d6 100644 --- a/packages/content/src/sources/__tests__/strapi-source.test.ts +++ b/packages/content/src/sources/__tests__/strapi-source.test.ts @@ -26,8 +26,27 @@ function createFetchContext() { }; } -describe("strapiSource", () => { - it("should fail with unsupported version", async () => { +const majorNodeVersion = Number.parseInt(process.versions.node.split(".")[0]!); + +describe.runIf(majorNodeVersion < 20)("strapiSource - unsupported", () => { + it("should fail with unsupported node version", async () => { + await expect(() => + strapiSource({ + id: "test-strapi", + baseUrl: "http://localhost:1337", + identifier: "test@example.com", + password: "password", + version: "4", + queries: ["test-content"], + }), + ).rejects.toThrowError( + `Unsupported node version ${process.versions.node}. Strapi source requires node >= 20.`, + ); + }); +}); + +describe.runIf(majorNodeVersion >= 20)("strapiSource", () => { + it("should fail with unsupported strapi API version", async () => { await expect(() => strapiSource({ id: "test-strapi", @@ -38,7 +57,22 @@ describe("strapiSource", () => { version: "5", queries: ["test-content"], }), - ).rejects.toThrow(); + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ZodError: [ + { + "received": "5", + "code": "invalid_enum_value", + "options": [ + "3", + "4" + ], + "path": [ + "version" + ], + "message": "Invalid enum value. Expected '3' | '4', received '5'" + } + ]] + `); }); describe("Strapi v4", () => { diff --git a/packages/content/src/sources/strapi-source.ts b/packages/content/src/sources/strapi-source.ts index 97ec493a..6cbf8a4d 100644 --- a/packages/content/src/sources/strapi-source.ts +++ b/packages/content/src/sources/strapi-source.ts @@ -235,6 +235,14 @@ async function getToken(assembledOptions: StrapiSourceSchemaOutput) { } export default async function strapiSource(options: z.input) { + const majorNodeVersion = Number.parseInt(process.versions.node.split(".")?.[0] ?? "0"); + + if (majorNodeVersion < 20) { + throw new Error( + `Unsupported node version ${process.versions.node}. Strapi source requires node >= 20.`, + ); + } + const assembledOptions = strapiSourceSchema.parse(options); if (assembledOptions.version !== "4" && assembledOptions.version !== "3") { diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 7ee08c65..df9f39f3 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -3,8 +3,7 @@ "version": "2.0.12", "description": "Suite of tools to manage media installations", "engines": { - "npm": ">=8.5.1", - "node": ">=17.5.0" + "node": ">=18" }, "type": "module", "scripts": { diff --git a/packages/monitor/package.json b/packages/monitor/package.json index d581970f..778cca8a 100644 --- a/packages/monitor/package.json +++ b/packages/monitor/package.json @@ -17,6 +17,9 @@ "scripts": { "test": "vitest" }, + "engines": { + "node": ">=18" + }, "repository": { "type": "git", "url": "git+https://github.com/bluecadet/launchpad.git" From fa632ab0508af0ca3f4be5f86fdb78c3cfebaa29 Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:25:41 -0400 Subject: [PATCH 5/8] fix lint error, enforce lf line endings --- .gitattributes | 3 +-- biome.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index a3002918..21256661 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -# Declare files that will always have LF line endings on checkout. -*.sh text eol=lf +* text=auto \ No newline at end of file diff --git a/biome.json b/biome.json index 847188d9..208cc346 100644 --- a/biome.json +++ b/biome.json @@ -13,7 +13,8 @@ "formatter": { "enabled": true, "indentStyle": "tab", - "lineWidth": 100 + "lineWidth": 100, + "lineEnding": "lf" }, "organizeImports": { "enabled": true From 95cbd71caf57ba4903ebc18769c464025ec1a18b Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:33:21 -0400 Subject: [PATCH 6/8] update biome --- biome.json | 25 +++++-- docs/.vitepress/config.mts | 2 +- docs/.vitepress/theme/index.ts | 2 +- docs/src/components/PackageCard.vue | 4 +- docs/src/components/PackageHeader.vue | 6 +- docs/src/components/package-versions.data.ts | 2 +- package-lock.json | 73 +++++++++---------- package.json | 2 +- packages/cli/src/cli.ts | 1 + packages/cli/src/commands/content.ts | 2 +- packages/cli/src/commands/monitor.ts | 2 +- packages/cli/src/commands/scaffold.ts | 2 +- packages/cli/src/commands/start.ts | 2 +- packages/cli/src/commands/stop.ts | 2 +- packages/cli/src/utils/command-utils.ts | 4 +- .../src/__tests__/content-integration.test.ts | 2 +- packages/content/src/content-config.ts | 3 +- packages/content/src/content-plugin-driver.ts | 2 +- packages/content/src/index.ts | 6 +- packages/content/src/launchpad-content.ts | 7 +- .../__tests__/media-downloader.test.ts | 6 +- .../plugins/__tests__/plugins.test-utils.ts | 9 ++- packages/content/src/plugins/index.ts | 6 +- packages/content/src/plugins/md-to-html.ts | 2 +- .../content/src/plugins/media-downloader.ts | 4 +- .../sources/__tests__/airtable-source.test.ts | 2 +- .../__tests__/contentful-source.test.ts | 2 +- .../src/sources/__tests__/json-source.test.ts | 2 +- .../sources/__tests__/sanity-source.test.ts | 17 ++--- .../sources/__tests__/strapi-source.test.ts | 2 +- packages/content/src/sources/index.ts | 4 +- packages/content/src/sources/json-source.ts | 1 - packages/content/src/sources/strapi-source.ts | 6 +- .../src/utils/__tests__/data-store.test.ts | 2 +- .../utils/__tests__/fetch-paginated.test.ts | 2 +- .../src/utils/__tests__/safe-ky.test.ts | 4 +- .../src/utils/content-transform-utils.ts | 2 +- packages/content/src/utils/data-store.ts | 4 +- packages/content/src/utils/fetch-logger.ts | 2 +- packages/content/src/utils/file-utils.ts | 2 +- .../content/src/utils/result-async-queue.ts | 6 +- packages/content/src/utils/safe-ky.ts | 4 +- .../src/core/__tests__/app-manager.test.ts | 2 +- .../src/core/__tests__/bus-manager.test.ts | 6 +- .../src/core/__tests__/core.test-utils.ts | 2 +- .../core/__tests__/process-manager.test.ts | 8 +- packages/monitor/src/core/app-manager.ts | 2 +- packages/monitor/src/core/bus-manager.ts | 6 +- .../monitor/src/core/monitor-plugin-driver.ts | 2 +- packages/monitor/src/core/process-manager.ts | 2 +- packages/monitor/src/index.ts | 2 +- packages/monitor/src/launchpad-monitor.ts | 7 +- .../utils/__tests__/debounce-results.test.ts | 2 +- packages/scaffold/src/index.ts | 4 +- packages/testing/src/setup.ts | 12 +-- .../utils/src/__tests__/log-manager.test.ts | 4 +- .../utils/src/__tests__/plugin-driver.test.ts | 2 +- packages/utils/src/index.ts | 20 ++--- packages/utils/src/plugin-driver.ts | 6 +- 59 files changed, 165 insertions(+), 166 deletions(-) diff --git a/biome.json b/biome.json index 208cc346..68240948 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json", "vcs": { "enabled": false, "clientKind": "git", @@ -7,8 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": [], - "include": ["**/src/**/*.ts", "docs/**/*.ts", "docs/**/*.mts", "docs/**/*.vue"] + "includes": ["**/src/**/*.ts", "**/docs/**/*.ts", "**/docs/**/*.mts", "**/docs/**/*.vue"] }, "formatter": { "enabled": true, @@ -16,13 +15,23 @@ "lineWidth": 100, "lineEnding": "lf" }, - "organizeImports": { - "enabled": true - }, + "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { - "recommended": true + "recommended": true, + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } } }, "javascript": { @@ -32,7 +41,7 @@ }, "overrides": [ { - "include": ["**/*.test.ts"], + "includes": ["**/*.test.ts"], "linter": { "rules": { "style": { diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 34202c6c..21782cc2 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -55,7 +55,7 @@ export default defineConfig({ }, // add canonical link to head - transformPageData(pageData, ctx) { + transformPageData(pageData, _ctx) { const canonicalUrl = `https://bluecadet.github.io/launchpad/${pageData.relativePath}` .replace(/index\.md$/, "") .replace(/\.md$/, ".html"); diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index f5debb24..e79fa52e 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -11,5 +11,5 @@ export default { // https://vitepress.dev/guide/extending-default-theme#layout-slots }); }, - enhanceApp({ app, router, siteData }) {}, + enhanceApp() {}, } satisfies Theme; diff --git a/docs/src/components/PackageCard.vue b/docs/src/components/PackageCard.vue index bd694421..9e7ef248 100644 --- a/docs/src/components/PackageCard.vue +++ b/docs/src/components/PackageCard.vue @@ -1,7 +1,5 @@ diff --git a/docs/src/components/package-versions.data.ts b/docs/src/components/package-versions.data.ts index 581e89d3..d2348a1d 100644 --- a/docs/src/components/package-versions.data.ts +++ b/docs/src/components/package-versions.data.ts @@ -1,8 +1,8 @@ +import launchpadPackage from "@bluecadet/launchpad/package.json" with { type: "json" }; import cliPackage from "@bluecadet/launchpad-cli/package.json" with { type: "json" }; import contentPackage from "@bluecadet/launchpad-content/package.json" with { type: "json" }; import monitorPackage from "@bluecadet/launchpad-monitor/package.json" with { type: "json" }; import scaffoldPackage from "@bluecadet/launchpad-scaffold/package.json" with { type: "json" }; -import launchpadPackage from "@bluecadet/launchpad/package.json" with { type: "json" }; import { defineLoader } from "vitepress"; export interface Data { diff --git a/package-lock.json b/package-lock.json index 0189ad10..fc1a6409 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "./docs" ], "devDependencies": { - "@biomejs/biome": "1.9.4", + "@biomejs/biome": "2.0.6", "@changesets/changelog-github": "^0.4.6", "@changesets/cli": "^2.23.0", "@types/node": "^22.9.3", @@ -329,11 +329,10 @@ } }, "node_modules/@biomejs/biome": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", - "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.0.6.tgz", + "integrity": "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA==", "dev": true, - "hasInstallScript": true, "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" @@ -346,20 +345,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.9.4", - "@biomejs/cli-darwin-x64": "1.9.4", - "@biomejs/cli-linux-arm64": "1.9.4", - "@biomejs/cli-linux-arm64-musl": "1.9.4", - "@biomejs/cli-linux-x64": "1.9.4", - "@biomejs/cli-linux-x64-musl": "1.9.4", - "@biomejs/cli-win32-arm64": "1.9.4", - "@biomejs/cli-win32-x64": "1.9.4" + "@biomejs/cli-darwin-arm64": "2.0.6", + "@biomejs/cli-darwin-x64": "2.0.6", + "@biomejs/cli-linux-arm64": "2.0.6", + "@biomejs/cli-linux-arm64-musl": "2.0.6", + "@biomejs/cli-linux-x64": "2.0.6", + "@biomejs/cli-linux-x64-musl": "2.0.6", + "@biomejs/cli-win32-arm64": "2.0.6", + "@biomejs/cli-win32-x64": "2.0.6" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", - "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A==", "cpu": [ "arm64" ], @@ -374,9 +373,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", - "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.0.6.tgz", + "integrity": "sha512-wJjjP4E7bO4WJmiQaLnsdXMa516dbtC6542qeRkyJg0MqMXP0fvs4gdsHhZ7p9XWTAmGIjZHFKXdsjBvKGIJJQ==", "cpu": [ "x64" ], @@ -391,9 +390,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", - "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.0.6.tgz", + "integrity": "sha512-ZSVf6TYo5rNMUHIW1tww+rs/krol7U5A1Is/yzWyHVZguuB0lBnIodqyFuwCNqG9aJGyk7xIMS8HG0qGUPz0SA==", "cpu": [ "arm64" ], @@ -408,9 +407,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", - "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.6.tgz", + "integrity": "sha512-CVPEMlin3bW49sBqLBg2x016Pws7eUXA27XYDFlEtponD0luYjg2zQaMJ2nOqlkKG9fqzzkamdYxHdMDc2gZFw==", "cpu": [ "arm64" ], @@ -425,9 +424,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", - "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.0.6.tgz", + "integrity": "sha512-geM1MkHTV1Kh2Cs/Xzot9BOF3WBacihw6bkEmxkz4nSga8B9/hWy5BDiOG3gHDGIBa8WxT0nzsJs2f/hPqQIQw==", "cpu": [ "x64" ], @@ -442,9 +441,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", - "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.6.tgz", + "integrity": "sha512-mKHE/e954hR/hSnAcJSjkf4xGqZc/53Kh39HVW1EgO5iFi0JutTN07TSjEMg616julRtfSNJi0KNyxvc30Y4rQ==", "cpu": [ "x64" ], @@ -459,9 +458,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", - "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.0.6.tgz", + "integrity": "sha512-290V4oSFoKaprKE1zkYVsDfAdn0An5DowZ+GIABgjoq1ndhvNxkJcpxPsiYtT7slbVe3xmlT0ncdfOsN7KruzA==", "cpu": [ "arm64" ], @@ -476,9 +475,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", - "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.0.6.tgz", + "integrity": "sha512-bfM1Bce0d69Ao7pjTjUS+AWSZ02+5UHdiAP85Th8e9yV5xzw6JrHXbL5YWlcEKQ84FIZMdDc7ncuti1wd2sdbw==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index f8a696b6..0b72f9ff 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "./docs" ], "devDependencies": { - "@biomejs/biome": "1.9.4", + "@biomejs/biome": "2.0.6", "@changesets/changelog-github": "^0.4.6", "@changesets/cli": "^2.23.0", "@types/node": "^22.9.3", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index f5f8df2d..594c0681 100755 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,6 +2,7 @@ import { hideBin } from "yargs/helpers"; import yargs from "yargs/yargs"; + export { defineConfig } from "./launchpad-config.js"; export type LaunchpadArgv = { diff --git a/packages/cli/src/commands/content.ts b/packages/cli/src/commands/content.ts index fc462cb5..0a8d5909 100644 --- a/packages/cli/src/commands/content.ts +++ b/packages/cli/src/commands/content.ts @@ -1,4 +1,4 @@ -import { ResultAsync, err, ok } from "neverthrow"; +import { err, ResultAsync } from "neverthrow"; import type { LaunchpadArgv } from "../cli.js"; import { ConfigError, ImportError } from "../errors.js"; import { handleFatalError, initializeLogger, loadConfigAndEnv } from "../utils/command-utils.js"; diff --git a/packages/cli/src/commands/monitor.ts b/packages/cli/src/commands/monitor.ts index e1773a67..eeca1cbc 100644 --- a/packages/cli/src/commands/monitor.ts +++ b/packages/cli/src/commands/monitor.ts @@ -1,4 +1,4 @@ -import { ResultAsync, err, ok } from "neverthrow"; +import { err, ok, ResultAsync } from "neverthrow"; import type { LaunchpadArgv } from "../cli.js"; import { ConfigError, ImportError, MonitorError } from "../errors.js"; import { handleFatalError, initializeLogger, loadConfigAndEnv } from "../utils/command-utils.js"; diff --git a/packages/cli/src/commands/scaffold.ts b/packages/cli/src/commands/scaffold.ts index e12f8d82..35703108 100644 --- a/packages/cli/src/commands/scaffold.ts +++ b/packages/cli/src/commands/scaffold.ts @@ -2,7 +2,7 @@ import { launchScaffold } from "@bluecadet/launchpad-scaffold"; import { LogManager } from "@bluecadet/launchpad-utils"; import type { LaunchpadArgv } from "../cli.js"; -export async function scaffold(argv: LaunchpadArgv) { +export async function scaffold(_argv: LaunchpadArgv) { const rootLogger = LogManager.configureRootLogger(); await launchScaffold(rootLogger); } diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 14c5d9fa..b8ddf189 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -1,4 +1,4 @@ -import { ResultAsync, err, ok } from "neverthrow"; +import { err, ok, ResultAsync } from "neverthrow"; import type { LaunchpadArgv } from "../cli.js"; import { ConfigError, MonitorError } from "../errors.js"; import { handleFatalError, initializeLogger, loadConfigAndEnv } from "../utils/command-utils.js"; diff --git a/packages/cli/src/commands/stop.ts b/packages/cli/src/commands/stop.ts index 05164f39..3ecd1a05 100644 --- a/packages/cli/src/commands/stop.ts +++ b/packages/cli/src/commands/stop.ts @@ -2,7 +2,7 @@ import LaunchpadMonitor from "@bluecadet/launchpad-monitor"; import { LogManager } from "@bluecadet/launchpad-utils"; import type { LaunchpadArgv } from "../cli.js"; -export async function stop(argv: LaunchpadArgv) { +export async function stop(_argv: LaunchpadArgv) { const logger = LogManager.configureRootLogger(); await LaunchpadMonitor.kill(logger); } diff --git a/packages/cli/src/utils/command-utils.ts b/packages/cli/src/utils/command-utils.ts index 6ca5ba14..0e04af30 100644 --- a/packages/cli/src/utils/command-utils.ts +++ b/packages/cli/src/utils/command-utils.ts @@ -1,7 +1,7 @@ import path from "node:path"; -import { LogManager, type Logger, NO_TTY, TTY_FIXED_END } from "@bluecadet/launchpad-utils"; +import { type Logger, LogManager, TTY_FIXED_END } from "@bluecadet/launchpad-utils"; import chalk from "chalk"; -import { ResultAsync, err, errAsync, ok } from "neverthrow"; +import { errAsync, ok, ResultAsync } from "neverthrow"; import { ZodError } from "zod"; import type { LaunchpadArgv } from "../cli.js"; import { ConfigError } from "../errors.js"; diff --git a/packages/content/src/__tests__/content-integration.test.ts b/packages/content/src/__tests__/content-integration.test.ts index a2270c41..7723deec 100644 --- a/packages/content/src/__tests__/content-integration.test.ts +++ b/packages/content/src/__tests__/content-integration.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import { vol } from "memfs"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import LaunchpadContent from "../launchpad-content.js"; diff --git a/packages/content/src/content-config.ts b/packages/content/src/content-config.ts index 2f04a5b4..fb143322 100644 --- a/packages/content/src/content-config.ts +++ b/packages/content/src/content-config.ts @@ -1,7 +1,6 @@ import { z } from "zod"; -import { type ContentPlugin, contentPluginSchema } from "./content-plugin-driver.js"; +import { contentPluginSchema } from "./content-plugin-driver.js"; import { type ContentSource, contentSourceSchema } from "./sources/source.js"; -import plugin from "./utils/markdown-it-italic-bold.js"; export const DOWNLOAD_PATH_TOKEN = "%DOWNLOAD_PATH%"; export const TIMESTAMP_TOKEN = "%TIMESTAMP%"; diff --git a/packages/content/src/content-plugin-driver.ts b/packages/content/src/content-plugin-driver.ts index b6722bb4..7633b836 100644 --- a/packages/content/src/content-plugin-driver.ts +++ b/packages/content/src/content-plugin-driver.ts @@ -1,9 +1,9 @@ import { type BaseHookContext, + createPluginValidator, HookContextProvider, type Plugin, type PluginDriver, - createPluginValidator, } from "@bluecadet/launchpad-utils"; import type { ResolvedContentConfig } from "./content-config.js"; import type { DataStore } from "./utils/data-store.js"; diff --git a/packages/content/src/index.ts b/packages/content/src/index.ts index 3d1b18f5..e5360906 100755 --- a/packages/content/src/index.ts +++ b/packages/content/src/index.ts @@ -1,10 +1,10 @@ import LaunchpadContent from "./launchpad-content.js"; export * from "./content-config.js"; +export * from "./content-plugin-driver.js"; export * from "./launchpad-content.js"; -export * from "./utils/file-utils.js"; -export * from "./sources/index.js"; export * from "./plugins/index.js"; -export * from "./content-plugin-driver.js"; +export * from "./sources/index.js"; +export * from "./utils/file-utils.js"; export default LaunchpadContent; diff --git a/packages/content/src/launchpad-content.ts b/packages/content/src/launchpad-content.ts index 1ca3b048..1f5c03b4 100644 --- a/packages/content/src/launchpad-content.ts +++ b/packages/content/src/launchpad-content.ts @@ -1,15 +1,14 @@ import path from "node:path"; -import { LogManager, type Logger } from "@bluecadet/launchpad-utils"; -import { PluginDriver } from "@bluecadet/launchpad-utils"; +import { type Logger, LogManager, PluginDriver } from "@bluecadet/launchpad-utils"; import chalk from "chalk"; -import { Result, ResultAsync, err, ok, okAsync } from "neverthrow"; +import { err, ok, okAsync, Result, ResultAsync } from "neverthrow"; import { type ConfigContentSource, type ContentConfig, + contentConfigSchema, DOWNLOAD_PATH_TOKEN, type ResolvedContentConfig, TIMESTAMP_TOKEN, - contentConfigSchema, } from "./content-config.js"; import { ContentError, ContentPluginDriver } from "./content-plugin-driver.js"; import type { ContentSource } from "./sources/source.js"; diff --git a/packages/content/src/plugins/__tests__/media-downloader.test.ts b/packages/content/src/plugins/__tests__/media-downloader.test.ts index 768552b4..34388b27 100644 --- a/packages/content/src/plugins/__tests__/media-downloader.test.ts +++ b/packages/content/src/plugins/__tests__/media-downloader.test.ts @@ -1,14 +1,14 @@ import path from "node:path"; import { vol } from "memfs"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import type { z } from "zod"; import mediaDownloader, { - localFilePathFromUrl, checkCacheStatus, downloadFile, findMediaUrls, + localFilePathFromUrl, mediaDownloaderConfigSchema, } from "../media-downloader.js"; import { createTestPluginContext } from "./plugins.test-utils.js"; diff --git a/packages/content/src/plugins/__tests__/plugins.test-utils.ts b/packages/content/src/plugins/__tests__/plugins.test-utils.ts index 4456043c..d63b9da7 100644 --- a/packages/content/src/plugins/__tests__/plugins.test-utils.ts +++ b/packages/content/src/plugins/__tests__/plugins.test-utils.ts @@ -2,8 +2,7 @@ import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import type { Logger } from "@bluecadet/launchpad-utils"; import { vol } from "memfs"; -import { vi } from "vitest"; -import { afterEach } from "vitest"; +import { afterEach, vi } from "vitest"; import { type ContentConfig, contentConfigSchema } from "../../content-config.js"; import { DataStore } from "../../utils/data-store.js"; @@ -17,7 +16,11 @@ afterEach(() => { export async function createTestPluginContext({ baseOptions = {}, logger = createMockLogger(), -}: { namespaces?: string[]; baseOptions?: ContentConfig; logger?: Logger } = {}) { +}: { + namespaces?: string[]; + baseOptions?: ContentConfig; + logger?: Logger; +} = {}) { const data = new DataStore("/"); return { diff --git a/packages/content/src/plugins/index.ts b/packages/content/src/plugins/index.ts index e1a5467b..1ddb4e21 100644 --- a/packages/content/src/plugins/index.ts +++ b/packages/content/src/plugins/index.ts @@ -1,7 +1,7 @@ export { default as mdToHtml } from "./md-to-html.js"; +export { default as mediaDownloader } from "./media-downloader.js"; +export { default as sanityImageUrlTransform } from "./sanity-image-url-transform.js"; export { default as sanityToHtml } from "./sanity-to-html.js"; -export { default as sanityToPlain } from "./sanity-to-plain.js"; export { default as sanityToMd } from "./sanity-to-markdown.js"; -export { default as mediaDownloader } from "./media-downloader.js"; +export { default as sanityToPlain } from "./sanity-to-plain.js"; export { default as sharp } from "./sharp.js"; -export { default as sanityImageUrlTransform } from "./sanity-image-url-transform.js"; diff --git a/packages/content/src/plugins/md-to-html.ts b/packages/content/src/plugins/md-to-html.ts index 72597cdf..d5bd3dd8 100644 --- a/packages/content/src/plugins/md-to-html.ts +++ b/packages/content/src/plugins/md-to-html.ts @@ -4,7 +4,7 @@ import sanitizeHtml from "sanitize-html"; import { z } from "zod"; import { defineContentPlugin } from "../content-plugin-driver.js"; import { applyTransformToFiles } from "../utils/content-transform-utils.js"; -import { type DataKeys, dataKeysSchema } from "../utils/data-store.js"; +import { dataKeysSchema } from "../utils/data-store.js"; import markdownItItalicBold from "../utils/markdown-it-italic-bold.js"; import { parsePluginConfig } from "./contentPluginHelpers.js"; diff --git a/packages/content/src/plugins/media-downloader.ts b/packages/content/src/plugins/media-downloader.ts index 915c66af..d9da09b8 100644 --- a/packages/content/src/plugins/media-downloader.ts +++ b/packages/content/src/plugins/media-downloader.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { pipeline } from "node:stream/promises"; import chalk from "chalk"; -import { ResultAsync, errAsync, ok, okAsync } from "neverthrow"; +import { errAsync, ok, okAsync, ResultAsync } from "neverthrow"; import { z } from "zod"; import { type ContentHookContext, defineContentPlugin } from "../content-plugin-driver.js"; import type { DataStore } from "../utils/data-store.js"; @@ -295,7 +295,7 @@ function setupDownloadDirectories( function cleanupAfterDownload( ctx: ContentHookContext, - config: MediaDownloaderConfigWithDefaults, + _config: MediaDownloaderConfigWithDefaults, ): ResultAsync { return FileUtils.copy(ctx.paths.getTempPath(), ctx.paths.getDownloadPath()) .andThen(() => FileUtils.remove(ctx.paths.getTempPath())) diff --git a/packages/content/src/sources/__tests__/airtable-source.test.ts b/packages/content/src/sources/__tests__/airtable-source.test.ts index fbd38e3e..568c7f70 100644 --- a/packages/content/src/sources/__tests__/airtable-source.test.ts +++ b/packages/content/src/sources/__tests__/airtable-source.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { DataStore } from "../../utils/data-store.js"; diff --git a/packages/content/src/sources/__tests__/contentful-source.test.ts b/packages/content/src/sources/__tests__/contentful-source.test.ts index 611977f7..6edba409 100644 --- a/packages/content/src/sources/__tests__/contentful-source.test.ts +++ b/packages/content/src/sources/__tests__/contentful-source.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { DataStore } from "../../utils/data-store.js"; diff --git a/packages/content/src/sources/__tests__/json-source.test.ts b/packages/content/src/sources/__tests__/json-source.test.ts index c64bfc48..78577bb4 100644 --- a/packages/content/src/sources/__tests__/json-source.test.ts +++ b/packages/content/src/sources/__tests__/json-source.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { DataStore } from "../../utils/data-store.js"; diff --git a/packages/content/src/sources/__tests__/sanity-source.test.ts b/packages/content/src/sources/__tests__/sanity-source.test.ts index f10032f8..72934ccc 100644 --- a/packages/content/src/sources/__tests__/sanity-source.test.ts +++ b/packages/content/src/sources/__tests__/sanity-source.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { DataStore } from "../../utils/data-store.js"; @@ -230,15 +230,12 @@ describe("sanitySource", () => { it("should support single item responses", async () => { server.use( - http.get( - "https://test-project.api.sanity.io/v2021-10-21/data/query/production", - ({ request }) => { - return HttpResponse.json({ - result: { _type: "test", title: "Test Document" }, - ms: 15, - }); - }, - ), + http.get("https://test-project.api.sanity.io/v2021-10-21/data/query/production", () => { + return HttpResponse.json({ + result: { _type: "test", title: "Test Document" }, + ms: 15, + }); + }), ); const source = await sanitySource({ diff --git a/packages/content/src/sources/__tests__/strapi-source.test.ts b/packages/content/src/sources/__tests__/strapi-source.test.ts index 227394d6..36b10c36 100644 --- a/packages/content/src/sources/__tests__/strapi-source.test.ts +++ b/packages/content/src/sources/__tests__/strapi-source.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { DataStore } from "../../utils/data-store.js"; diff --git a/packages/content/src/sources/index.ts b/packages/content/src/sources/index.ts index c879c0fc..c8f568ea 100644 --- a/packages/content/src/sources/index.ts +++ b/packages/content/src/sources/index.ts @@ -1,6 +1,6 @@ -export { default as jsonSource } from "./json-source.js"; export { default as airtableSource } from "./airtable-source.js"; export { default as contentfulSource } from "./contentful-source.js"; +export { default as jsonSource } from "./json-source.js"; export { default as sanitySource } from "./sanity-source.js"; -export { default as strapiSource } from "./strapi-source.js"; export * from "./source.js"; +export { default as strapiSource } from "./strapi-source.js"; diff --git a/packages/content/src/sources/json-source.ts b/packages/content/src/sources/json-source.ts index 4c3aece5..ef7ae2ca 100644 --- a/packages/content/src/sources/json-source.ts +++ b/packages/content/src/sources/json-source.ts @@ -1,6 +1,5 @@ import chalk from "chalk"; import ky from "ky"; -import { okAsync } from "neverthrow"; import { z } from "zod"; import { defineSource } from "./source.js"; diff --git a/packages/content/src/sources/strapi-source.ts b/packages/content/src/sources/strapi-source.ts index 6cbf8a4d..6a44b85b 100644 --- a/packages/content/src/sources/strapi-source.ts +++ b/packages/content/src/sources/strapi-source.ts @@ -101,11 +101,11 @@ class StrapiVersionUtils { this.logger = logger; } - buildUrl(query: StrapiObjectQuery, pagination?: StrapiPagination): string { + buildUrl(_query: StrapiObjectQuery, _pagination?: StrapiPagination): string { throw new Error("Not implemented"); } - hasPaginationParams(query: StrapiObjectQuery): boolean { + hasPaginationParams(_query: StrapiObjectQuery): boolean { throw new Error("Not implemented"); } @@ -117,7 +117,7 @@ class StrapiVersionUtils { return result; } - canFetchMore(result: unknown): boolean { + canFetchMore(_result: unknown): boolean { throw new Error("Not implemented"); } diff --git a/packages/content/src/utils/__tests__/data-store.test.ts b/packages/content/src/utils/__tests__/data-store.test.ts index 72ee0db1..785a9aff 100644 --- a/packages/content/src/utils/__tests__/data-store.test.ts +++ b/packages/content/src/utils/__tests__/data-store.test.ts @@ -148,7 +148,7 @@ describe("BatchDocument", () => { { id: 3, content: "third" }, ]; - const doc = await namespace.insert( + const _doc = await namespace.insert( "test-doc", (async function* () { for (const item of items) { diff --git a/packages/content/src/utils/__tests__/fetch-paginated.test.ts b/packages/content/src/utils/__tests__/fetch-paginated.test.ts index cd2cfb9c..aa62039c 100644 --- a/packages/content/src/utils/__tests__/fetch-paginated.test.ts +++ b/packages/content/src/utils/__tests__/fetch-paginated.test.ts @@ -1,5 +1,5 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { fetchPaginated } from "../fetch-paginated.js"; diff --git a/packages/content/src/utils/__tests__/safe-ky.test.ts b/packages/content/src/utils/__tests__/safe-ky.test.ts index 11745aab..6d4df4b6 100644 --- a/packages/content/src/utils/__tests__/safe-ky.test.ts +++ b/packages/content/src/utils/__tests__/safe-ky.test.ts @@ -1,7 +1,7 @@ -import { http, HttpResponse } from "msw"; +import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; import { ok } from "neverthrow"; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { SafeKyFetchError, SafeKyParseError, safeKy } from "../safe-ky.js"; const server = setupServer(); diff --git a/packages/content/src/utils/content-transform-utils.ts b/packages/content/src/utils/content-transform-utils.ts index ff0e219d..2cd695b2 100644 --- a/packages/content/src/utils/content-transform-utils.ts +++ b/packages/content/src/utils/content-transform-utils.ts @@ -1,6 +1,6 @@ import type { Logger } from "@bluecadet/launchpad-utils"; import chalk from "chalk"; -import { type Result, ok } from "neverthrow"; +import { ok, type Result } from "neverthrow"; import type { DataKeys, DataStore, Document } from "./data-store.js"; /** diff --git a/packages/content/src/utils/data-store.ts b/packages/content/src/utils/data-store.ts index a7af4fc4..6c68b6a0 100644 --- a/packages/content/src/utils/data-store.ts +++ b/packages/content/src/utils/data-store.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; -import path, { resolve } from "node:path"; +import path from "node:path"; import { JSONPath } from "jsonpath-plus"; -import { Result, ResultAsync, err, errAsync, ok, okAsync } from "neverthrow"; +import { err, errAsync, ok, okAsync, Result, ResultAsync } from "neverthrow"; import { z } from "zod"; import { ensureDir } from "./file-utils.js"; diff --git a/packages/content/src/utils/fetch-logger.ts b/packages/content/src/utils/fetch-logger.ts index 151a41a4..f51064c2 100644 --- a/packages/content/src/utils/fetch-logger.ts +++ b/packages/content/src/utils/fetch-logger.ts @@ -52,7 +52,7 @@ export class FetchLogger extends FixedConsoleLogger { [NO_TTY]: true, }, ); - } catch (e) { + } catch (_e) { const endTime = Date.now(); const duration = endTime - startTime; this.#fetches.get(sourceId)?.set(documentId, { state: "rejected", duration }); diff --git a/packages/content/src/utils/file-utils.ts b/packages/content/src/utils/file-utils.ts index d5821294..7ab85019 100644 --- a/packages/content/src/utils/file-utils.ts +++ b/packages/content/src/utils/file-utils.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { glob } from "glob"; -import { ResultAsync, okAsync } from "neverthrow"; +import { okAsync, ResultAsync } from "neverthrow"; export class FileUtilsError extends Error { constructor(...args: ConstructorParameters) { diff --git a/packages/content/src/utils/result-async-queue.ts b/packages/content/src/utils/result-async-queue.ts index bc58fc5a..477e8217 100644 --- a/packages/content/src/utils/result-async-queue.ts +++ b/packages/content/src/utils/result-async-queue.ts @@ -1,6 +1,6 @@ import type { Logger } from "@bluecadet/launchpad-utils"; import chalk from "chalk"; -import { Result, ResultAsync, err, ok } from "neverthrow"; +import { ok, Result, ResultAsync } from "neverthrow"; import PQueue from "p-queue"; type ResultAsyncTaskOptions = { @@ -39,10 +39,10 @@ export default class ResultAsyncQueue { tasks: Array>, options: { logger: Logger; abortOnError?: boolean }, ): ResultAsync, Array> { - let wrappedTasks = tasks; + let _wrappedTasks = tasks; if (options.abortOnError) { - wrappedTasks = tasks.map((task) => { + _wrappedTasks = tasks.map((task) => { return (...args) => task(...args).mapErr((e) => { this.queue.clear(); diff --git a/packages/content/src/utils/safe-ky.ts b/packages/content/src/utils/safe-ky.ts index 7b454625..304d7905 100644 --- a/packages/content/src/utils/safe-ky.ts +++ b/packages/content/src/utils/safe-ky.ts @@ -1,5 +1,5 @@ -import ky, { type Input, type Options, type KyResponse, type ResponsePromise } from "ky"; -import { Err, Ok, ResultAsync, err, ok } from "neverthrow"; +import ky, { type Input, type KyResponse, type Options, type ResponsePromise } from "ky"; +import { Err, Ok, ResultAsync } from "neverthrow"; export class SafeKyFetchError extends Error { constructor(...args: ConstructorParameters) { diff --git a/packages/monitor/src/core/__tests__/app-manager.test.ts b/packages/monitor/src/core/__tests__/app-manager.test.ts index 56f06d53..be1839b9 100644 --- a/packages/monitor/src/core/__tests__/app-manager.test.ts +++ b/packages/monitor/src/core/__tests__/app-manager.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; -import { ok, okAsync } from "neverthrow"; +import { okAsync } from "neverthrow"; import { describe, expect, it, vi } from "vitest"; import type { ResolvedMonitorConfig } from "../../monitor-config.js"; import { AppManager } from "../app-manager.js"; diff --git a/packages/monitor/src/core/__tests__/bus-manager.test.ts b/packages/monitor/src/core/__tests__/bus-manager.test.ts index e4185444..e04f8e89 100644 --- a/packages/monitor/src/core/__tests__/bus-manager.test.ts +++ b/packages/monitor/src/core/__tests__/bus-manager.test.ts @@ -1,6 +1,6 @@ import { createMockLogger } from "@bluecadet/launchpad-testing/test-utils.ts"; import pm2 from "pm2"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { BusManager } from "../bus-manager.js"; import { createMockSubEmitterSocket } from "./core.test-utils.js"; @@ -24,7 +24,7 @@ describe("BusManager", () => { describe("connect", () => { it("should connect to PM2 bus successfully", async () => { - const { busManager, mockSubEmitterSocket, emit } = buildTestBusManager(); + const { busManager, mockSubEmitterSocket } = buildTestBusManager(); const result = await busManager.connect(); @@ -48,7 +48,7 @@ describe("BusManager", () => { describe("disconnect", () => { it("should disconnect from PM2 bus successfully", async () => { - const { busManager, mockSubEmitterSocket, emit } = buildTestBusManager(); + const { busManager, mockSubEmitterSocket } = buildTestBusManager(); await busManager.connect(); const result = await busManager.disconnect(); diff --git a/packages/monitor/src/core/__tests__/core.test-utils.ts b/packages/monitor/src/core/__tests__/core.test-utils.ts index ba7de892..b86dcc06 100644 --- a/packages/monitor/src/core/__tests__/core.test-utils.ts +++ b/packages/monitor/src/core/__tests__/core.test-utils.ts @@ -2,7 +2,7 @@ import type { SubEmitterSocket } from "axon"; import { vi } from "vitest"; export function createMockSubEmitterSocket() { - // biome-ignore lint/suspicious/noExplicitAny: + // biome-ignore lint/suspicious/noExplicitAny: test utility const listeners: Map void)[]> = new Map(); const mockSubEmitterSocket: SubEmitterSocket = { diff --git a/packages/monitor/src/core/__tests__/process-manager.test.ts b/packages/monitor/src/core/__tests__/process-manager.test.ts index a61f536b..98a48985 100644 --- a/packages/monitor/src/core/__tests__/process-manager.test.ts +++ b/packages/monitor/src/core/__tests__/process-manager.test.ts @@ -15,12 +15,12 @@ describe("ProcessManager", () => { processManager = new ProcessManager(mockLogger); // Setup PM2 mock implementations - pm2.connect = vi.fn().mockImplementation((force, cb) => cb(null)); + pm2.connect = vi.fn().mockImplementation((_force, cb) => cb(null)); pm2.disconnect = vi.fn(); pm2.list = vi.fn().mockImplementation((cb) => cb(null, [])); - pm2.start = vi.fn().mockImplementation((options, cb) => cb(null, {})); - pm2.stop = vi.fn().mockImplementation((name, cb) => cb(null, {})); - pm2.delete = vi.fn().mockImplementation((name, cb) => cb(null, {})); + pm2.start = vi.fn().mockImplementation((_options, cb) => cb(null, {})); + pm2.stop = vi.fn().mockImplementation((_name, cb) => cb(null, {})); + pm2.delete = vi.fn().mockImplementation((_name, cb) => cb(null, {})); // @ts-ignore - this is a private api, so throws a type error pm2.Client = { // eslint-disable-next-line n/no-callback-literal diff --git a/packages/monitor/src/core/app-manager.ts b/packages/monitor/src/core/app-manager.ts index e49c9828..ec14ae2c 100644 --- a/packages/monitor/src/core/app-manager.ts +++ b/packages/monitor/src/core/app-manager.ts @@ -1,6 +1,6 @@ import path from "node:path"; import type { Logger } from "@bluecadet/launchpad-utils"; -import { type Result, ResultAsync, err, errAsync, ok, okAsync } from "neverthrow"; +import { err, errAsync, ok, okAsync, type Result, ResultAsync } from "neverthrow"; import type pm2 from "pm2"; import type { ResolvedAppConfig, ResolvedMonitorConfig } from "../monitor-config.js"; import { debounceResultAsync } from "../utils/debounce-results.js"; diff --git a/packages/monitor/src/core/bus-manager.ts b/packages/monitor/src/core/bus-manager.ts index 07823b91..4241ce65 100644 --- a/packages/monitor/src/core/bus-manager.ts +++ b/packages/monitor/src/core/bus-manager.ts @@ -1,6 +1,6 @@ -import { LogManager, type Logger } from "@bluecadet/launchpad-utils"; +import { type Logger, LogManager } from "@bluecadet/launchpad-utils"; import type { SubEmitterSocket } from "axon"; -import { type Result, ResultAsync, err, ok } from "neverthrow"; +import { err, ok, type Result, ResultAsync } from "neverthrow"; import pm2 from "pm2"; import { Tail } from "tail"; import { LogModes, type ResolvedAppConfig } from "../monitor-config.js"; @@ -202,7 +202,7 @@ export class BusManager { appLogger.info(data); } - #handleTailError(appName: string, data: string, isTailError = false) { + #handleTailError(appName: string, data: string, _isTailError = false) { const appLogger = LogManager.getLogger(appName, this.#logger); appLogger.error(data); } diff --git a/packages/monitor/src/core/monitor-plugin-driver.ts b/packages/monitor/src/core/monitor-plugin-driver.ts index ebabc2f2..e5b6e258 100644 --- a/packages/monitor/src/core/monitor-plugin-driver.ts +++ b/packages/monitor/src/core/monitor-plugin-driver.ts @@ -1,10 +1,10 @@ import { type BaseHookContext, + createPluginValidator, HookContextProvider, type Plugin, type PluginDriver, } from "@bluecadet/launchpad-utils"; -import { createPluginValidator } from "@bluecadet/launchpad-utils"; import type pm2 from "pm2"; import type LaunchpadMonitor from "../launchpad-monitor.js"; diff --git a/packages/monitor/src/core/process-manager.ts b/packages/monitor/src/core/process-manager.ts index 2d2bc538..87dfb16a 100644 --- a/packages/monitor/src/core/process-manager.ts +++ b/packages/monitor/src/core/process-manager.ts @@ -1,5 +1,5 @@ import type { Logger } from "@bluecadet/launchpad-utils"; -import { Result, ResultAsync, err, ok, okAsync } from "neverthrow"; +import { err, ok, okAsync, Result, ResultAsync } from "neverthrow"; import pm2 from "pm2"; export class ProcessManager { diff --git a/packages/monitor/src/index.ts b/packages/monitor/src/index.ts index b605a733..f87d895c 100755 --- a/packages/monitor/src/index.ts +++ b/packages/monitor/src/index.ts @@ -1,8 +1,8 @@ import LaunchpadMonitor from "./launchpad-monitor.js"; +export * from "./core/monitor-plugin-driver.js"; // export * from './windows-api.js'; // Includes optional dependencies, so not exported here export * from "./launchpad-monitor.js"; export * from "./monitor-config.js"; -export * from "./core/monitor-plugin-driver.js"; export default LaunchpadMonitor; diff --git a/packages/monitor/src/launchpad-monitor.ts b/packages/monitor/src/launchpad-monitor.ts index fa35306c..1e0cfe8f 100644 --- a/packages/monitor/src/launchpad-monitor.ts +++ b/packages/monitor/src/launchpad-monitor.ts @@ -1,8 +1,7 @@ -import { LogManager, type Logger, onExit } from "@bluecadet/launchpad-utils"; -import { PluginDriver } from "@bluecadet/launchpad-utils"; +import { type Logger, LogManager, onExit, PluginDriver } from "@bluecadet/launchpad-utils"; import autoBind from "auto-bind"; import { spawn } from "cross-spawn"; -import { ResultAsync, okAsync } from "neverthrow"; +import { okAsync, ResultAsync } from "neverthrow"; import type pm2 from "pm2"; import { AppManager } from "./core/app-manager.js"; import { BusManager } from "./core/bus-manager.js"; @@ -10,8 +9,8 @@ import { MonitorPluginDriver } from "./core/monitor-plugin-driver.js"; import { ProcessManager } from "./core/process-manager.js"; import { type MonitorConfig, - type ResolvedMonitorConfig, monitorConfigSchema, + type ResolvedMonitorConfig, } from "./monitor-config.js"; class LaunchpadMonitor { diff --git a/packages/monitor/src/utils/__tests__/debounce-results.test.ts b/packages/monitor/src/utils/__tests__/debounce-results.test.ts index b85e2977..6c16eded 100644 --- a/packages/monitor/src/utils/__tests__/debounce-results.test.ts +++ b/packages/monitor/src/utils/__tests__/debounce-results.test.ts @@ -1,4 +1,4 @@ -import { ResultAsync, err, ok } from "neverthrow"; +import { ResultAsync } from "neverthrow"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { debounceResultAsync } from "../debounce-results.js"; diff --git a/packages/scaffold/src/index.ts b/packages/scaffold/src/index.ts index c5ad1d9a..04aa85d4 100755 --- a/packages/scaffold/src/index.ts +++ b/packages/scaffold/src/index.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import { LogManager, type Logger } from "@bluecadet/launchpad-utils"; +import { type Logger, LogManager } from "@bluecadet/launchpad-utils"; import * as sudo from "sudo-prompt"; export function launchScaffold(parentLogger: Logger) { @@ -18,7 +18,7 @@ export function launchScaffold(parentLogger: Logger) { { name: "Launchpad Scaffold", }, - (error, stdout, stderr) => { + (error, stdout, _stderr) => { if (error) throw error; console.log(stdout); }, diff --git a/packages/testing/src/setup.ts b/packages/testing/src/setup.ts index d9ba656f..ad1966ab 100644 --- a/packages/testing/src/setup.ts +++ b/packages/testing/src/setup.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { fs } from "memfs"; -import { type Result, err, ok } from "neverthrow"; +import { err, ok, type Result } from "neverthrow"; import { expect, vi } from "vitest"; import type { LogEntry } from "winston"; @@ -39,11 +39,11 @@ vi.mock("pm2", () => { return { default: { list: vi.fn().mockImplementation((cb) => cb(null, [])), - start: vi.fn().mockImplementation((options, cb) => cb(null, {})), - stop: vi.fn().mockImplementation((name, cb) => cb(null, {})), - connect: vi.fn().mockImplementation((force, cb) => cb(null)), + start: vi.fn().mockImplementation((_options, cb) => cb(null, {})), + stop: vi.fn().mockImplementation((_name, cb) => cb(null, {})), + connect: vi.fn().mockImplementation((_force, cb) => cb(null)), disconnect: vi.fn().mockImplementation(() => undefined), - delete: vi.fn().mockImplementation((name, cb) => { + delete: vi.fn().mockImplementation((_name, cb) => { cb(null, {}); }), launchBus: vi.fn().mockImplementation((cb) => @@ -89,7 +89,7 @@ vi.mock("winston-daily-rotate-file", async () => { const { default: Transport } = await import("winston-transport"); class DummyTransport extends Transport { - override log(info: LogEntry) { + override log(_info: LogEntry) { // do nothing } } diff --git a/packages/utils/src/__tests__/log-manager.test.ts b/packages/utils/src/__tests__/log-manager.test.ts index e5df6692..4294ad0e 100644 --- a/packages/utils/src/__tests__/log-manager.test.ts +++ b/packages/utils/src/__tests__/log-manager.test.ts @@ -7,7 +7,7 @@ import { LogManager } from "../log-manager.js"; // we don't want to actually log anything to the console during tests const consoleLogSpy = vi .spyOn(winston.transports.Console.prototype, "log") - .mockImplementation((info, cb) => { + .mockImplementation((_info, cb) => { if (cb && typeof cb === "function") cb(); }); @@ -54,7 +54,7 @@ describe("LogManager", () => { }); it("should create child loggers with module names", () => { - const logger = LogManager.configureRootLogger(); + const _logger = LogManager.configureRootLogger(); const childLogger = LogManager.getLogger("test-module"); expect(childLogger).toBeDefined(); diff --git a/packages/utils/src/__tests__/plugin-driver.test.ts b/packages/utils/src/__tests__/plugin-driver.test.ts index 30b27d14..0a5a9555 100644 --- a/packages/utils/src/__tests__/plugin-driver.test.ts +++ b/packages/utils/src/__tests__/plugin-driver.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import PluginDriver, { HookContextProvider, PluginError } from "../plugin-driver.js"; import type { HookSet, Plugin } from "../plugin-driver.js"; +import PluginDriver, { HookContextProvider, PluginError } from "../plugin-driver.js"; import { createMockLogger } from "./test-utils.js"; describe("PluginDriver", () => { diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d46e3f69..9892b068 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,16 +1,16 @@ -export { onExit } from "./on-exit.js"; -export { LogManager, logConfigSchema } from "./log-manager.js"; -export { - default as PluginDriver, - HookContextProvider, - createPluginValidator, -} from "./plugin-driver.js"; -export type { Logger, LogConfig } from "./log-manager.js"; -export type { Plugin, HookSet, BaseHookContext } from "./plugin-driver.js"; export { FixedConsoleLogger, - TTY_ONLY, NO_TTY, TTY_FIXED, TTY_FIXED_END, + TTY_ONLY, } from "./console-transport.js"; +export type { LogConfig, Logger } from "./log-manager.js"; +export { LogManager, logConfigSchema } from "./log-manager.js"; +export { onExit } from "./on-exit.js"; +export type { BaseHookContext, HookSet, Plugin } from "./plugin-driver.js"; +export { + createPluginValidator, + default as PluginDriver, + HookContextProvider, +} from "./plugin-driver.js"; diff --git a/packages/utils/src/plugin-driver.ts b/packages/utils/src/plugin-driver.ts index 3d187a44..d9201731 100644 --- a/packages/utils/src/plugin-driver.ts +++ b/packages/utils/src/plugin-driver.ts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import { ResultAsync, okAsync } from "neverthrow"; +import { okAsync, ResultAsync } from "neverthrow"; import { z } from "zod"; import type { Logger } from "./log-manager.js"; import { onExit } from "./on-exit.js"; @@ -190,11 +190,11 @@ export class HookContextProvider { return this.#innerDriver.plugins; } - protected _initialize(plugins: ReadonlyArray>): void { + protected _initialize(_plugins: ReadonlyArray>): void { // implement in subclass } - protected _getPluginContext(plugin: Plugin): C { + protected _getPluginContext(_plugin: Plugin): C { throw new Error("_getPluginContext Not implemented"); } From 967c9163530d7302d54ebe324a860e264f1a0489 Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:41:15 -0400 Subject: [PATCH 7/8] try fix line endings --- .github/workflows/ci.yml | 112 ++-- CONTRIBUTING.md | 90 +-- packages/monitor/README.md | 82 +-- .../scaffold/scripts/vendor/powerplan.psm1 | 520 +++++++++--------- packages/utils/README.md | 6 +- 5 files changed, 405 insertions(+), 405 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c74e8a7f..b871b419 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,57 +1,57 @@ -name: Main - -on: - pull_request: - push: - branches: - - main - - develop - paths: - - "packages/**" - - "docs/**" - merge_group: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read # to fetch code (actions/checkout) - -env: - FORCE_COLOR: 3 - -jobs: - - ci: - name: CI - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - node: [18, 24] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: lint - run: npm run lint - - - name: lint dependency versions - # ignore internal launchpad dependency mismatches. These versions - # are handled by changesets. - run: npx sherif -i @bluecadet/launchpad* - - - name: Validate types - run: npm run build - - - name: Run tests +name: Main + +on: + pull_request: + push: + branches: + - main + - develop + paths: + - "packages/**" + - "docs/**" + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + FORCE_COLOR: 3 + +jobs: + + ci: + name: CI + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + node: [18, 24] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: lint + run: npm run lint + + - name: lint dependency versions + # ignore internal launchpad dependency mismatches. These versions + # are handled by changesets. + run: npx sherif -i @bluecadet/launchpad* + + - name: Validate types + run: npm run build + + - name: Run tests run: npm run test \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6628a357..54387d24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,45 +1,45 @@ -# Contributing to Launchpad - -## Generating Changelogs - -If you are contributing a user-facing or noteworthy change to Launchpad that should be added to the changelog, you should include a changeset with your PR. - -To add a changeset, run this script locally: - -``` -npm run changeset -``` - -Follow the prompts to select which package(s) are affected by your change, and whether the change is a major, minor or patch change. This will create a file in the `.changesets` directory of the repo. This change should be committed and included with your PR. - -Considerations: - -- A changeset is required to trigger the versioning/publishing workflow. -- Non-packages, like examples and tests, do not need changesets. -- You can use markdown in your changeset to include code examples, headings, and more. However, please use plain text for the first line of your changeset. The formatting of the GitHub release notes does not support headings as the first line of the changeset. - -## Releases - -The [Changesets GitHub action](https://github.com/changesets/action#with-publishing) will create and update a PR that applies changesets and publishes new versions of changed packages to npm. - -To release a new version of Launchpad, find the `Version Packages` PR, read it over, and merge it. - -The `main` branch is kept up to date with the latest releases. - -## Testing Launchpad - -If you want to test Launchpad as a local dependency and frequently make changes, then the best way to do that is to clone launchpad and link `npm @bluecadet/launchpad` in your local project. - -For example: - -```bat -git clone git@github.com:bluecadet/launchpad.git -cd launchpad -npm i -cd packages/launchpad -npm link - -cd ../../my-test-project -@REM If needed: npm init -npm link @bluecadet/launchpad --save -``` +# Contributing to Launchpad + +## Generating Changelogs + +If you are contributing a user-facing or noteworthy change to Launchpad that should be added to the changelog, you should include a changeset with your PR. + +To add a changeset, run this script locally: + +``` +npm run changeset +``` + +Follow the prompts to select which package(s) are affected by your change, and whether the change is a major, minor or patch change. This will create a file in the `.changesets` directory of the repo. This change should be committed and included with your PR. + +Considerations: + +- A changeset is required to trigger the versioning/publishing workflow. +- Non-packages, like examples and tests, do not need changesets. +- You can use markdown in your changeset to include code examples, headings, and more. However, please use plain text for the first line of your changeset. The formatting of the GitHub release notes does not support headings as the first line of the changeset. + +## Releases + +The [Changesets GitHub action](https://github.com/changesets/action#with-publishing) will create and update a PR that applies changesets and publishes new versions of changed packages to npm. + +To release a new version of Launchpad, find the `Version Packages` PR, read it over, and merge it. + +The `main` branch is kept up to date with the latest releases. + +## Testing Launchpad + +If you want to test Launchpad as a local dependency and frequently make changes, then the best way to do that is to clone launchpad and link `npm @bluecadet/launchpad` in your local project. + +For example: + +```bat +git clone git@github.com:bluecadet/launchpad.git +cd launchpad +npm i +cd packages/launchpad +npm link + +cd ../../my-test-project +@REM If needed: npm init +npm link @bluecadet/launchpad --save +``` diff --git a/packages/monitor/README.md b/packages/monitor/README.md index dbfad4d4..7135fa83 100644 --- a/packages/monitor/README.md +++ b/packages/monitor/README.md @@ -1,41 +1,41 @@ -# @bluecadet/launchpad-monitor - -Process monitoring and management for interactive installations. Part of the Launchpad suite of tools. - -## Documentation - -For complete documentation, examples, and API reference, visit: - - -## Features - -- Process management via PM2 -- Plugin system for custom monitoring behavior -- Process lifecycle hooks -- Built-in logging and error handling -- Window management capabilities - -## Installation - -```bash -npm install @bluecadet/launchpad-monitor -``` - -## Basic Usage - -```typescript -import { Monitor } from '@bluecadet/launchpad-monitor'; - -const monitor = new Monitor({ - apps: [{ - name: 'my-app', - script: 'app.js' - }] -}); - -await monitor.start(); -``` - -## License - -MIT © Bluecadet +# @bluecadet/launchpad-monitor + +Process monitoring and management for interactive installations. Part of the Launchpad suite of tools. + +## Documentation + +For complete documentation, examples, and API reference, visit: + + +## Features + +- Process management via PM2 +- Plugin system for custom monitoring behavior +- Process lifecycle hooks +- Built-in logging and error handling +- Window management capabilities + +## Installation + +```bash +npm install @bluecadet/launchpad-monitor +``` + +## Basic Usage + +```typescript +import { Monitor } from '@bluecadet/launchpad-monitor'; + +const monitor = new Monitor({ + apps: [{ + name: 'my-app', + script: 'app.js' + }] +}); + +await monitor.start(); +``` + +## License + +MIT © Bluecadet diff --git a/packages/scaffold/scripts/vendor/powerplan.psm1 b/packages/scaffold/scripts/vendor/powerplan.psm1 index e91d9741..686c9dd3 100644 --- a/packages/scaffold/scripts/vendor/powerplan.psm1 +++ b/packages/scaffold/scripts/vendor/powerplan.psm1 @@ -1,261 +1,261 @@ -# See https://github.com/torgro/PowerPlan - -function Get-Powerplan -{ -<# -.Synopsis - Get a Powerplan by name or all of them -.DESCRIPTION - This cmdlet queries the CIM class Win32_PowerPlan. See also Set-PowerPlan cmdlet -.EXAMPLE - Get-Powerplan - This command will output all powerplans: -Caption : -Description : Automatically balances performance with energy consumption on capable hardware. -ElementName : Balanced -InstanceID : Microsoft:PowerPlan\{381b4222-f694-41f0-9685-ff5bb260df2e} -IsActive : False -PSComputerName : -Caption : -Description : Favors performance, but may use more energy. -ElementName : High performance -InstanceID : Microsoft:PowerPlan\{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c} -IsActive : True -PSComputerName : -Caption : -Description : Saves energy by reducing your computer’s performance where possible. -ElementName : Power saver -InstanceID : Microsoft:PowerPlan\{a1841308-3541-4fab-bc81-f71556f20b4a} -IsActive : False -PSComputerName : -.EXAMPLE - Get-Powerplan -PlanName high* - This command will output all plans that begins with high -Caption : -Description : Favors performance, but may use more energy. -ElementName : High performance -InstanceID : Microsoft:PowerPlan\{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c} -IsActive : True -PSComputerName : -.EXAMPLE - Get-PowerPlan -PlanName high* -ComputerName "Server1","Server2" - Will output the powerplan with name like high for server1 and server2 - -.EXAMPLE - Get-PowerPlan -Active - Will output the active powerplan -.OUTPUTS - CimInstance -.NOTES - Powerplan and performance -.COMPONENT - Powerplan -.ROLE - Powerplan -.FUNCTIONALITY - This cmdlet queries the CIM class Win32_PowerPlan -#> -[cmdletbinding()] -[OutputType([CimInstance[]])] -Param( - [Parameter( - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - ValueFromRemainingArguments=$false - )] - [Alias("ElementName")] - [string]$PlanName = "*" - , - [Parameter( - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - ValueFromRemainingArguments=$false - )] - [string[]]$ComputerName, - [switch]$Active -) - - Begin - { - $f = $MyInvocation.InvocationName - Write-Verbose -Message "$f - START" - - $GetCimInstance = @{ - Namespace = "root\cimv2\power" - ClassName = "Win32_PowerPlan" - } - - if ($ComputerName) - { - $GetCimInstance.Add("ComputerName",$ComputerName) - } - - if ($Active) - { - $GetCimInstance.Add("Filter",'IsActive="True"') - } - } - - Process - { - if ($PlanName) - { - Get-CimInstance @GetCimInstance | Where-Object ElementName -Like "$PlanName" - } - else - { - Get-CimInstance @GetCimInstance - } - } - - End - { - Write-Verbose -Message "$f - END" - } -} - -function Set-PowerPlan -{ -<# -.Synopsis - Sets a Powerplan by name or by value provided from the pipeline -.DESCRIPTION - This cmdlet invokes the CIM-method Activate in class Win32_PowerPlan. See also Get-PowerPlan cmdlet -.EXAMPLE - Set-PowerPlan -PlanName high* - This will set the current powerplan to High for the current computer -.EXAMPLE - Get-Powerplan -PlanName "Power Saver" | Set-PowerPlan - Will set the powerplan to "Power Saver" for current computer -.EXAMPLE - Get-Powerplan -PlanName "Power Saver" -ComputerName "Server1","Server2" | Set-PowerPlan - This will set the current powerpla to "Power Saver" for the computers Server1 and Server2 -.EXAMPLE - Set-PowerPlan -PlanName "Power Saver" -ComputerName "Server1","Server2" - This will set the current powerpla to "Power Saver" for the computers Server1 and Server2 -.NOTES - Powerplan and performance -.COMPONENT - Powerplan -.ROLE - Powerplan -.FUNCTIONALITY - This cmdlet invokes CIM-methods in the class Win32_PowerPlan -#> -[cmdletbinding( - SupportsShouldProcess=$true, - ConfirmImpact='Medium' -)] -Param( - [Parameter( - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - ValueFromRemainingArguments=$false - )] - [Alias("ElementName")] - [string]$PlanName = "*" - , - [Parameter( - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - ValueFromRemainingArguments=$false - )] - [Alias("PSComputerName")] - [string[]]$ComputerName -) - - Begin - { - $f = $MyInvocation.InvocationName - Write-Verbose -Message "$f - START" - $GetCimInstance = @{ - Namespace = "root\cimv2\power" - ClassName = "Win32_PowerPlan" - } - - if ($ComputerName) - { - $GetCimInstance.Add("ComputerName",$ComputerName) - } - - $InvokeCimMethod = @{ - MethodName = "Activate" - } - - if ($WhatIfPreference) - { - $InvokeCimMethod.Add("WhatIf",$true) - } - } - - Process - { - Write-Verbose -Message "$f - ElementName=$PlanName" - $CimObjectPowerPlan = Get-CimInstance @GetCimInstance | Where-Object ElementName -like "$PlanName" - - foreach ($Instance in $CimObjectPowerPlan) - { - if ($pscmdlet.ShouldProcess($Instance)) - { - $null = Invoke-CimMethod -InputObject $Instance @InvokeCimMethod - } - } - if (-not $CimObjectPowerPlan) - { - Write-Warning -Message "Unable to find powerplan $PlanName" - } - } - - End - { - Write-Verbose -Message "$f - END" - } - -} - -<# - DSC Resource - Manages the power plan selection for a computer. -#> -[DscResource()] -class PowerPlan -{ - - <# - This property is the name of an available power plan. - #> - [DscProperty(Key)] - [string]$Name - - <# - Sets the specified power plan as active. - #> - [void] Set() - { - Set-PowerPlan $this.Name - } - - <# - Tests if the machine is using the specified power plan. - #> - [bool] Test() - { - if ((Get-PowerPlan -Active).ElementName -eq $this.Name) - { - return $true - } - else - { - return $false - } - } - - <# - Returns an instance of this class to identify the active plan. - #> - [PowerPlan] Get() - { - $this.Name = (Get-PowerPlan -Active).ElementName - return $this - } +# See https://github.com/torgro/PowerPlan + +function Get-Powerplan +{ +<# +.Synopsis + Get a Powerplan by name or all of them +.DESCRIPTION + This cmdlet queries the CIM class Win32_PowerPlan. See also Set-PowerPlan cmdlet +.EXAMPLE + Get-Powerplan + This command will output all powerplans: +Caption : +Description : Automatically balances performance with energy consumption on capable hardware. +ElementName : Balanced +InstanceID : Microsoft:PowerPlan\{381b4222-f694-41f0-9685-ff5bb260df2e} +IsActive : False +PSComputerName : +Caption : +Description : Favors performance, but may use more energy. +ElementName : High performance +InstanceID : Microsoft:PowerPlan\{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c} +IsActive : True +PSComputerName : +Caption : +Description : Saves energy by reducing your computer’s performance where possible. +ElementName : Power saver +InstanceID : Microsoft:PowerPlan\{a1841308-3541-4fab-bc81-f71556f20b4a} +IsActive : False +PSComputerName : +.EXAMPLE + Get-Powerplan -PlanName high* + This command will output all plans that begins with high +Caption : +Description : Favors performance, but may use more energy. +ElementName : High performance +InstanceID : Microsoft:PowerPlan\{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c} +IsActive : True +PSComputerName : +.EXAMPLE + Get-PowerPlan -PlanName high* -ComputerName "Server1","Server2" + Will output the powerplan with name like high for server1 and server2 + +.EXAMPLE + Get-PowerPlan -Active + Will output the active powerplan +.OUTPUTS + CimInstance +.NOTES + Powerplan and performance +.COMPONENT + Powerplan +.ROLE + Powerplan +.FUNCTIONALITY + This cmdlet queries the CIM class Win32_PowerPlan +#> +[cmdletbinding()] +[OutputType([CimInstance[]])] +Param( + [Parameter( + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + ValueFromRemainingArguments=$false + )] + [Alias("ElementName")] + [string]$PlanName = "*" + , + [Parameter( + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + ValueFromRemainingArguments=$false + )] + [string[]]$ComputerName, + [switch]$Active +) + + Begin + { + $f = $MyInvocation.InvocationName + Write-Verbose -Message "$f - START" + + $GetCimInstance = @{ + Namespace = "root\cimv2\power" + ClassName = "Win32_PowerPlan" + } + + if ($ComputerName) + { + $GetCimInstance.Add("ComputerName",$ComputerName) + } + + if ($Active) + { + $GetCimInstance.Add("Filter",'IsActive="True"') + } + } + + Process + { + if ($PlanName) + { + Get-CimInstance @GetCimInstance | Where-Object ElementName -Like "$PlanName" + } + else + { + Get-CimInstance @GetCimInstance + } + } + + End + { + Write-Verbose -Message "$f - END" + } +} + +function Set-PowerPlan +{ +<# +.Synopsis + Sets a Powerplan by name or by value provided from the pipeline +.DESCRIPTION + This cmdlet invokes the CIM-method Activate in class Win32_PowerPlan. See also Get-PowerPlan cmdlet +.EXAMPLE + Set-PowerPlan -PlanName high* + This will set the current powerplan to High for the current computer +.EXAMPLE + Get-Powerplan -PlanName "Power Saver" | Set-PowerPlan + Will set the powerplan to "Power Saver" for current computer +.EXAMPLE + Get-Powerplan -PlanName "Power Saver" -ComputerName "Server1","Server2" | Set-PowerPlan + This will set the current powerpla to "Power Saver" for the computers Server1 and Server2 +.EXAMPLE + Set-PowerPlan -PlanName "Power Saver" -ComputerName "Server1","Server2" + This will set the current powerpla to "Power Saver" for the computers Server1 and Server2 +.NOTES + Powerplan and performance +.COMPONENT + Powerplan +.ROLE + Powerplan +.FUNCTIONALITY + This cmdlet invokes CIM-methods in the class Win32_PowerPlan +#> +[cmdletbinding( + SupportsShouldProcess=$true, + ConfirmImpact='Medium' +)] +Param( + [Parameter( + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + ValueFromRemainingArguments=$false + )] + [Alias("ElementName")] + [string]$PlanName = "*" + , + [Parameter( + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + ValueFromRemainingArguments=$false + )] + [Alias("PSComputerName")] + [string[]]$ComputerName +) + + Begin + { + $f = $MyInvocation.InvocationName + Write-Verbose -Message "$f - START" + $GetCimInstance = @{ + Namespace = "root\cimv2\power" + ClassName = "Win32_PowerPlan" + } + + if ($ComputerName) + { + $GetCimInstance.Add("ComputerName",$ComputerName) + } + + $InvokeCimMethod = @{ + MethodName = "Activate" + } + + if ($WhatIfPreference) + { + $InvokeCimMethod.Add("WhatIf",$true) + } + } + + Process + { + Write-Verbose -Message "$f - ElementName=$PlanName" + $CimObjectPowerPlan = Get-CimInstance @GetCimInstance | Where-Object ElementName -like "$PlanName" + + foreach ($Instance in $CimObjectPowerPlan) + { + if ($pscmdlet.ShouldProcess($Instance)) + { + $null = Invoke-CimMethod -InputObject $Instance @InvokeCimMethod + } + } + if (-not $CimObjectPowerPlan) + { + Write-Warning -Message "Unable to find powerplan $PlanName" + } + } + + End + { + Write-Verbose -Message "$f - END" + } + +} + +<# + DSC Resource + Manages the power plan selection for a computer. +#> +[DscResource()] +class PowerPlan +{ + + <# + This property is the name of an available power plan. + #> + [DscProperty(Key)] + [string]$Name + + <# + Sets the specified power plan as active. + #> + [void] Set() + { + Set-PowerPlan $this.Name + } + + <# + Tests if the machine is using the specified power plan. + #> + [bool] Test() + { + if ((Get-PowerPlan -Active).ElementName -eq $this.Name) + { + return $true + } + else + { + return $false + } + } + + <# + Returns an instance of this class to identify the active plan. + #> + [PowerPlan] Get() + { + $this.Name = (Get-PowerPlan -Active).ElementName + return $this + } } \ No newline at end of file diff --git a/packages/utils/README.md b/packages/utils/README.md index 6b5cdfdf..219cbdf0 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -1,3 +1,3 @@ -# Launchpad Utils - -Collection of utils used across [@bluecadet/launchpad](https://www.npmjs.com/package/@bluecadet/launchpad) packages. +# Launchpad Utils + +Collection of utils used across [@bluecadet/launchpad](https://www.npmjs.com/package/@bluecadet/launchpad) packages. From 0c6ec6578270048602b3805787fe7d9bbc99c38e Mon Sep 17 00:00:00 2001 From: Clay Tercek <30105080+claytercek@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:50:45 -0400 Subject: [PATCH 8/8] try explicit eol=lf --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 21256661..7f80b853 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* text=auto \ No newline at end of file +* text=auto +* text eol=lf \ No newline at end of file