* * Produces a clean, distributable archive: excludes docs, build tooling, * markdown, VCS and editor/AI metadata — nothing that documents how the code * was produced ships to the client. * * Exclusion rule (keep in sync with the "no docs/AI files in zips" preference): * - any path segment: .git, .claude, .github, docs, build, node_modules * - any *.md file * - dotfiles at any level except .htaccess */ $src = isset( $argv[1] ) ? rtrim( str_replace( '\\', '/', $argv[1] ), '/' ) : ''; $out = isset( $argv[2] ) ? $argv[2] : ''; if ( '' === $src || '' === $out || ! is_dir( $src ) ) { fwrite( STDERR, "Usage: php build-zip.php \n" ); exit( 1 ); } $excluded_dirs = array( '.git', '.claude', '.github', 'docs', 'build', 'node_modules' ); function is_excluded( $rel, array $excluded_dirs ) { $parts = explode( '/', $rel ); $base = end( $parts ); foreach ( $parts as $seg ) { if ( in_array( $seg, $excluded_dirs, true ) ) { return true; } } // Markdown / doc files. if ( preg_match( '/\.md$/i', $base ) ) { return true; } // Dotfiles (e.g. .gitignore, .DS_Store) except .htaccess. if ( '' !== $base && '.' === $base[0] && '.htaccess' !== $base ) { return true; } return false; } @unlink( $out ); $zip = new ZipArchive(); if ( true !== $zip->open( $out, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { fwrite( STDERR, "Cannot create $out\n" ); exit( 1 ); } $root = basename( $src ); $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $src, FilesystemIterator::SKIP_DOTS ) ); $added = 0; $skip = 0; foreach ( $it as $file ) { $path = str_replace( '\\', '/', $file->getPathname() ); // Path relative to the source's parent, so the archive contains /… $rel_from_src = ltrim( substr( $path, strlen( $src ) ), '/' ); if ( is_excluded( $rel_from_src, $excluded_dirs ) ) { $skip++; continue; } $zip->addFile( $path, $root . '/' . $rel_from_src ); $added++; } $zip->close(); fwrite( STDOUT, "Wrote $out — $added files (skipped $skip)\n" );