UNPKG

@timeax/zipper

Version:

Config-driven project zipper with .zipconfig / zip.json (include/exclude, presets, dry-run, gitignore support).

917 lines (908 loc) 31.5 kB
#!/usr/bin/env node // src/config.ts import { cosmiconfig } from "cosmiconfig"; import fs3 from "fs"; import path4 from "path"; import YAML2 from "yaml"; import Ajv from "ajv/dist/2020.js"; // src/utils.ts import os from "os"; import path from "path"; import fsSync from "fs"; import { fileURLToPath } from "url"; function getBuiltinStubDir() { const here = path.dirname(fileURLToPath(import.meta.url)); return path.resolve(here, "../stubs"); } // src/config.ts import pc2 from "picocolors"; import { pathToFileURL } from "url"; // src/smart-merge.ts import path3 from "path"; import fs2 from "fs"; import picomatch from "picomatch"; import { globby } from "globby"; // src/user-presets.ts import fs from "fs"; import fsp from "fs/promises"; import path2 from "path"; import os2 from "os"; import YAML from "yaml"; function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } } function getPresetDirs(extra = []) { const env = (process.env.ZIPPER_PRESETS ?? "").split(path2.delimiter).map((s) => s.trim()).filter(Boolean); const home = os2.homedir(); const defaults = [ path2.join(home, ".config", "zipper", "presets"), path2.join(home, ".zipper", "presets") ]; const all = [...extra, ...env, ...defaults]; const seen = /* @__PURE__ */ new Set(); const out = []; for (const d of all) { const abs = path2.isAbsolute(d) ? d : path2.resolve(process.cwd(), d); if (!seen.has(abs) && isDir(abs)) { seen.add(abs); out.push(abs); } } return out; } async function loadUserPresets(extraDirs = []) { const dirs = getPresetDirs(extraDirs); const out = {}; for (const d of dirs) { const entries = await fsp.readdir(d, { withFileTypes: true }).catch(() => []); for (const e of entries) { if (!e.isFile()) continue; if (!/\.(ya?ml|json)$/i.test(e.name)) continue; const full = path2.join(d, e.name); try { const content = await fsp.readFile(full, "utf8"); const data = e.name.endsWith(".json") ? JSON.parse(content) : YAML.parse(content); const name = path2.basename(e.name).replace(/\.(ya?ml|json)$/i, ""); if (data && typeof data === "object") out[name] = data; } catch { } } } return out; } // src/presets.ts var PRESETS = { "laravel-basic": { include: [ "app/**", "bootstrap/**", "config/**", "database/**", "public/**", "resources/**", "routes/**", "artisan", "composer.*", "package.json" ], exclude: [ "node_modules/**", "vendor/**", "storage/logs/**/*", "storage/framework/**/*", // files only; dirs can be kept via placeholders ".env*", ".git/**", "**/*.map" ] }, "laravel-no-vendor": { include: [ "app/**", "bootstrap/**", "config/**", "database/**", "public/**", "resources/**", "routes/**", "artisan", "composer.*", "package.json" ], exclude: [ "vendor/**", "node_modules/**", "storage/**/*", // if you want folders kept, switch to logs/**/* + framework/**/* like above ".env*", ".git/**" ] }, "node-module": { include: ["dist/**", "package.json", "README*", "LICENSE*"], exclude: ["**/*.map", ".git/**", "node_modules/**"] }, // NEW "inertia": { include: [ // server-side "app/**", "bootstrap/**", "config/**", "database/**", "routes/**", "artisan", "composer.*", ".env.example", // front-end source "resources/js/**", "resources/ts/**", "resources/css/**", "resources/sass/**", "resources/views/**", "resources/lang/**", "vite.config.*", "postcss.config.*", "tailwind.config.*", "package.json", "pnpm-lock.yaml", "yarn.lock", "package-lock.json", ".eslintrc.*", ".prettierrc*", ".editorconfig", // public runtime / built assets "public/index.php", "public/.htaccess", "public/build/**" ], exclude: [ // deps "node_modules/**", "vendor/**", // storage (drop files but allow dirs to survive if you include placeholders) "storage/logs/**/*", "storage/framework/**/*", // public stuff you usually don’t want "public/hot", "public/storage/**", "public/*.map", "public/**/*.map", // sourcemaps anywhere "**/*.map", // VCS ".git/**" ] }, /** * Laravel + Inertia with grouped archive layout: * - server/ → backend + blade/lang * - source/ → frontend sources + tooling * - public/* → kept as-is (no double "public/public") */ "inertia-grouped": { groups: { server: { target: "server/", priority: 10, include: [ "app/**", "bootstrap/**", "config/**", "database/**", "routes/**", "artisan", "composer.*", ".env", "resources/views/**", "resources/lang/**", "vendor/**", "storage/logs/**", "storage/framework/**" ] }, source: { target: "source/", priority: 15, include: [ "resources/js/**", "resources/ts/**", "resources/css/**", "resources/sass/**", "vite.config.*", "postcss.config.*", "tailwind.config.*", "package.json", "pnpm-lock.yaml", "yarn.lock", "package-lock.json", ".eslintrc.*", ".prettierrc*", ".editorconfig" ], exclude: ["resources/views/**", "resources/lang/**"] }, public: { // keep existing public/* paths as-is (avoid public/public/**) target: "", priority: 1, include: ["public/**"], exclude: ["public/hot", "public/storage/**", "public/*.map", "public/**/*.map", "public/index.php", "public/.htaccess"] }, root: { target: "", priority: 100, files: [ "public/index.php", "public/.htaccess" ] } } } }; function resolvePresets(names) { if (!names?.length) return {}; const merged = {}; const accInclude = []; const accExclude = []; const accIgnoreFiles = []; const accGroups = {}; const accPre = { includes: [], excludes: [], files: [], modules: [] }; for (const n of names) { const p = PRESETS[n]; if (!p) continue; if (p.include?.length) accInclude.push(...p.include); if (p.exclude?.length) accExclude.push(...p.exclude); if (p.ignoreFiles?.length) accIgnoreFiles.push(...p.ignoreFiles); if (p.groups) { for (const [gname, g] of Object.entries(p.groups)) { const cur = accGroups[gname]; if (!cur) { accGroups[gname] = { target: g.target ?? "", include: [...g.include ?? []], exclude: g.exclude?.length ? [...g.exclude] : void 0, files: g.files?.length ? [...g.files] : void 0, // NEW priority: g.priority ?? 0 }; } else { if (g.target != null) cur.target = g.target; if (g.priority != null) cur.priority = g.priority; if (g.include?.length) cur.include?.push(...g.include); if (g.exclude?.length) cur.exclude = [...cur.exclude ?? [], ...g.exclude]; if (g.files?.length) cur.files = [...cur.files ?? [], ...g.files]; } } } if (p.preprocess) { const pr = p.preprocess; if (pr.includes?.length) accPre.includes.push(...pr.includes); if (pr.excludes?.length) accPre.excludes.push(...pr.excludes); if (pr.files?.length) accPre.files.push(...pr.files); if (pr.modules?.length) accPre.modules.push(...pr.modules); if (pr.module) accPre.modules.push(pr.module); if (typeof pr.maxBytes === "number") accPre.maxBytes = pr.maxBytes; if (typeof pr.timeoutMs === "number") accPre.timeoutMs = pr.timeoutMs; if (typeof pr.binaryMode === "string") accPre.binaryMode = pr.binaryMode; } } merged.include = uniqKeepLast(accInclude); merged.exclude = uniqKeepLast(accExclude); if (accIgnoreFiles.length) merged.ignoreFiles = uniqKeepLast(accIgnoreFiles); if (Object.keys(accGroups).length) { const outGroups = {}; for (const [k, g] of Object.entries(accGroups)) { outGroups[k] = { target: g.target, priority: g.priority, include: uniqKeepLast(g.include ?? []), ...g.exclude?.length ? { exclude: uniqKeepLast(g.exclude) } : {}, ...g.files?.length ? { files: uniqKeepLast(g.files) } : {} // NEW }; } merged.groups = outGroups; } const hasPre = (accPre.includes?.length ?? 0) || (accPre.excludes?.length ?? 0) || (accPre.files?.length ?? 0) || (accPre.modules?.length ?? 0) || accPre.maxBytes != null || accPre.timeoutMs != null || accPre.binaryMode != null; if (hasPre) { const preOut = {}; if (accPre.includes?.length) preOut.includes = uniqKeepLast(accPre.includes); if (accPre.excludes?.length) preOut.excludes = uniqKeepLast(accPre.excludes); if (accPre.files?.length) preOut.files = uniqKeepLast(accPre.files); if (accPre.modules?.length) preOut.modules = uniqKeepLast(accPre.modules); if (accPre.maxBytes != null) preOut.maxBytes = accPre.maxBytes; if (accPre.timeoutMs != null) preOut.timeoutMs = accPre.timeoutMs; if (accPre.binaryMode != null) preOut.binaryMode = accPre.binaryMode; merged.preprocess = preOut; } return merged; } function uniqKeepLast(arr) { const res = []; const seen = /* @__PURE__ */ new Set(); for (let i = arr.length - 1; i >= 0; i--) { const key = String(arr[i]); if (!seen.has(key)) { seen.add(key); res.unshift(arr[i]); } } return res; } async function getAllPresets(extraDirs = []) { const user = await loadUserPresets(extraDirs); return { ...PRESETS, ...user }; } // src/smart-merge.ts import pc from "picocolors"; import cliProgress from "cli-progress"; var normRel = (p) => p.replaceAll("\\", "/").replace(/^\.?\//, ""); var normArr = (a) => (a ?? []).map(normRel); var uniq = (xs) => Array.from(new Set(xs ?? [])); var union = (...arrs) => uniq(arrs.flatMap((a) => a ?? [])); function mergeGroups(a, b) { const out = {}; const names = uniq([...a ? Object.keys(a) : [], ...b ? Object.keys(b) : []]); for (const name of names) { const ga = a?.[name] ?? {}; const gb = b?.[name] ?? {}; const target = gb.target ?? ga.target ?? ""; const priority = gb.priority ?? ga.priority ?? 0; const include = union(ga.include, gb.include); const exclude = union(ga.exclude, gb.exclude); const files = union(ga.files, gb.files); const g = { target, priority }; if (include.length) g.include = include; if (exclude.length) g.exclude = exclude; if (files.length) g.files = files; if (!g.include && !g.files) continue; out[name] = g; } return Object.keys(out).length ? out : void 0; } function sourcesFromPresetsAndUser(cfg, root) { const presetSources = (cfg.presets ?? []).map((pname, i) => { const p = PRESETS[pname] ?? {}; return { name: `preset:${pname}`, tier: i, order: p.order ?? cfg.order ?? ["include", "exclude"], include: normArr(p.include), exclude: normArr(p.exclude), files: normArr(p.files), groups: p.groups }; }); const userTier = cfg.presets?.length ?? 0; const groupFiles = (() => { const g = cfg.groups ?? {}; const all = /* @__PURE__ */ new Set(); for (const k of Object.keys(g)) for (const f of g[k].files ?? []) all.add(normRel(f)); return Array.from(all); })(); const userSource = { name: "user", tier: userTier, order: cfg.order, include: normArr(cfg.include), exclude: normArr(cfg.exclude), files: groupFiles, groups: cfg.groups }; return [...presetSources, userSource]; } function compileSignals(sources) { const sigs = []; for (const src of sources) { let seq = 0; for (const g of src.include) { const m = picomatch(g, { dot: true }); sigs.push({ kind: "include", tier: src.tier, seq: seq++, order: src.order, name: src.name, match: m }); } for (const g of src.exclude) { const m = picomatch(g, { dot: true }); sigs.push({ kind: "exclude", tier: src.tier, seq: seq++, order: src.order, name: src.name, match: m }); } for (const f of src.files) { const rel = normRel(f); const m = (x) => normRel(x) === rel; sigs.push({ kind: "include", tier: src.tier, seq: seq++, order: src.order, name: src.name, match: m }); } } sigs.sort((a, b) => a.tier - b.tier || a.seq - b.seq); return sigs; } function decideFor(rel, signals) { let winner; for (const s of signals) { if (!s.match(rel)) continue; if (!winner) { winner = s; continue; } if (s.tier > winner.tier) { winner = s; continue; } if (s.tier === winner.tier) { if (s.seq > winner.seq) winner = s; else if (s.seq === winner.seq) { if (s.order[0] === "exclude" && s.kind === "include" && winner.kind === "exclude") winner = s; else if (s.order[0] === "include" && s.kind === "exclude" && winner.kind === "include") winner = s; } } } return winner; } async function buildUniverse(root, sources, opts, log) { const includeGlobs = /* @__PURE__ */ new Set(); const explicitFiles = /* @__PURE__ */ new Set(); for (const s of sources) { for (const g of s.include) includeGlobs.add(g); for (const f of s.files) explicitFiles.add(f); } if (includeGlobs.size === 0 && explicitFiles.size === 0) includeGlobs.add("**/*"); const t0 = Date.now(); log(`scan: globby(${includeGlobs.size} globs) \u2026`); const fromGlobs = includeGlobs.size ? await globby(Array.from(includeGlobs), { cwd: root, dot: opts.dot, followSymbolicLinks: opts.followSymlinks, onlyFiles: true }) : []; const t1 = Date.now(); const fromFiles = Array.from(explicitFiles).filter((f) => fs2.existsSync(path3.join(root, f))); const universe = Array.from(/* @__PURE__ */ new Set([...fromGlobs.map(normRel), ...fromFiles.map(normRel)])); log(`scan: ${pc.bold(String(universe.length))} candidates (globs: ${fromGlobs.length}, explicit: ${fromFiles.length}) in ${t1 - t0}ms`); return { universe, stats: { includeGlobs: includeGlobs.size, explicitFiles: explicitFiles.size } }; } async function applySmartMergeToConfig(cfg, rootAbs, opts = {}) { const log = opts.log ?? ((m) => console.log(pc.dim(`[smart-merge]`), m)); const label = opts.label ? `[${opts.label}] ` : ""; const root = rootAbs ?? path3.resolve(process.cwd(), cfg.root ?? "."); log(`${label}start (presets: ${cfg.presets?.length ?? 0})`); const tSrc0 = Date.now(); const sources = sourcesFromPresetsAndUser(cfg, root); const tSrc1 = Date.now(); log(`${label}sources: ${sources.length} tier(s) in ${tSrc1 - tSrc0}ms`); const g0 = Date.now(); const mergedPresetGroups = sources.filter((s) => s.name.startsWith("preset:")).map((s) => s.groups).reduce((acc, g) => mergeGroups(acc, g), void 0); cfg.groups = mergeGroups(mergedPresetGroups, sources[sources.length - 1]?.groups) ?? cfg.groups; const g1 = Date.now(); log(`${label}groups: merged in ${g1 - g0}ms (${Object.keys(cfg.groups ?? {}).length} group(s))`); const u = await buildUniverse(root, sources, { dot: !!cfg.dot, followSymlinks: !!cfg.followSymlinks }, log); const s0 = Date.now(); const signals = compileSignals(sources); const s1 = Date.now(); log(`${label}signals: ${signals.length} rule(s) in ${s1 - s0}ms`); const bar = new cliProgress.SingleBar({ hideCursor: true }, cliProgress.Presets.shades_classic); const total = u.universe.length; const tickEvery = Math.max(1, opts.tickEvery ?? 500); const decideT0 = Date.now(); const selectedSet = /* @__PURE__ */ new Set(); if (opts.progress && total > 0) bar.start(total, 0); for (let i = 0; i < total; i++) { const rel = u.universe[i]; const w = decideFor(rel, signals); if (w?.kind === "include") selectedSet.add(rel); if (opts.progress && (i + 1 === total || (i + 1) % tickEvery === 0)) bar.update(i + 1); } if (opts.progress && bar.isActive) bar.stop(); const decideT1 = Date.now(); const selected = Array.from(selectedSet); if (cfg.deterministic) selected.sort((a, b) => a.localeCompare(b)); cfg.backup = { include: cfg.include ?? void 0, exclude: cfg.exclude ?? void 0, presets: cfg.presets ?? void 0, groups: cfg.groups ?? void 0, respectGitignore: cfg.respectGitignore ?? void 0, ignoreFiles: cfg.ignoreFiles ?? void 0, order: cfg.order ?? void 0 }; cfg.include = selected; cfg.exclude = []; cfg.respectGitignore = false; cfg.ignoreFiles = []; cfg.order = ["include", "exclude"]; log(`${label}decide: kept ${pc.bold(String(selected.length))} / ${total} in ${decideT1 - decideT0}ms`); log(`${label}done`); return selected; } // src/config.ts var MODULE_NAME = "zipper"; var SCHEMA_PATH = new URL("../schema/zipconfig.schema.json", import.meta.url); function appendStubIfNoExt(p) { return path4.extname(p) ? p : `${p}.stub`; } function envExpand(s) { return s.replace(/\$\{?([A-Z0-9_]+)\}?/gi, (_, k) => process.env[k] ?? ""); } function parseUnknownConfig(content) { try { return JSON.parse(content); } catch { return YAML2.parse(content); } } async function loadConfig(explicitPath) { const explorer = cosmiconfig(MODULE_NAME, { searchPlaces: [ ".zipconfig", "zip.json", ".zipconfig.json", ".zipconfig.yaml", ".zipconfig.yml", "package.json" ], loaders: { ".yaml": (_fp, content) => YAML2.parse(content), ".yml": (_fp, content) => YAML2.parse(content), noExt: (_fp, content) => parseUnknownConfig(content) // extensionless .zipconfig } }); let result; if (explicitPath) { let fp = appendStubIfNoExt(explicitPath); if (fp.endsWith(".stub")) { const abs = path4.isAbsolute(fp) ? fp : path4.resolve(process.cwd(), fp); let content = null; if (fs3.existsSync(abs)) content = fs3.readFileSync(abs, "utf8"); if (content === null) { const local = path4.join(process.cwd(), "stubs", path4.basename(abs)); if (fs3.existsSync(local)) content = fs3.readFileSync(local, "utf8"); } if (content === null) { const builtin = path4.join(getBuiltinStubDir(), path4.basename(abs)); if (fs3.existsSync(builtin)) content = fs3.readFileSync(builtin, "utf8"); } if (content !== null) { result = { config: parseUnknownConfig(content), filepath: abs }; } else { throw new Error(`Config not found: ${explicitPath} (.stub assumed). Looked in: ${abs}, ./stubs, and built-in stubs.`); } } else { const r = await explorer.load(fp); if (r) result = { config: r.config, filepath: r.filepath }; } } else { const r = await explorer.search(); if (r) result = { config: r.config, filepath: r.filepath }; } const raw = result?.config && result.config.zipper ? result.config.zipper : result?.config; const defaults = { out: "dist.zip", root: ".", include: ["**/*"], // will be replaced by smart-merge materialization exclude: [], // ditto dot: true, followSymlinks: false, order: ["include", "exclude"], presets: [], respectGitignore: false, ignoreFiles: [".zipignore"], deterministic: true, manifest: true }; const merged = { ...defaults, ...raw ?? {} }; merged.out = envExpand(merged.out); if (merged.root) merged.root = envExpand(merged.root); try { const schema = JSON.parse(fs3.readFileSync(SCHEMA_PATH, "utf8")); const ajv = new Ajv({ allowUnionTypes: true, allErrors: true, strict: false }); const authored = JSON.parse(JSON.stringify(merged)); delete authored.$schema; const validate = ajv.compile(schema); if (!validate(authored)) { const msgs = (validate.errors ?? []).map((e) => `${e.instancePath || "<root>"} ${e.message}`).join("; "); throw new Error(`Invalid .zipconfig: ${msgs}`); } } catch (e) { console.warn(`[zipper] config validation warning: ${e.message}`); } const rootAbs = path4.resolve(process.cwd(), merged.root ?? "."); try { const extraIg = await readIgnoreFiles(rootAbs, merged.ignoreFiles); if (extraIg?.length) { merged.exclude = [...merged.exclude ?? [], ...extraIg]; } } catch { } const progress = process.env.ZIPPER_SMARTMERGE_PROGRESS === "1" || process.env.ZIPPER_DEBUG === "1"; await applySmartMergeToConfig(merged, rootAbs, { progress, label: "smart-merge", log: (m) => console.log(pc2.dim(m)), // or keep default tickEvery: 1e3 // optional: fewer updates for very large trees }); return { cfg: merged, filepath: result?.filepath }; } function loadListFile(root, listPath) { if (!listPath) return []; const full = path4.isAbsolute(listPath) ? listPath : path4.join(root, listPath); if (!fs3.existsSync(full)) return []; return fs3.readFileSync(full, "utf8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean); } function readIgnoreFiles(root, files) { const out = []; for (const rel of files ?? []) { const fp = path4.isAbsolute(rel) ? rel : path4.join(root, rel); if (fs3.existsSync(fp)) { out.push(...fs3.readFileSync(fp, "utf8").split(/\r?\n/)); } } return out; } async function loadPreprocessHandlers(cfg, root, extraModuleFromCli) { const modulesFromCfg = Array.isArray(cfg.preprocess?.modules) ? cfg.preprocess.modules : cfg.preprocess?.module ? [cfg.preprocess.module] : []; const modulesFromCli = Array.isArray(extraModuleFromCli) ? extraModuleFromCli : extraModuleFromCli ? [extraModuleFromCli] : []; const modules = [...modulesFromCfg, ...modulesFromCli]; if (!modules.length) return []; const out = []; for (const modPath of modules) { const abs = path4.isAbsolute(modPath) ? modPath : path4.resolve(root, String(modPath)); if (!fs3.existsSync(abs)) { console.warn(pc2.yellow(`preprocess: module not found: ${modPath}`)); continue; } const handlers = await importHandlers(abs); if (!handlers.length) { console.warn(pc2.yellow(`preprocess: no handlers exported by ${modPath}`)); continue; } out.push(...handlers); } return out; } async function importHandlers(absFile) { const lower = absFile.toLowerCase(); let mod; if (lower.endsWith(".ts")) { let esbuild; try { esbuild = await import("esbuild"); } catch { throw new Error(`To use TS preprocess module (${absFile}), install "esbuild".`); } const src = await fs3.promises.readFile(absFile, "utf8"); const out = await esbuild.transform(src, { loader: "ts", format: "esm", sourcemap: "inline", sourcefile: absFile, target: "es2020" }); const dataUrl = "data:text/javascript;base64," + Buffer.from(out.code).toString("base64"); mod = await import(dataUrl); } else if (lower.endsWith(".mjs") || lower.endsWith(".js")) { mod = await import(pathToFileURL(absFile).href); } else { throw new Error(`Unsupported preprocess module extension: ${absFile}`); } const cand = mod?.default ?? mod?.handlers ?? []; if (Array.isArray(cand)) return cand; if (typeof cand === "function") return [cand]; return []; } function mergeGroupFilesIntoInclude(cfg) { const files = Object.values(cfg.groups ?? {}).flatMap((g) => g.files ?? []).map((s) => s.replaceAll("\\", "/").replace(/^\.?\//, "")); if (!files.length) return; cfg.include = [.../* @__PURE__ */ new Set([...cfg.include ?? [], ...files])]; } // src/pack.ts import fs4 from "fs"; import path5 from "path"; import archiver from "archiver"; import { globby as globby2 } from "globby"; import ignore from "ignore"; import pc3 from "picocolors"; import crypto from "crypto"; import cliProgress2 from "cli-progress"; function stableSort(arr) { return [...arr].sort((a, b) => a.localeCompare(b)); } function readGitignore(root) { const gi = path5.join(root, ".gitignore"); if (!fs4.existsSync(gi)) return []; return fs4.readFileSync(gi, "utf8").split(/\r?\n/); } var GLOB_CHARS = /[*?[\]{}()!+@]|\\|[/]?\*\*[/]?/; var looksLikeGlob = (p) => GLOB_CHARS.test(p); var hasAnyGlob = (patterns) => !!patterns?.some(looksLikeGlob); var normRel2 = (p) => p.replaceAll("\\", "/").replace(/^\.?\//, ""); async function buildFileListWithGlob(cfg, extraList, extraIgnore) { const root = path5.resolve(process.cwd(), cfg.root ?? "."); const include = (cfg.include?.length ? cfg.include : ["**/*"]).map(normRel2); if (!hasAnyGlob(include)) { const merged2 = Array.from(/* @__PURE__ */ new Set([...include, ...extraList.map(normRel2)])); const existing = merged2.filter((rel) => { try { return fs4.statSync(path5.join(root, rel)).isFile(); } catch { return false; } }); const ig2 = ignore(); if (cfg.respectGitignore) ig2.add(readGitignore(root)); ig2.add(cfg.exclude ?? []); ig2.add(extraIgnore); let final2 = existing.filter((f) => !ig2.ignores(f)); if (cfg.order?.join(",") === "exclude,include") { final2 = existing; } return cfg.deterministic ? stableSort(final2) : final2; } console.log(include.filter(looksLikeGlob)); const candidates = await globby2(include, { cwd: root, dot: !!cfg.dot, followSymbolicLinks: !!cfg.followSymlinks, onlyFiles: true }); const ig = ignore(); if (cfg.respectGitignore) ig.add(readGitignore(root)); ig.add(cfg.exclude ?? []); ig.add(extraIgnore); const merged = Array.from(/* @__PURE__ */ new Set([...candidates, ...extraList.map(normRel2)])); let final = merged.filter((f) => !ig.ignores(f)); if (cfg.order?.join(",") === "exclude,include" && cfg.include?.length) { const reincluded = await globby2(cfg.include, { cwd: root, dot: !!cfg.dot, followSymbolicLinks: !!cfg.followSymlinks, onlyFiles: true }); const set = new Set(final); for (const f of reincluded) set.add(normRel2(f)); final = [...set]; } return cfg.deterministic ? stableSort(final) : final; } async function buildFileList(cfg, extraList, extraIgnore) { const root = path5.resolve(process.cwd(), cfg.root ?? "."); const include = (cfg.include ?? []).map(normRel2); const merged = Array.from(/* @__PURE__ */ new Set([ ...include, ...extraList.map(normRel2) ])); const ig = ignore(); if (cfg.respectGitignore) ig.add(readGitignore(root)); ig.add(cfg.exclude ?? []); ig.add(extraIgnore); let final = merged.filter((f) => !ig.ignores(f)); if (cfg.order?.join(",") === "exclude,include") { final = merged; } return cfg.deterministic ? stableSort(final) : final; } function zipPathNormalize(p) { return String(p).replaceAll("\\", "/").replace(/^\.?\//, ""); } function isContentLike(x) { return Buffer.isBuffer(x) || x instanceof Uint8Array || typeof x === "string"; } function hashBuffer(buf) { return crypto.createHash("sha256").update(buf).digest("hex"); } async function computeManifestFromEntries(entries) { const sorted = [...entries].sort( (a, b) => zipPathNormalize(a.zipPath).localeCompare(zipPathNormalize(b.zipPath)) ); const files = []; for (const e of sorted) { if ("content" in e && isContentLike(e.content)) { const c = Buffer.isBuffer(e.content) ? e.content : Buffer.from(e.content); files.push({ path: e.zipPath, size: c.length, sha256: hashBuffer(c) }); } else if ("sourcePath" in e && typeof e.sourcePath === "string") { const data = await fs4.promises.readFile(e.sourcePath); files.push({ path: e.zipPath, size: data.length, sha256: hashBuffer(data) }); } } return { createdAt: (/* @__PURE__ */ new Date()).toISOString(), files, totalFiles: files.length, totalBytes: files.reduce((acc, f) => acc + f.size, 0), algorithm: "sha256", version: 1 }; } async function writeZip(cfg, files) { const root = path5.resolve(process.cwd(), cfg.root ?? "."); const outPath = path5.resolve(process.cwd(), cfg.out); fs4.mkdirSync(path5.dirname(outPath), { recursive: true }); const rawEntries = Array.isArray(files) && typeof files[0] === "string" ? files.map((rel) => ({ zipPath: zipPathNormalize(rel), sourcePath: path5.join(root, rel) })) : files.map((e) => { const zipPath = zipPathNormalize(e.zipPath); if ("content" in e && isContentLike(e.content)) { return { zipPath, content: e.content }; } let sp = e.sourcePath; if (typeof sp !== "string") { throw new TypeError(`Invalid sourcePath for ${zipPath}: expected string, got ${typeof sp}`); } const abs = path5.isAbsolute(sp) ? sp : path5.join(root, sp); return { zipPath, sourcePath: abs }; }); const lastWins = /* @__PURE__ */ new Map(); for (const e of rawEntries) lastWins.set(e.zipPath, e); const entries = Array.from(lastWins.values()); let manifest; if (cfg.manifest !== false) { manifest = await computeManifestFromEntries(entries); } const output = fs4.createWriteStream(outPath); const archive = archiver("zip", { zlib: { level: 9 } }); const bar = new cliProgress2.SingleBar({ hideCursor: true }, cliProgress2.Presets.shades_classic); archive.on("progress", (p) => { const total = p.entries.total || entries.length; const processed = p.entries.processed || 0; if (!bar.isActive) bar.start(total, processed); else bar.update(processed); }); archive.on("warning", (err) => console.warn(pc3.yellow("archiver:"), err.message)); archive.on("error", (err) => { throw err; }); archive.pipe(output); if (manifest) { archive.append(JSON.stringify(manifest, null, 2), { name: "MANIFEST.json" }); } for (const e of entries) { if ("content" in e && isContentLike(e.content)) { const c = Buffer.isBuffer(e.content) ? e.content : Buffer.from(e.content); archive.append(c, { name: e.zipPath }); } else { if (typeof e.sourcePath !== "string") { throw new TypeError(`Invalid sourcePath for ${e.zipPath}: not a string`); } try { archive.file(e.sourcePath, { name: e.zipPath, stats: fs4.statSync(e.sourcePath) }); } catch (err) { console.warn(pc3.yellow(`skipping missing file: ${e.zipPath}`)); } } } await archive.finalize(); bar.stop(); if (manifest) { const manifestOut = cfg.manifestPath ? path5.resolve(process.cwd(), cfg.manifestPath) : path5.join(path5.dirname(outPath), path5.basename(outPath, ".zip") + ".manifest.json"); fs4.writeFileSync(manifestOut, JSON.stringify(manifest, null, 2)); const zhash = crypto.createHash("sha256").update(fs4.readFileSync(outPath)).digest("hex"); fs4.writeFileSync(outPath + ".sha256", `${zhash} ${path5.basename(outPath)} `); } return outPath; } export { PRESETS, appendStubIfNoExt, buildFileList, buildFileListWithGlob, getAllPresets, loadConfig, loadListFile, loadPreprocessHandlers, mergeGroupFilesIntoInclude, readIgnoreFiles, resolvePresets, writeZip }; //# sourceMappingURL=index.js.map