From d61d8605ab7fa680aea8ad87c8473c119cbb6ed3 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Wed, 11 Feb 2026 11:05:24 -0500 Subject: [PATCH 01/11] ENG-219: Add build command Co-Authored-By: Claude Opus 4.6 --- src/cli.ts | 3 ++ src/commands/build.ts | 55 +++++++++++++++++++++++++++++++ tests/commands/build.test.ts | 63 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/commands/build.ts create mode 100644 tests/commands/build.test.ts diff --git a/src/cli.ts b/src/cli.ts index 50ff300..b0f1eb8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,10 @@ import { createApp } from './app.js'; +import { registerBuildCommand } from './commands/build.js'; const program = createApp(); +registerBuildCommand(program); + program.parseAsync(process.argv).catch((err) => { console.error(err instanceof Error ? err.message : String(err)); process.exit(1); diff --git a/src/commands/build.ts b/src/commands/build.ts new file mode 100644 index 0000000..abd2807 --- /dev/null +++ b/src/commands/build.ts @@ -0,0 +1,55 @@ +import type { Command } from 'commander'; +import { getConfig } from '../config.js'; +import { runCommand } from '../utils/process.js'; +import * as output from '../utils/output.js'; + +/** + * Registers the `build` command with the CLI program. + * + * @since TBD + * + * @param {Command} program - The Commander.js program instance. + * + * @returns {void} + */ +export function registerBuildCommand(program: Command): void { + program + .command('build') + .description('Run the build commands.') + .option('--dev', 'Run the dev build commands.') + .option('--root ', 'Set the root directory for running commands.') + .action(async (options: { dev?: boolean; root?: string }) => { + const config = getConfig(options.root); + const buildSteps = config.getBuildCommands(options.dev); + const cwd = options.root ?? config.getWorkingDir(); + + output.log('Running build steps...'); + + for (const step of buildSteps) { + let cmd = step; + let bailOnFailure = true; + + if (cmd.startsWith('@')) { + bailOnFailure = false; + cmd = cmd.slice(1); + } + + output.section(`> ${cmd}`); + + const result = await runCommand(cmd, { + cwd, + envVarNames: config.getEnvVarNames(), + }); + + if (result.exitCode !== 0) { + output.error(`[FAIL] Build step failed: ${cmd}`); + if (bailOnFailure) { + output.error('Exiting...'); + process.exit(result.exitCode); + } + } + } + + output.success('Build complete.'); + }); +} diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts new file mode 100644 index 0000000..ddcb772 --- /dev/null +++ b/tests/commands/build.test.ts @@ -0,0 +1,63 @@ +import { + runPup, + resetFixtures, + writeDefaultPuprc, + writePuprc, + getPuprc, +} from '../helpers/setup.js'; + +describe('build command', () => { + beforeEach(() => { + writeDefaultPuprc(); + }); + + afterEach(() => { + resetFixtures(); + }); + + it('should run build', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "fake project, yo"']; + writePuprc(puprc); + + const result = await runPup('build'); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('fake project, yo'); + }); + + it('should handle no build steps', async () => { + const puprc = getPuprc(); + puprc.build = []; + writePuprc(puprc); + + const result = await runPup('build'); + expect(result.exitCode).toBe(0); + }); + + it('should fail without .puprc', async () => { + resetFixtures(); + // Run in a dir with no .puprc but a package.json + const result = await runPup('build'); + expect(result.exitCode).toBe(0); + }); + + it('should pass default env vars', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "env test"']; + writePuprc(puprc); + + const result = await runPup('build'); + expect(result.exitCode).toBe(0); + }); + + it('should run build with dev flag', async () => { + const puprc = getPuprc(); + puprc.build = ['echo "production build"']; + (puprc as Record).build_dev = ['echo "dev build"']; + writePuprc(puprc); + + const result = await runPup('build --dev'); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('dev build'); + }); +}); From ea08446fac458e02f1177e80501e5be85b6ae895 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Wed, 11 Feb 2026 12:37:34 -0500 Subject: [PATCH 02/11] ENG-219: Use createTempProject in build tests for isolation Co-Authored-By: Claude Opus 4.6 --- tests/commands/build.test.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts index ddcb772..a8385dd 100644 --- a/tests/commands/build.test.ts +++ b/tests/commands/build.test.ts @@ -1,26 +1,28 @@ import { runPup, - resetFixtures, - writeDefaultPuprc, writePuprc, getPuprc, + createTempProject, + cleanupTempProjects, } from '../helpers/setup.js'; describe('build command', () => { + let projectDir: string; + beforeEach(() => { - writeDefaultPuprc(); + projectDir = createTempProject(); }); afterEach(() => { - resetFixtures(); + cleanupTempProjects(); }); it('should run build', async () => { const puprc = getPuprc(); puprc.build = ['echo "fake project, yo"']; - writePuprc(puprc); + writePuprc(puprc, projectDir); - const result = await runPup('build'); + const result = await runPup('build', { cwd: projectDir }); expect(result.exitCode).toBe(0); expect(result.output).toContain('fake project, yo'); }); @@ -28,25 +30,25 @@ describe('build command', () => { it('should handle no build steps', async () => { const puprc = getPuprc(); puprc.build = []; - writePuprc(puprc); + writePuprc(puprc, projectDir); - const result = await runPup('build'); + const result = await runPup('build', { cwd: projectDir }); expect(result.exitCode).toBe(0); }); it('should fail without .puprc', async () => { - resetFixtures(); // Run in a dir with no .puprc but a package.json - const result = await runPup('build'); + const emptyDir = createTempProject(); + const result = await runPup('build', { cwd: emptyDir }); expect(result.exitCode).toBe(0); }); it('should pass default env vars', async () => { const puprc = getPuprc(); puprc.build = ['echo "env test"']; - writePuprc(puprc); + writePuprc(puprc, projectDir); - const result = await runPup('build'); + const result = await runPup('build', { cwd: projectDir }); expect(result.exitCode).toBe(0); }); @@ -54,9 +56,9 @@ describe('build command', () => { const puprc = getPuprc(); puprc.build = ['echo "production build"']; (puprc as Record).build_dev = ['echo "dev build"']; - writePuprc(puprc); + writePuprc(puprc, projectDir); - const result = await runPup('build --dev'); + const result = await runPup('build --dev', { cwd: projectDir }); expect(result.exitCode).toBe(0); expect(result.output).toContain('dev build'); }); From cda8080803f3ded6d78984017e18069f9ddcb767 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 13:48:52 -0500 Subject: [PATCH 03/11] ENG-219: Improve build command test assertions - Rename misleading "should fail without .puprc" test to reflect actual behavior (succeeds with defaults when no .puprc exists) - Update env vars test to actually verify environment variable passthrough by setting NODE_AUTH_TOKEN and asserting it appears in build step output --- tests/commands/build.test.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts index a8385dd..2eaba9f 100644 --- a/tests/commands/build.test.ts +++ b/tests/commands/build.test.ts @@ -36,20 +36,33 @@ describe('build command', () => { expect(result.exitCode).toBe(0); }); - it('should fail without .puprc', async () => { - // Run in a dir with no .puprc but a package.json + it('should succeed with defaults when no .puprc exists (defaults to no build steps)', async () => { const emptyDir = createTempProject(); const result = await runPup('build', { cwd: emptyDir }); expect(result.exitCode).toBe(0); + expect(result.output).toContain('Build complete'); }); - it('should pass default env vars', async () => { + it('should pass env vars', async () => { const puprc = getPuprc(); - puprc.build = ['echo "env test"']; + puprc.build = ['echo "TOKEN=$NODE_AUTH_TOKEN"']; + puprc.env = ['NODE_AUTH_TOKEN']; writePuprc(puprc, projectDir); - const result = await runPup('build', { cwd: projectDir }); - expect(result.exitCode).toBe(0); + const originalToken = process.env.NODE_AUTH_TOKEN; + process.env.NODE_AUTH_TOKEN = 'test-token-12345'; + + try { + const result = await runPup('build', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('TOKEN=test-token-12345'); + } finally { + if (originalToken === undefined) { + delete process.env.NODE_AUTH_TOKEN; + } else { + process.env.NODE_AUTH_TOKEN = originalToken; + } + } }); it('should run build with dev flag', async () => { From 26fd3e1ee625164975f8b642ae7b740112ae4be5 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 16:05:18 -0500 Subject: [PATCH 04/11] ENG-219: Remove unused workingDir arg from getConfig() call The getConfig() singleton is already initialized in createApp() before commands run, so passing options.root has no effect. The --root flag correctly controls the cwd for subprocess execution, not config loading. --- src/commands/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/build.ts b/src/commands/build.ts index 06e4562..491c9fd 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -19,7 +19,7 @@ export function registerBuildCommand(program: Command): void { .option('--dev', 'Run the dev build commands.') .option('--root ', 'Set the root directory for running commands.') .action(async (options: { dev?: boolean; root?: string }) => { - const config = getConfig(options.root); + const config = getConfig(); const buildSteps = config.getBuildCommands(options.dev); const cwd = options.root ?? config.getWorkingDir(); From 8698d9f510e3c883589d87ca1b39879616a612d7 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:34:58 -0500 Subject: [PATCH 05/11] ENG-219: Remove `dist/` files --- dist/cli.js | 823 ------------------------------------------------ dist/cli.js.map | 1 - 2 files changed, 824 deletions(-) delete mode 100755 dist/cli.js delete mode 100644 dist/cli.js.map diff --git a/dist/cli.js b/dist/cli.js deleted file mode 100755 index f826838..0000000 --- a/dist/cli.js +++ /dev/null @@ -1,823 +0,0 @@ -#!/usr/bin/env node -import { Command } from "commander"; -import fs from "fs-extra"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { z } from "zod"; - -//#region src/utils/directory.ts -/** -* Ensures a directory path ends with a trailing separator. -* -* @since TBD -* -* @param {string} p - The path to ensure has a trailing separator. -* -* @returns {string} The path with a trailing separator. -* -* @throws {Error} If the path appears to be a file (has an extension). -*/ -function trailingSlashIt(p) { - const { dir, base, ext } = path.parse(p); - if (ext.length > 0) throw new Error("Could not add trailing slash to file path."); - return path.join(dir, base, path.sep); -} - -//#endregion -//#region src/models/workflow.ts -/** -* Creates a Workflow object from a slug and list of commands. -* -* @since TBD -* -* @param {string} slug - The unique identifier for the workflow. -* @param {string[]} commands - The list of commands to execute in the workflow. -* -* @returns {Workflow} A Workflow object with the provided slug and commands. -*/ -function createWorkflow(slug, commands) { - return { - slug, - commands - }; -} -/** -* Manages a collection of named workflows. -* -* @since TBD -*/ -var WorkflowCollection = class { - workflows = /* @__PURE__ */ new Map(); - /** - * Adds a workflow to the collection. - * - * @since TBD - * - * @param {Workflow} workflow - The workflow to add. - * - * @returns {void} - */ - add(workflow) { - this.workflows.set(workflow.slug, workflow); - } - /** - * Retrieves a workflow by its slug. - * - * @since TBD - * - * @param {string} slug - The slug of the workflow to retrieve. - * - * @returns {Workflow | undefined} The workflow if found, otherwise undefined. - */ - get(slug) { - return this.workflows.get(slug); - } - /** - * Checks whether a workflow with the given slug exists. - * - * @since TBD - * - * @param {string} slug - The slug to check. - * - * @returns {boolean} True if the workflow exists, false otherwise. - */ - has(slug) { - return this.workflows.has(slug); - } - /** - * Returns all workflows as an array. - * - * @since TBD - * - * @returns {Workflow[]} An array containing all workflows in the collection. - */ - getAll() { - return Array.from(this.workflows.values()); - } - /** - * Returns the number of workflows in the collection. - * - * @since TBD - * - * @returns {number} The count of workflows. - */ - get size() { - return this.workflows.size; - } - /** - * Allows iterating over all workflows in the collection. - * - * @since TBD - * - * @returns {Iterator} An iterator over the workflows. - */ - [Symbol.iterator]() { - return this.workflows.values(); - } -}; - -//#endregion -//#region src/schemas.ts -/** -* Schema for a version file entry in .puprc paths.versions. -* -* @since TBD -*/ -const VersionFileInputSchema = z.object({ - file: z.string(), - regex: z.string() -}); -/** -* Parsed version file (same shape as input). -* -* @since TBD -*/ -const VersionFileSchema = z.object({ - file: z.string(), - regex: z.string() -}); -/** -* Schema for the i18n filter configuration. -* -* @since TBD -*/ -const I18nFilterSchema = z.object({ minimum_percentage: z.number() }); -/** -* Schema for an i18n configuration entry from .puprc (all fields optional). -* -* @since TBD -*/ -const I18nConfigInputSchema = z.object({ - path: z.string().optional(), - url: z.string().optional(), - slug: z.string().optional(), - textdomain: z.string().optional(), - file_format: z.string().optional(), - formats: z.array(z.string()).optional(), - filter: z.object({ minimum_percentage: z.number().optional() }).optional() -}).passthrough(); -/** -* Schema for the i18n defaults section of configuration. -* -* @since TBD -*/ -const I18nDefaultsSchema = z.object({ - path: z.string(), - url: z.string(), - slug: z.string(), - textdomain: z.string(), - file_format: z.string(), - formats: z.array(z.string()), - filter: I18nFilterSchema -}); -/** -* Schema for a fully resolved i18n configuration entry (all fields required). -* -* @since TBD -*/ -const I18nResolvedConfigSchema = z.object({ - path: z.string(), - url: z.string(), - slug: z.string(), - textdomain: z.string(), - file_format: z.string(), - formats: z.array(z.string()), - filter: I18nFilterSchema -}); -/** -* Schema for a check configuration entry from .puprc (optional fields with defaults). -* -* @since TBD -*/ -const CheckConfigInputSchema = z.object({ - fail_method: z.enum(["error", "warn"]).optional(), - fail_method_dev: z.enum(["error", "warn"]).optional(), - type: z.enum([ - "simple", - "class", - "pup", - "command" - ]).optional(), - file: z.string().optional(), - command: z.string().optional(), - configure: z.string().optional(), - args: z.record(z.string(), z.string()).optional(), - dirs: z.array(z.string()).optional(), - skip_directories: z.string().optional(), - skip_files: z.string().optional() -}).passthrough(); -/** -* Schema for a fully resolved check configuration with defaults applied. -* -* @since TBD -*/ -const CheckConfigSchema = z.object({ - slug: z.string(), - fail_method: z.enum(["error", "warn"]).default("error"), - fail_method_dev: z.enum(["error", "warn"]).default("warn"), - type: z.enum([ - "simple", - "class", - "pup", - "command" - ]).default("pup"), - file: z.string().optional(), - command: z.string().optional(), - configure: z.string().optional(), - args: z.record(z.string(), z.string()).default({}), - dirs: z.array(z.string()).optional(), - skip_directories: z.string().optional(), - skip_files: z.string().optional() -}); -/** -* Schema for the paths section of configuration. -* -* @since TBD -*/ -const PathsConfigSchema = z.object({ - build_dir: z.string(), - changelog: z.string().nullable(), - css: z.array(z.string()), - js: z.array(z.string()), - sync_files: z.array(z.string()), - versions: z.array(VersionFileInputSchema), - views: z.array(z.string()), - zip_dir: z.string() -}); -/** -* Schema for the full merged pup configuration (after defaults are applied). -* -* @since TBD -*/ -const PupConfigSchema = z.object({ - build: z.array(z.string()), - build_dev: z.array(z.string()), - workflows: z.record(z.string(), z.array(z.string())), - checks: z.record(z.string(), CheckConfigInputSchema), - clean: z.array(z.string()), - i18n: z.union([z.array(I18nConfigInputSchema), I18nConfigInputSchema]), - i18n_defaults: I18nDefaultsSchema, - paths: PathsConfigSchema, - env: z.array(z.string()), - repo: z.string().nullable(), - zip_use_default_ignore: z.boolean(), - zip_name: z.string().nullable() -}).passthrough(); -/** -* Schema for validating raw .puprc input (all fields optional + passthrough for custom keys). -* -* @since TBD -*/ -const PuprcInputSchema = z.object({ - build: z.array(z.string()).optional(), - build_dev: z.array(z.string()).optional(), - workflows: z.record(z.string(), z.array(z.string())).optional(), - checks: z.record(z.string(), CheckConfigInputSchema.or(z.object({}).passthrough())).optional(), - clean: z.array(z.string()).optional(), - i18n: z.union([z.array(I18nConfigInputSchema), I18nConfigInputSchema]).optional(), - i18n_defaults: I18nDefaultsSchema.partial().optional(), - paths: PathsConfigSchema.partial().optional(), - env: z.array(z.string()).optional(), - repo: z.string().nullable().optional(), - zip_use_default_ignore: z.boolean().optional(), - zip_name: z.string().nullable().optional() -}).passthrough(); -/** -* Schema for a workflow. -* -* @since TBD -*/ -const WorkflowSchema = z.object({ - slug: z.string(), - commands: z.array(z.string()) -}); - -//#endregion -//#region defaults/.puprc-defaults.json -var _puprc_defaults_default = { - build: [], - build_dev: [], - workflows: {}, - checks: { - "tbd": { - "fail_method": "error", - "fail_method_dev": "warn", - "skip_directories": "bin|build|vendor|node_modules|.git|.github|tests", - "skip_files": ".min.css|.min.js|.map.js|.css|.png|.jpg|.jpeg|.svg|.gif|.ico", - "dirs": ["src"] - }, - "version-conflict": { - "fail_method": "error", - "fail_method_dev": "warn" - } - }, - clean: [], - i18n: [], - i18n_defaults: { - "path": "lang", - "url": "", - "slug": "", - "textdomain": "", - "file_format": "%textdomain%-%wp_locale%.%format%", - "formats": ["po", "mo"], - "filter": { "minimum_percentage": 30 } - }, - paths: { - "build_dir": ".pup-build", - "changelog": null, - "css": [], - "js": [], - "sync_files": [], - "versions": [], - "views": [], - "zip_dir": ".pup-zip" - }, - env: ["NODE_AUTH_TOKEN"], - repo: null, - zip_use_default_ignore: true, - zip_name: null -}; - -//#endregion -//#region src/config.ts -const __filename$1 = fileURLToPath(import.meta.url); -const __dirname$1 = path.dirname(__filename$1); -/** -* Loads, merges, and provides access to the project's pup configuration. -* -* @since TBD -*/ -var Config = class { - #workingDir; - #puprcFilePath; - #config; - #workflows; - #checks; - #versionFiles; - #i18n = null; - /** - * Initializes configuration by loading and merging .puprc with defaults. - * - * @since TBD - * - * @param {string} workingDir - The project working directory. Defaults to process.cwd(). - * - * @throws {Error} If the .puprc file is present but contains invalid JSON or fails validation. - */ - constructor(workingDir) { - const cwd = workingDir ?? process.cwd(); - this.#workingDir = trailingSlashIt(path.normalize(cwd)); - this.#puprcFilePath = path.join(this.#workingDir, ".puprc"); - this.#config = this.getDefaultConfig(); - this.mergeConfigWithDefaults(); - this.#workflows = this.buildWorkflows(); - this.#checks = this.parseCheckConfig(); - this.#versionFiles = this.parseVersionFiles(); - } - /** - * Returns the default configuration from the bundled .puprc-defaults. - * - * @since TBD - * - * @returns {PupConfig} The parsed default configuration object. - */ - getDefaultConfig() { - return structuredClone(_puprc_defaults_default); - } - /** - * Merges the project's .puprc file into the default configuration. - * - * @since TBD - * - * @throws {Error} If the .puprc file contains invalid JSON or fails schema validation. - */ - mergeConfigWithDefaults() { - if (!fs.existsSync(this.#puprcFilePath)) return; - const puprcContents = fs.readFileSync(this.#puprcFilePath, "utf-8"); - let rawPuprc; - try { - rawPuprc = JSON.parse(puprcContents); - } catch { - throw new Error("There is a .puprc file in this directory, but it could not be parsed. Invalid JSON in .puprc."); - } - if (!rawPuprc || typeof rawPuprc !== "object") throw new Error("There is a .puprc file in this directory, but it could not be parsed. Invalid .puprc format."); - const parseResult = PuprcInputSchema.safeParse(rawPuprc); - if (!parseResult.success) { - const issues = parseResult.error.issues.map((issue) => ` ${issue.path.join(".")}: ${issue.message}`).join("\n"); - throw new Error(`There is a .puprc file in this directory, but it contains invalid configuration:\n${issues}`); - } - const puprc = parseResult.data; - const configRecord = this.#config; - for (const [key, value] of Object.entries(puprc)) { - const current = configRecord[key]; - if (current === void 0 || current === null) { - configRecord[key] = value; - continue; - } - if (typeof current !== "object") { - configRecord[key] = value; - continue; - } - if (key === "checks" && typeof value === "object" && value !== null) { - const defaultChecks = current; - const newChecks = value; - configRecord[key] = newChecks; - for (const [checkSlug, checkConfig] of Object.entries(newChecks)) if (defaultChecks[checkSlug] !== void 0) configRecord[key][checkSlug] = this.mergeConfigValue(defaultChecks[checkSlug], checkConfig); - continue; - } - configRecord[key] = this.mergeConfigValue(current, value); - } - } - /** - * Deep-merges two configuration values. Scalars and arrays replace; objects merge recursively. - * - * @since TBD - * - * @param {unknown} original - The original configuration value. - * @param {unknown} newVal - The new configuration value to merge in. - * - * @returns {unknown} The merged configuration value. - */ - mergeConfigValue(original, newVal) { - if (typeof newVal !== "object" || newVal === null) return newVal; - if (typeof original !== "object" || original === null) return newVal; - if (Array.isArray(original)) return newVal; - if (Array.isArray(newVal)) return newVal; - const orig = original; - const nv = newVal; - const result = { ...orig }; - for (const [key, item] of Object.entries(orig)) { - if (nv[key] === void 0) continue; - if (typeof item === "object" && item !== null && !Array.isArray(item)) result[key] = this.mergeConfigValue(item, nv[key]); - else result[key] = nv[key]; - } - for (const [key, item] of Object.entries(nv)) if (result[key] === void 0) result[key] = item; - return result; - } - /** - * Builds the workflow collection from configuration, including auto-generated build workflows. - * - * @since TBD - * - * @returns {WorkflowCollection} The built workflow collection. - */ - buildWorkflows() { - const collection = new WorkflowCollection(); - const rawWorkflows = this.#config.workflows; - if (this.#config.build?.length > 0 && !rawWorkflows?.["build"]) collection.add(createWorkflow("build", this.#config.build)); - if (this.#config.build_dev?.length > 0 && !rawWorkflows?.["build_dev"]) collection.add(createWorkflow("build_dev", this.#config.build_dev)); - if (rawWorkflows && typeof rawWorkflows === "object") for (const [slug, commands] of Object.entries(rawWorkflows)) collection.add(createWorkflow(slug, Array.isArray(commands) ? commands : [])); - return collection; - } - /** - * Parses the checks section of the configuration into CheckConfig objects. - * Uses Zod schema defaults for per-field values. - * - * @since TBD - * - * @returns {Map} A map of check slug to CheckConfig. - */ - parseCheckConfig() { - const checks = this.#config.checks; - const result = /* @__PURE__ */ new Map(); - if (!checks) return result; - for (const [slug, checkInput] of Object.entries(checks)) { - const input = typeof checkInput === "object" && checkInput !== null ? checkInput : {}; - const parsed = CheckConfigSchema.parse({ - slug, - ...input - }); - result.set(slug, parsed); - } - return result; - } - /** - * Parses and validates the version files section of the configuration. - * - * @since TBD - * - * @returns {VersionFile[]} The parsed list of version file objects. - * - * @throws {Error} If a version file entry is missing required properties or the file does not exist. - */ - parseVersionFiles() { - const versions = this.#config.paths?.versions; - const result = []; - if (!versions || !Array.isArray(versions)) return result; - for (const vf of versions) { - if (!vf.file || !vf.regex) throw new Error("Versions specified in .puprc .paths.versions must have the \"file\" and \"regex\" property."); - const filePath = path.join(this.#workingDir, vf.file); - if (!fs.existsSync(filePath)) throw new Error(`Version file does not exist: ${vf.file}`); - const contents = fs.readFileSync(filePath, "utf-8"); - const regex = new RegExp(vf.regex); - const matches = contents.match(regex); - if (!matches || !matches[1] || !matches[2]) throw new Error(`Could not find version in file ${vf.file} using regex "/${vf.regex}/"`); - result.push({ - file: vf.file, - regex: vf.regex - }); - } - return result; - } - /** - * Returns the raw merged configuration object. - * - * @since TBD - * - * @returns {PupConfig} The configuration object. - */ - get raw() { - return this.#config; - } - /** - * Returns the build commands, preferring dev commands when isDev is true. - * - * @since TBD - * - * @param {boolean} isDev - Whether to return dev build commands. - * - * @returns {string[]} The list of build command strings. - */ - getBuildCommands(isDev = false) { - if (isDev && this.#config.build_dev?.length > 0) return this.#config.build_dev; - return this.#config.build ?? []; - } - /** - * Returns the build directory path, optionally as a full absolute path. - * - * @since TBD - * - * @param {boolean} fullPath - Whether to return the full absolute path. - * - * @returns {string} The build directory path. - */ - getBuildDir(fullPath = true) { - const buildDir = this.#config.paths?.build_dir ?? ".pup-build"; - if (!fullPath) return buildDir; - return path.resolve(this.#workingDir, buildDir); - } - /** - * Returns the clean commands from the configuration. - * - * @since TBD - * - * @returns {string[]} The list of clean command strings. - */ - getCleanCommands() { - return this.#config.clean ?? []; - } - /** - * Returns the map of parsed check configurations. - * - * @since TBD - * - * @returns {Map} A map of check slug to CheckConfig. - */ - getChecks() { - return this.#checks; - } - /** - * Returns resolved i18n configurations, merging with defaults. - * - * @since TBD - * - * @returns {I18nResolvedConfig[]} The list of resolved i18n configuration objects. - */ - getI18n() { - if (this.#i18n !== null) return this.#i18n; - const defaults = this.#config.i18n_defaults; - const i18nRaw = this.#config.i18n; - if (!i18nRaw || Array.isArray(i18nRaw) && i18nRaw.length === 0) { - this.#i18n = []; - return this.#i18n; - } - let i18nArr; - if (!Array.isArray(i18nRaw)) i18nArr = [i18nRaw]; - else i18nArr = i18nRaw; - i18nArr = i18nArr.filter((item) => item.url && item.textdomain && item.slug); - if (i18nArr.length === 0) { - this.#i18n = []; - return this.#i18n; - } - this.#i18n = i18nArr.map((item) => ({ - path: item.path ?? defaults.path, - url: item.url ?? defaults.url, - slug: item.slug ?? defaults.slug, - textdomain: item.textdomain ?? defaults.textdomain, - file_format: item.file_format ?? defaults.file_format, - formats: item.formats?.length ? item.formats : defaults.formats, - filter: { minimum_percentage: item.filter?.minimum_percentage ?? defaults.filter.minimum_percentage } - })); - return this.#i18n; - } - /** - * Returns the list of environment variable names from configuration. - * - * @since TBD - * - * @returns {string[]} The list of environment variable name strings. - */ - getEnvVarNames() { - return this.#config.env ?? []; - } - /** - * Returns the git repository URL, inferring from package.json or composer.json if not set. - * - * @since TBD - * - * @returns {string} The git repository URL string. - * - * @throws {Error} If no repository can be determined. - */ - getRepo() { - if (!this.#config.repo) { - const pkgPath = path.join(this.#workingDir, "package.json"); - if (fs.existsSync(pkgPath)) { - const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); - if (typeof pkg.repository === "string") return `git@github.com:${pkg.repository}.git`; - if (pkg.repository?.url) return pkg.repository.url; - } - const composerPath = path.join(this.#workingDir, "composer.json"); - if (fs.existsSync(composerPath)) { - const composer = JSON.parse(fs.readFileSync(composerPath, "utf-8")); - if (composer.name) return `git@github.com:${composer.name}.git`; - } - throw new Error("Could not find a repo in the .puprc file or the \"name\" property in package.json/composer.json."); - } - const repo = this.#config.repo; - if (!repo.includes("https://") && !repo.includes("file://") && !repo.includes("git://") && !repo.includes("git@github.com") && !fs.existsSync(repo)) return `git@github.com:${repo}.git`; - return repo; - } - /** - * Returns the list of sync file names (.distfiles, .distinclude, etc.). - * - * @since TBD - * - * @returns {string[]} The list of sync file name strings. - */ - getSyncFiles() { - const defaults = [ - ".distfiles", - ".distinclude", - ".distignore", - ".gitattributes" - ]; - const configFiles = this.#config.paths?.sync_files; - if (!configFiles || !Array.isArray(configFiles) || configFiles.length === 0) return defaults; - return [...new Set([...defaults, ...configFiles])]; - } - /** - * Returns the parsed version file configurations. - * - * @since TBD - * - * @returns {VersionFile[]} The list of version file objects. - */ - getVersionFiles() { - return this.#versionFiles; - } - /** - * Returns the workflow collection. - * - * @since TBD - * - * @returns {WorkflowCollection} The WorkflowCollection instance. - */ - getWorkflows() { - return this.#workflows; - } - /** - * Returns the working directory path. - * - * @since TBD - * - * @returns {string} The absolute working directory path with trailing slash. - */ - getWorkingDir() { - return this.#workingDir; - } - /** - * Returns the zip staging directory path, optionally as a full absolute path. - * - * @since TBD - * - * @param {boolean} fullPath - Whether to return the full absolute path. - * - * @returns {string} The zip staging directory path. - */ - getZipDir(fullPath = true) { - const zipDir = this.#config.paths?.zip_dir ?? ".pup-zip"; - if (!fullPath) return zipDir; - return path.resolve(this.#workingDir, zipDir); - } - /** - * Returns the zip archive base name, inferring from package.json if not set. - * - * @since TBD - * - * @returns {string} The zip archive base name string. - * - * @throws {Error} If no zip name can be determined. - */ - getZipName() { - if (this.#config.zip_name) return this.#config.zip_name; - const pkgPath = path.join(this.#workingDir, "package.json"); - if (fs.existsSync(pkgPath)) { - const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); - if (pkg.name) return pkg.name.replace(/^@[^/]+\//, ""); - } - const composerPath = path.join(this.#workingDir, "composer.json"); - if (fs.existsSync(composerPath)) { - const composer = JSON.parse(fs.readFileSync(composerPath, "utf-8")); - if (composer.name) return composer.name.replace(/^[^/]+\//, ""); - } - throw new Error("Could not find a \"zip_name\" in .puprc"); - } - /** - * Returns whether to use the default .distignore-defaults patterns. - * - * @since TBD - * - * @returns {boolean} True if default ignore patterns should be used. - */ - getZipUseDefaultIgnore() { - return this.#config.zip_use_default_ignore ?? true; - } - /** - * Serializes the configuration to a plain object. - * - * @since TBD - * - * @returns {PupConfig} The configuration as a PupConfig object. - */ - toJSON() { - return this.#config; - } -}; -let globalConfig = null; -/** -* Returns the singleton Config instance, creating it if needed. -* -* @since TBD -* -* @param {string} workingDir - Optional working directory to pass to the Config constructor. -* -* @returns {Config} The singleton Config instance. -*/ -function getConfig(workingDir) { - if (!globalConfig) globalConfig = new Config(workingDir); - return globalConfig; -} -/** -* Resets the singleton Config instance, forcing a fresh load on next access. -* -* @since TBD -* -* @returns {void} -*/ -function resetConfig() { - globalConfig = null; -} - -//#endregion -//#region src/app.ts -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -/** -* Reads the pup version from the nearest package.json. -* -* @since TBD -* -* @returns {string} The version string from package.json, or '2.0.0' as a fallback. -*/ -function getVersion() { - const candidates = [path.resolve(__dirname, "..", "package.json"), path.resolve(__dirname, "..", "..", "package.json")]; - for (const candidate of candidates) if (fs.existsSync(candidate)) return JSON.parse(fs.readFileSync(candidate, "utf-8")).version; - return "2.0.0"; -} -const PUP_VERSION = getVersion(); -/** -* Creates and configures the Commander program instance. -* -* @since TBD -* -* @returns {Command} The configured Commander program. -*/ -function createApp() { - resetConfig(); - getConfig(); - const program = new Command(); - program.name("pup").version(PUP_VERSION).description("StellarWP's Project Utilities & Packager"); - return program; -} - -//#endregion -//#region src/cli.ts -createApp().parseAsync(process.argv).catch((err) => { - console.error(err instanceof Error ? err.message : String(err)); - process.exit(1); -}); - -//#endregion -export { }; -//# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map deleted file mode 100644 index 6d684eb..0000000 --- a/dist/cli.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli.js","names":["__filename","__dirname","#workingDir","#puprcFilePath","#config","#workflows","#checks","#versionFiles","puprcDefaults","#i18n"],"sources":["../src/utils/directory.ts","../src/models/workflow.ts","../src/schemas.ts","../defaults/.puprc-defaults.json","../src/config.ts","../src/app.ts","../src/cli.ts"],"sourcesContent":["import path from 'node:path';\nimport fs from 'fs-extra';\n\n/**\n * Checks whether a child directory is inside a parent directory.\n *\n * @since TBD\n *\n * @param {string} parentDir - The parent directory path.\n * @param {string} childDir - The child directory path.\n *\n * @returns {boolean} True if childDir is inside parentDir.\n */\nexport function isInside(parentDir: string, childDir: string): boolean {\n const relative = path.relative(parentDir, childDir);\n return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);\n}\n\n/**\n * Ensures a directory path ends with a trailing separator.\n *\n * @since TBD\n *\n * @param {string} p - The path to ensure has a trailing separator.\n *\n * @returns {string} The path with a trailing separator.\n *\n * @throws {Error} If the path appears to be a file (has an extension).\n */\nexport function trailingSlashIt(p: string): string {\n const { dir, base, ext } = path.parse(p);\n\n if (ext.length > 0) {\n throw new Error('Could not add trailing slash to file path.');\n }\n\n return path.join(dir, base, path.sep);\n}\n\n/**\n * Removes a directory, but only if it is within the given working directory.\n *\n * @since TBD\n *\n * @param {string} dir - The directory path to remove.\n * @param {string} workingDir - The working directory that the target must be within.\n *\n * @returns {void}\n *\n * @throws {Error} If the directory is outside the working directory.\n */\nexport async function rmdir(dir: string, workingDir: string): Promise {\n const relative = path.relative(workingDir, dir);\n const inside = relative && !relative.startsWith('..') && !path.isAbsolute(relative);\n\n // Safety check: only remove directories within the working directory\n if (!inside) {\n throw new Error(\n `Refusing to remove directory outside working directory: ${dir}`\n );\n }\n\n if (await fs.pathExists(dir)) {\n await fs.remove(dir);\n }\n}\n\n/**\n * Resolves a relative path against a working directory.\n * Rejects absolute paths unless a default is provided.\n *\n * @since TBD\n *\n * @param {string} relativePath - The relative path to resolve.\n * @param {string} workingDir - The working directory to resolve against.\n * @param {string} [defaultPath] - Optional default path to use if an absolute path is given.\n *\n * @returns {string} The resolved absolute path.\n *\n * @throws {Error} If an absolute path is given without a default fallback.\n */\nexport function resolveRelativePath(\n relativePath: string,\n workingDir: string,\n defaultPath?: string\n): string {\n if (path.isAbsolute(relativePath)) {\n if (!defaultPath) {\n throw new Error('Absolute paths are not allowed in the .puprc file.');\n }\n\n relativePath = defaultPath;\n }\n\n if (isInside(workingDir, relativePath)) {\n return relativePath;\n }\n\n return path.join(workingDir, relativePath);\n}\n","import type { Workflow } from '../types.ts';\n\n/**\n * Creates a Workflow object from a slug and list of commands.\n *\n * @since TBD\n *\n * @param {string} slug - The unique identifier for the workflow.\n * @param {string[]} commands - The list of commands to execute in the workflow.\n *\n * @returns {Workflow} A Workflow object with the provided slug and commands.\n */\nexport function createWorkflow(slug: string, commands: string[]): Workflow {\n return { slug, commands };\n}\n\n/**\n * Manages a collection of named workflows.\n *\n * @since TBD\n */\nexport class WorkflowCollection {\n private workflows: Map = new Map();\n\n /**\n * Adds a workflow to the collection.\n *\n * @since TBD\n *\n * @param {Workflow} workflow - The workflow to add.\n *\n * @returns {void}\n */\n add(workflow: Workflow): void {\n this.workflows.set(workflow.slug, workflow);\n }\n\n /**\n * Retrieves a workflow by its slug.\n *\n * @since TBD\n *\n * @param {string} slug - The slug of the workflow to retrieve.\n *\n * @returns {Workflow | undefined} The workflow if found, otherwise undefined.\n */\n get(slug: string): Workflow | undefined {\n return this.workflows.get(slug);\n }\n\n /**\n * Checks whether a workflow with the given slug exists.\n *\n * @since TBD\n *\n * @param {string} slug - The slug to check.\n *\n * @returns {boolean} True if the workflow exists, false otherwise.\n */\n has(slug: string): boolean {\n return this.workflows.has(slug);\n }\n\n /**\n * Returns all workflows as an array.\n *\n * @since TBD\n *\n * @returns {Workflow[]} An array containing all workflows in the collection.\n */\n getAll(): Workflow[] {\n return Array.from(this.workflows.values());\n }\n\n /**\n * Returns the number of workflows in the collection.\n *\n * @since TBD\n *\n * @returns {number} The count of workflows.\n */\n get size(): number {\n return this.workflows.size;\n }\n\n /**\n * Allows iterating over all workflows in the collection.\n *\n * @since TBD\n *\n * @returns {Iterator} An iterator over the workflows.\n */\n [Symbol.iterator](): Iterator {\n return this.workflows.values();\n }\n}\n","import { z } from 'zod';\n\n/**\n * Schema for a version file entry in .puprc paths.versions.\n *\n * @since TBD\n */\nexport const VersionFileInputSchema = z.object({\n file: z.string(),\n regex: z.string(),\n});\n\nexport type VersionFileInput = z.infer;\n\n/**\n * Parsed version file (same shape as input).\n *\n * @since TBD\n */\nexport const VersionFileSchema = z.object({\n file: z.string(),\n regex: z.string(),\n});\n\nexport type VersionFile = z.infer;\n\n/**\n * Schema for the i18n filter configuration.\n *\n * @since TBD\n */\nconst I18nFilterSchema = z.object({\n minimum_percentage: z.number(),\n});\n\n/**\n * Schema for an i18n configuration entry from .puprc (all fields optional).\n *\n * @since TBD\n */\nexport const I18nConfigInputSchema = z.object({\n path: z.string().optional(),\n url: z.string().optional(),\n slug: z.string().optional(),\n textdomain: z.string().optional(),\n file_format: z.string().optional(),\n formats: z.array(z.string()).optional(),\n filter: z.object({\n minimum_percentage: z.number().optional(),\n }).optional(),\n}).passthrough();\n\nexport type I18nConfigInput = z.infer;\n\n/**\n * Schema for the i18n defaults section of configuration.\n *\n * @since TBD\n */\nexport const I18nDefaultsSchema = z.object({\n path: z.string(),\n url: z.string(),\n slug: z.string(),\n textdomain: z.string(),\n file_format: z.string(),\n formats: z.array(z.string()),\n filter: I18nFilterSchema,\n});\n\nexport type I18nDefaults = z.infer;\n\n/**\n * Schema for a fully resolved i18n configuration entry (all fields required).\n *\n * @since TBD\n */\nexport const I18nResolvedConfigSchema = z.object({\n path: z.string(),\n url: z.string(),\n slug: z.string(),\n textdomain: z.string(),\n file_format: z.string(),\n formats: z.array(z.string()),\n filter: I18nFilterSchema,\n});\n\nexport type I18nResolvedConfig = z.infer;\n\n/**\n * Schema for a check configuration entry from .puprc (optional fields with defaults).\n *\n * @since TBD\n */\nexport const CheckConfigInputSchema = z.object({\n fail_method: z.enum(['error', 'warn']).optional(),\n fail_method_dev: z.enum(['error', 'warn']).optional(),\n type: z.enum(['simple', 'class', 'pup', 'command']).optional(),\n file: z.string().optional(),\n command: z.string().optional(),\n configure: z.string().optional(),\n args: z.record(z.string(), z.string()).optional(),\n dirs: z.array(z.string()).optional(),\n skip_directories: z.string().optional(),\n skip_files: z.string().optional(),\n}).passthrough();\n\nexport type CheckConfigInput = z.infer;\n\n/**\n * Schema for a fully resolved check configuration with defaults applied.\n *\n * @since TBD\n */\nexport const CheckConfigSchema = z.object({\n slug: z.string(),\n fail_method: z.enum(['error', 'warn']).default('error'),\n fail_method_dev: z.enum(['error', 'warn']).default('warn'),\n type: z.enum(['simple', 'class', 'pup', 'command']).default('pup'),\n file: z.string().optional(),\n command: z.string().optional(),\n configure: z.string().optional(),\n args: z.record(z.string(), z.string()).default({}),\n dirs: z.array(z.string()).optional(),\n skip_directories: z.string().optional(),\n skip_files: z.string().optional(),\n});\n\nexport type CheckConfig = z.infer;\n\n/**\n * Schema for the paths section of configuration.\n *\n * @since TBD\n */\nexport const PathsConfigSchema = z.object({\n build_dir: z.string(),\n changelog: z.string().nullable(),\n css: z.array(z.string()),\n js: z.array(z.string()),\n sync_files: z.array(z.string()),\n versions: z.array(VersionFileInputSchema),\n views: z.array(z.string()),\n zip_dir: z.string(),\n});\n\nexport type PathsConfig = z.infer;\n\n/**\n * Schema for the full merged pup configuration (after defaults are applied).\n *\n * @since TBD\n */\nexport const PupConfigSchema = z.object({\n build: z.array(z.string()),\n build_dev: z.array(z.string()),\n workflows: z.record(z.string(), z.array(z.string())),\n checks: z.record(z.string(), CheckConfigInputSchema),\n clean: z.array(z.string()),\n i18n: z.union([z.array(I18nConfigInputSchema), I18nConfigInputSchema]),\n i18n_defaults: I18nDefaultsSchema,\n paths: PathsConfigSchema,\n env: z.array(z.string()),\n repo: z.string().nullable(),\n zip_use_default_ignore: z.boolean(),\n zip_name: z.string().nullable(),\n}).passthrough();\n\nexport type PupConfig = z.infer;\n\n/**\n * Schema for validating raw .puprc input (all fields optional + passthrough for custom keys).\n *\n * @since TBD\n */\nexport const PuprcInputSchema = z.object({\n build: z.array(z.string()).optional(),\n build_dev: z.array(z.string()).optional(),\n workflows: z.record(z.string(), z.array(z.string())).optional(),\n checks: z.record(z.string(), CheckConfigInputSchema.or(z.object({}).passthrough())).optional(),\n clean: z.array(z.string()).optional(),\n i18n: z.union([z.array(I18nConfigInputSchema), I18nConfigInputSchema]).optional(),\n i18n_defaults: I18nDefaultsSchema.partial().optional(),\n paths: PathsConfigSchema.partial().optional(),\n env: z.array(z.string()).optional(),\n repo: z.string().nullable().optional(),\n zip_use_default_ignore: z.boolean().optional(),\n zip_name: z.string().nullable().optional(),\n}).passthrough();\n\nexport type PuprcInput = z.infer;\n\n/**\n * Schema for a workflow.\n *\n * @since TBD\n */\nexport const WorkflowSchema = z.object({\n slug: z.string(),\n commands: z.array(z.string()),\n});\n\nexport type Workflow = z.infer;\n\n/**\n * Result of running a check.\n *\n * @since TBD\n */\nexport interface CheckResult {\n success: boolean;\n output: string;\n}\n\n/**\n * Result of running a shell command.\n *\n * @since TBD\n */\nexport interface RunCommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n","","import fs from 'fs-extra';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { trailingSlashIt } from './utils/directory.ts';\nimport { WorkflowCollection, createWorkflow } from './models/workflow.ts';\nimport { PuprcInputSchema, CheckConfigSchema } from './schemas.ts';\nimport type {\n PupConfig,\n CheckConfig,\n VersionFile,\n VersionFileInput,\n I18nResolvedConfig,\n I18nConfigInput,\n} from './types.ts';\nimport puprcDefaults from '../defaults/.puprc-defaults.json';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Locates the defaults directory containing .distignore-defaults and docs.\n *\n * @since TBD\n *\n * @returns {string} The absolute path to the defaults directory.\n */\nexport function getDefaultsDir(): string {\n // In built dist, defaults/ is at the package root\n // During dev, it's at the repo root\n const candidates = [\n path.resolve(__dirname, '..', 'defaults'),\n path.resolve(__dirname, '..', '..', 'defaults'),\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n return candidates[0];\n}\n\n/**\n * Loads, merges, and provides access to the project's pup configuration.\n *\n * @since TBD\n */\nexport class Config {\n readonly #workingDir: string;\n readonly #puprcFilePath: string;\n readonly #config: PupConfig;\n #workflows: WorkflowCollection;\n #checks: Map;\n #versionFiles: VersionFile[];\n #i18n: I18nResolvedConfig[] | null = null;\n\n /**\n * Initializes configuration by loading and merging .puprc with defaults.\n *\n * @since TBD\n *\n * @param {string} workingDir - The project working directory. Defaults to process.cwd().\n *\n * @throws {Error} If the .puprc file is present but contains invalid JSON or fails validation.\n */\n constructor(workingDir?: string) {\n const cwd = workingDir ?? process.cwd();\n\n this.#workingDir = trailingSlashIt(path.normalize(cwd));\n this.#puprcFilePath = path.join(this.#workingDir, '.puprc');\n this.#config = this.getDefaultConfig();\n this.mergeConfigWithDefaults();\n this.#workflows = this.buildWorkflows();\n this.#checks = this.parseCheckConfig();\n this.#versionFiles = this.parseVersionFiles();\n }\n\n /**\n * Returns the default configuration from the bundled .puprc-defaults.\n *\n * @since TBD\n *\n * @returns {PupConfig} The parsed default configuration object.\n */\n private getDefaultConfig(): PupConfig {\n return structuredClone(puprcDefaults) as PupConfig;\n }\n\n /**\n * Merges the project's .puprc file into the default configuration.\n *\n * @since TBD\n *\n * @throws {Error} If the .puprc file contains invalid JSON or fails schema validation.\n */\n private mergeConfigWithDefaults(): void {\n if (!fs.existsSync(this.#puprcFilePath)) {\n return;\n }\n\n const puprcContents = fs.readFileSync(this.#puprcFilePath, 'utf-8');\n let rawPuprc: unknown;\n\n try {\n rawPuprc = JSON.parse(puprcContents);\n } catch {\n throw new Error(\n 'There is a .puprc file in this directory, but it could not be parsed. Invalid JSON in .puprc.'\n );\n }\n\n if (!rawPuprc || typeof rawPuprc !== 'object') {\n throw new Error(\n 'There is a .puprc file in this directory, but it could not be parsed. Invalid .puprc format.'\n );\n }\n\n const parseResult = PuprcInputSchema.safeParse(rawPuprc);\n\n if (!parseResult.success) {\n const issues = parseResult.error.issues\n .map((issue) => ` ${issue.path.join('.')}: ${issue.message}`)\n .join('\\n');\n throw new Error(\n `There is a .puprc file in this directory, but it contains invalid configuration:\\n${issues}`\n );\n }\n\n const puprc = parseResult.data as Record;\n const configRecord = this.#config as unknown as Record;\n\n for (const [key, value] of Object.entries(puprc)) {\n const current = configRecord[key];\n\n if (current === undefined || current === null) {\n configRecord[key] = value;\n continue;\n }\n\n if (typeof current !== 'object') {\n configRecord[key] = value;\n continue;\n }\n\n // Special handling for checks: preserve defaults + merge\n if (key === 'checks' && typeof value === 'object' && value !== null) {\n const defaultChecks = current as Record;\n const newChecks = value as Record;\n configRecord[key] = newChecks;\n\n for (const [checkSlug, checkConfig] of Object.entries(newChecks)) {\n if (defaultChecks[checkSlug] !== undefined) {\n (configRecord[key] as Record)[checkSlug] =\n this.mergeConfigValue(defaultChecks[checkSlug], checkConfig);\n }\n }\n continue;\n }\n\n configRecord[key] = this.mergeConfigValue(current, value);\n }\n }\n\n /**\n * Deep-merges two configuration values. Scalars and arrays replace; objects merge recursively.\n *\n * @since TBD\n *\n * @param {unknown} original - The original configuration value.\n * @param {unknown} newVal - The new configuration value to merge in.\n *\n * @returns {unknown} The merged configuration value.\n */\n private mergeConfigValue(original: unknown, newVal: unknown): unknown {\n if (typeof newVal !== 'object' || newVal === null) {\n return newVal;\n }\n\n if (typeof original !== 'object' || original === null) {\n return newVal;\n }\n\n if (Array.isArray(original)) {\n // Numeric-keyed arrays: replace\n return newVal;\n }\n\n if (Array.isArray(newVal)) {\n return newVal;\n }\n\n const orig = original as Record;\n const nv = newVal as Record;\n const result = { ...orig };\n\n for (const [key, item] of Object.entries(orig)) {\n if (nv[key] === undefined) continue;\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n result[key] = this.mergeConfigValue(item, nv[key]);\n } else {\n result[key] = nv[key];\n }\n }\n\n for (const [key, item] of Object.entries(nv)) {\n if (result[key] === undefined) {\n result[key] = item;\n }\n }\n\n return result;\n }\n\n /**\n * Builds the workflow collection from configuration, including auto-generated build workflows.\n *\n * @since TBD\n *\n * @returns {WorkflowCollection} The built workflow collection.\n */\n private buildWorkflows(): WorkflowCollection {\n const collection = new WorkflowCollection();\n\n const rawWorkflows = this.#config.workflows as unknown;\n\n // Auto-create build workflow\n if (\n this.#config.build?.length > 0 &&\n !(rawWorkflows as Record)?.['build']\n ) {\n collection.add(createWorkflow('build', this.#config.build));\n }\n\n if (\n this.#config.build_dev?.length > 0 &&\n !(rawWorkflows as Record)?.['build_dev']\n ) {\n collection.add(createWorkflow('build_dev', this.#config.build_dev));\n }\n\n if (rawWorkflows && typeof rawWorkflows === 'object') {\n for (const [slug, commands] of Object.entries(\n rawWorkflows as Record\n )) {\n collection.add(\n createWorkflow(slug, Array.isArray(commands) ? commands : [])\n );\n }\n }\n\n return collection;\n }\n\n /**\n * Parses the checks section of the configuration into CheckConfig objects.\n * Uses Zod schema defaults for per-field values.\n *\n * @since TBD\n *\n * @returns {Map} A map of check slug to CheckConfig.\n */\n private parseCheckConfig(): Map {\n const checks = this.#config.checks;\n const result = new Map();\n if (!checks) return result;\n\n for (const [slug, checkInput] of Object.entries(checks)) {\n const input = typeof checkInput === 'object' && checkInput !== null\n ? checkInput\n : {};\n\n const parsed = CheckConfigSchema.parse({ slug, ...input });\n result.set(slug, parsed);\n }\n\n return result;\n }\n\n /**\n * Parses and validates the version files section of the configuration.\n *\n * @since TBD\n *\n * @returns {VersionFile[]} The parsed list of version file objects.\n *\n * @throws {Error} If a version file entry is missing required properties or the file does not exist.\n */\n private parseVersionFiles(): VersionFile[] {\n const versions = this.#config.paths?.versions;\n const result: VersionFile[] = [];\n if (!versions || !Array.isArray(versions)) return result;\n\n for (const vf of versions as VersionFileInput[]) {\n if (!vf.file || !vf.regex) {\n throw new Error(\n 'Versions specified in .puprc .paths.versions must have the \"file\" and \"regex\" property.'\n );\n }\n\n const filePath = path.join(this.#workingDir, vf.file);\n if (!fs.existsSync(filePath)) {\n throw new Error(`Version file does not exist: ${vf.file}`);\n }\n\n const contents = fs.readFileSync(filePath, 'utf-8');\n const regex = new RegExp(vf.regex);\n const matches = contents.match(regex);\n\n if (!matches || !matches[1] || !matches[2]) {\n throw new Error(\n `Could not find version in file ${vf.file} using regex \"/${vf.regex}/\"`\n );\n }\n\n result.push({ file: vf.file, regex: vf.regex });\n }\n\n return result;\n }\n\n /**\n * Returns the raw merged configuration object.\n *\n * @since TBD\n *\n * @returns {PupConfig} The configuration object.\n */\n get raw(): PupConfig {\n return this.#config;\n }\n\n /**\n * Returns the build commands, preferring dev commands when isDev is true.\n *\n * @since TBD\n *\n * @param {boolean} isDev - Whether to return dev build commands.\n *\n * @returns {string[]} The list of build command strings.\n */\n getBuildCommands(isDev = false): string[] {\n if (isDev && this.#config.build_dev?.length > 0) {\n return this.#config.build_dev;\n }\n return this.#config.build ?? [];\n }\n\n /**\n * Returns the build directory path, optionally as a full absolute path.\n *\n * @since TBD\n *\n * @param {boolean} fullPath - Whether to return the full absolute path.\n *\n * @returns {string} The build directory path.\n */\n getBuildDir(fullPath = true): string {\n const buildDir = this.#config.paths?.build_dir ?? '.pup-build';\n if (!fullPath) return buildDir;\n return path.resolve(this.#workingDir, buildDir);\n }\n\n /**\n * Returns the clean commands from the configuration.\n *\n * @since TBD\n *\n * @returns {string[]} The list of clean command strings.\n */\n getCleanCommands(): string[] {\n return this.#config.clean ?? [];\n }\n\n /**\n * Returns the map of parsed check configurations.\n *\n * @since TBD\n *\n * @returns {Map} A map of check slug to CheckConfig.\n */\n getChecks(): Map {\n return this.#checks;\n }\n\n /**\n * Returns resolved i18n configurations, merging with defaults.\n *\n * @since TBD\n *\n * @returns {I18nResolvedConfig[]} The list of resolved i18n configuration objects.\n */\n getI18n(): I18nResolvedConfig[] {\n if (this.#i18n !== null) return this.#i18n;\n\n const defaults = this.#config.i18n_defaults;\n const i18nRaw = this.#config.i18n;\n\n if (!i18nRaw || (Array.isArray(i18nRaw) && i18nRaw.length === 0)) {\n this.#i18n = [];\n return this.#i18n;\n }\n\n // Normalize to array\n let i18nArr: I18nConfigInput[];\n if (!Array.isArray(i18nRaw)) {\n i18nArr = [i18nRaw];\n } else {\n i18nArr = i18nRaw;\n }\n\n // Filter valid entries\n i18nArr = i18nArr.filter(\n (item) => item.url && item.textdomain && item.slug\n );\n\n if (i18nArr.length === 0) {\n this.#i18n = [];\n return this.#i18n;\n }\n\n this.#i18n = i18nArr.map((item) => ({\n path: item.path ?? defaults.path,\n url: item.url ?? defaults.url,\n slug: item.slug ?? defaults.slug,\n textdomain: item.textdomain ?? defaults.textdomain,\n file_format: item.file_format ?? defaults.file_format,\n formats: item.formats?.length ? item.formats : defaults.formats,\n filter: {\n minimum_percentage:\n item.filter?.minimum_percentage ??\n defaults.filter.minimum_percentage,\n },\n }));\n\n return this.#i18n;\n }\n\n /**\n * Returns the list of environment variable names from configuration.\n *\n * @since TBD\n *\n * @returns {string[]} The list of environment variable name strings.\n */\n getEnvVarNames(): string[] {\n return this.#config.env ?? [];\n }\n\n /**\n * Returns the git repository URL, inferring from package.json or composer.json if not set.\n *\n * @since TBD\n *\n * @returns {string} The git repository URL string.\n *\n * @throws {Error} If no repository can be determined.\n */\n getRepo(): string {\n if (!this.#config.repo) {\n // Try to infer from package.json\n const pkgPath = path.join(this.#workingDir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as {\n repository?: { url?: string } | string;\n };\n if (typeof pkg.repository === 'string') {\n return `git@github.com:${pkg.repository}.git`;\n }\n if (pkg.repository?.url) {\n return pkg.repository.url;\n }\n }\n\n // Try composer.json fallback\n const composerPath = path.join(this.#workingDir, 'composer.json');\n if (fs.existsSync(composerPath)) {\n const composer = JSON.parse(\n fs.readFileSync(composerPath, 'utf-8')\n ) as { name?: string };\n if (composer.name) {\n return `git@github.com:${composer.name}.git`;\n }\n }\n\n throw new Error(\n 'Could not find a repo in the .puprc file or the \"name\" property in package.json/composer.json.'\n );\n }\n\n const repo = this.#config.repo;\n\n if (\n !repo.includes('https://') &&\n !repo.includes('file://') &&\n !repo.includes('git://') &&\n !repo.includes('git@github.com') &&\n !fs.existsSync(repo)\n ) {\n return `git@github.com:${repo}.git`;\n }\n\n return repo;\n }\n\n /**\n * Returns the list of sync file names (.distfiles, .distinclude, etc.).\n *\n * @since TBD\n *\n * @returns {string[]} The list of sync file name strings.\n */\n getSyncFiles(): string[] {\n const defaults = ['.distfiles', '.distinclude', '.distignore', '.gitattributes'];\n const configFiles = this.#config.paths?.sync_files;\n\n if (!configFiles || !Array.isArray(configFiles) || configFiles.length === 0) {\n return defaults;\n }\n\n return [...new Set([...defaults, ...configFiles])];\n }\n\n /**\n * Returns the parsed version file configurations.\n *\n * @since TBD\n *\n * @returns {VersionFile[]} The list of version file objects.\n */\n getVersionFiles(): VersionFile[] {\n return this.#versionFiles;\n }\n\n /**\n * Returns the workflow collection.\n *\n * @since TBD\n *\n * @returns {WorkflowCollection} The WorkflowCollection instance.\n */\n getWorkflows(): WorkflowCollection {\n return this.#workflows;\n }\n\n /**\n * Returns the working directory path.\n *\n * @since TBD\n *\n * @returns {string} The absolute working directory path with trailing slash.\n */\n getWorkingDir(): string {\n return this.#workingDir;\n }\n\n /**\n * Returns the zip staging directory path, optionally as a full absolute path.\n *\n * @since TBD\n *\n * @param {boolean} fullPath - Whether to return the full absolute path.\n *\n * @returns {string} The zip staging directory path.\n */\n getZipDir(fullPath = true): string {\n const zipDir = this.#config.paths?.zip_dir ?? '.pup-zip';\n if (!fullPath) return zipDir;\n return path.resolve(this.#workingDir, zipDir);\n }\n\n /**\n * Returns the zip archive base name, inferring from package.json if not set.\n *\n * @since TBD\n *\n * @returns {string} The zip archive base name string.\n *\n * @throws {Error} If no zip name can be determined.\n */\n getZipName(): string {\n if (this.#config.zip_name) {\n return this.#config.zip_name;\n }\n\n // Try package.json name\n const pkgPath = path.join(this.#workingDir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as {\n name?: string;\n };\n if (pkg.name) {\n // Strip scope prefix (e.g., @stellarwp/pup -> pup)\n return pkg.name.replace(/^@[^/]+\\//, '');\n }\n }\n\n // Try composer.json name\n const composerPath = path.join(this.#workingDir, 'composer.json');\n if (fs.existsSync(composerPath)) {\n const composer = JSON.parse(\n fs.readFileSync(composerPath, 'utf-8')\n ) as { name?: string };\n if (composer.name) {\n return composer.name.replace(/^[^/]+\\//, '');\n }\n }\n\n throw new Error('Could not find a \"zip_name\" in .puprc');\n }\n\n /**\n * Returns whether to use the default .distignore-defaults patterns.\n *\n * @since TBD\n *\n * @returns {boolean} True if default ignore patterns should be used.\n */\n getZipUseDefaultIgnore(): boolean {\n return this.#config.zip_use_default_ignore ?? true;\n }\n\n /**\n * Serializes the configuration to a plain object.\n *\n * @since TBD\n *\n * @returns {PupConfig} The configuration as a PupConfig object.\n */\n toJSON(): PupConfig {\n return this.#config;\n }\n}\n\nlet globalConfig: Config | null = null;\n\n/**\n * Returns the singleton Config instance, creating it if needed.\n *\n * @since TBD\n *\n * @param {string} workingDir - Optional working directory to pass to the Config constructor.\n *\n * @returns {Config} The singleton Config instance.\n */\nexport function getConfig(workingDir?: string): Config {\n if (!globalConfig) {\n globalConfig = new Config(workingDir);\n }\n return globalConfig;\n}\n\n/**\n * Resets the singleton Config instance, forcing a fresh load on next access.\n *\n * @since TBD\n *\n * @returns {void}\n */\nexport function resetConfig(): void {\n globalConfig = null;\n}\n","import { Command } from 'commander';\nimport { Config, getConfig, resetConfig } from './config.ts';\nimport { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport fs from 'fs-extra';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Reads the pup version from the nearest package.json.\n *\n * @since TBD\n *\n * @returns {string} The version string from package.json, or '2.0.0' as a fallback.\n */\nfunction getVersion(): string {\n // Try to read from package.json\n const candidates = [\n path.resolve(__dirname, '..', 'package.json'),\n path.resolve(__dirname, '..', '..', 'package.json'),\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n const pkg = JSON.parse(fs.readFileSync(candidate, 'utf-8')) as { version: string };\n return pkg.version;\n }\n }\n\n return '2.0.0';\n}\n\nexport const PUP_VERSION = getVersion();\n\n/**\n * Creates and configures the Commander program instance.\n *\n * @since TBD\n *\n * @returns {Command} The configured Commander program.\n */\nexport function createApp(): Command {\n resetConfig();\n\n getConfig();\n\n const program = new Command();\n program\n .name('pup')\n .version(PUP_VERSION)\n .description(\"StellarWP's Project Utilities & Packager\");\n\n return program;\n}\n\nexport { Config, getConfig, resetConfig };\n","import { createApp } from './app.ts';\n\nconst program = createApp();\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"} \ No newline at end of file From 9ea1d91e92c0c5dc27340ce774bdcb30480e1ae3 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:40:04 -0500 Subject: [PATCH 06/11] ENG-219: Improve and clarify build env vars test Use a unique env var name not present in .puprc-defaults to actually test that arbitrary env vars are passed through to subprocesses. Remove the redundant puprc.env assignment that had no effect on the test outcome. --- tests/commands/build.test.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts index 2eaba9f..9637275 100644 --- a/tests/commands/build.test.ts +++ b/tests/commands/build.test.ts @@ -43,25 +43,19 @@ describe('build command', () => { expect(result.output).toContain('Build complete'); }); - it('should pass env vars', async () => { + it('should pass env vars that are not defined in puprc.env', async () => { const puprc = getPuprc(); - puprc.build = ['echo "TOKEN=$NODE_AUTH_TOKEN"']; - puprc.env = ['NODE_AUTH_TOKEN']; + puprc.build = ['echo "TOKEN=$PUP_TEST_UNIQUE_VAR"']; writePuprc(puprc, projectDir); - const originalToken = process.env.NODE_AUTH_TOKEN; - process.env.NODE_AUTH_TOKEN = 'test-token-12345'; + process.env.PUP_TEST_UNIQUE_VAR = 'test-token-12345'; try { const result = await runPup('build', { cwd: projectDir }); expect(result.exitCode).toBe(0); expect(result.output).toContain('TOKEN=test-token-12345'); } finally { - if (originalToken === undefined) { - delete process.env.NODE_AUTH_TOKEN; - } else { - process.env.NODE_AUTH_TOKEN = originalToken; - } + delete process.env.PUP_TEST_UNIQUE_VAR; } }); From 0abe46a68a38a1ee9224cab5d951c23b8ea478fe Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:41:19 -0500 Subject: [PATCH 07/11] ENG-219: Remove unnecessary file --- version-test.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 version-test.txt diff --git a/version-test.txt b/version-test.txt deleted file mode 100644 index aa2bcb5..0000000 --- a/version-test.txt +++ /dev/null @@ -1 +0,0 @@ -Version: 1.0.0 From e72de6c032f16778fb768adf7efb841cd0e720c9 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:41:42 -0500 Subject: [PATCH 08/11] ENG-219: Remove accidentally committed action --- action.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 action.yml diff --git a/action.yml b/action.yml deleted file mode 100644 index 274ad3f..0000000 --- a/action.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: 'StellarWP PUP' -description: 'Project Utilities & Packager for WordPress plugins and themes' -inputs: - command: - description: 'The pup command to run (e.g., "zip", "build", "check")' - required: true - args: - description: 'Additional arguments to pass to the command' - required: false - default: '' - working-directory: - description: 'Working directory for the command' - required: false - default: '.' - node-version: - description: 'Node.js version to use' - required: false - default: '20' -runs: - using: 'composite' - steps: - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ inputs.node-version }} - - - name: Run PUP - shell: bash - working-directory: ${{ inputs.working-directory }} - run: npx @stellarwp/pup@latest ${{ inputs.command }} ${{ inputs.args }} From 8f99f640644c819249425b3e9e3ac756599da52f Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:42:03 -0500 Subject: [PATCH 09/11] ENG-219: Remove accidentally comitted .puprc --- .puprc | 69 ---------------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 .puprc diff --git a/.puprc b/.puprc deleted file mode 100644 index b143b67..0000000 --- a/.puprc +++ /dev/null @@ -1,69 +0,0 @@ -{ - "build": [], - "build_dev": [], - "checks": { - "tbd": { - "fail_method": "error", - "fail_method_dev": "warn", - "skip_directories": "bin|build|vendor|node_modules|.git|.github|tests", - "skip_files": ".min.css|.min.js|.map.js|.css|.png|.jpg|.jpeg|.svg|.gif|.ico", - "dirs": [ - "src" - ] - }, - "version-conflict": { - "fail_method": "error", - "fail_method_dev": "warn" - } - }, - "clean": [], - "i18n": [ - { - "url": "http://127.0.0.1:1/does-not-exist", - "textdomain": "test-plugin", - "slug": "test-plugin" - } - ], - "i18n_defaults": { - "path": "lang", - "url": "", - "slug": "", - "textdomain": "", - "file_format": "%textdomain%-%wp_locale%.%format%", - "formats": ["po", "mo"], - "filter": { - "minimum_percentage": 30 - } - }, - "paths": { - "build_dir": ".pup-build", - "changelog": null, - "css": [], - "js": [], - "sync_files": [], - "versions": [ - { - "file": "package.json", - "regex": "(\"version\": \")([^\"]+)\"" - }, - { - "file": "version-test.txt", - "regex": "(Version: )(.+)" - } - ], - "views": [], - "zip_dir": ".pup-zip" - }, - "env": [ - "NODE_AUTH_TOKEN" - ], - "repo": null, - "zip_use_default_ignore": true, - "zip_name": null, - "workflows": { - "my-workflow": [ - "echo 'workflow step 1'", - "echo 'workflow step 2'" - ] - } -} From 13242bcbdd3b7700293f0aaf5ec302475e927496 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 22:42:57 -0500 Subject: [PATCH 10/11] ENG-219: Remove more accidentally comitted files --- .claude/settings.local.json | 12 ------------ .github/workflows/publish.yml | 21 --------------------- 2 files changed, 33 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 .github/workflows/publish.yml diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 94476fb..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(git checkout:*)", - "Bash(gh pr create:*)", - "Bash(gh pr edit:*)" - ] - } -} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index a20f511..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Publish -on: - release: - types: [created] -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: https://registry.npmjs.org - - run: npm ci - - run: npm run build - - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 6ed17204098f507123e1565eb73091f0d0a52f7d Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Sun, 15 Feb 2026 10:22:13 -0500 Subject: [PATCH 11/11] ENG-219: Add test for build command --root flag --- tests/commands/build.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/commands/build.test.ts b/tests/commands/build.test.ts index 9637275..1975c9d 100644 --- a/tests/commands/build.test.ts +++ b/tests/commands/build.test.ts @@ -59,6 +59,18 @@ describe('build command', () => { } }); + it('should run build in the specified --root directory', async () => { + const rootDir = createTempProject(); + + const puprc = getPuprc(); + puprc.build = ['pwd']; + writePuprc(puprc, projectDir); + + const result = await runPup(`build --root ${rootDir}`, { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain(rootDir); + }); + it('should run build with dev flag', async () => { const puprc = getPuprc(); puprc.build = ['echo "production build"'];