pressship 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/dist/auth/login.d.ts +1 -0
  4. package/dist/auth/login.js +39 -0
  5. package/dist/auth/login.js.map +1 -0
  6. package/dist/auth/logout.d.ts +1 -0
  7. package/dist/auth/logout.js +12 -0
  8. package/dist/auth/logout.js.map +1 -0
  9. package/dist/auth/session.d.ts +12 -0
  10. package/dist/auth/session.js +72 -0
  11. package/dist/auth/session.js.map +1 -0
  12. package/dist/auth/whoami.d.ts +17 -0
  13. package/dist/auth/whoami.js +108 -0
  14. package/dist/auth/whoami.js.map +1 -0
  15. package/dist/checks/plugin-check-environment.d.ts +16 -0
  16. package/dist/checks/plugin-check-environment.js +209 -0
  17. package/dist/checks/plugin-check-environment.js.map +1 -0
  18. package/dist/checks/plugin-check.d.ts +16 -0
  19. package/dist/checks/plugin-check.js +176 -0
  20. package/dist/checks/plugin-check.js.map +1 -0
  21. package/dist/checks/readme-validator.d.ts +16 -0
  22. package/dist/checks/readme-validator.js +84 -0
  23. package/dist/checks/readme-validator.js.map +1 -0
  24. package/dist/checks/summary.d.ts +3 -0
  25. package/dist/checks/summary.js +73 -0
  26. package/dist/checks/summary.js.map +1 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +66 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/package/archive.d.ts +12 -0
  31. package/dist/package/archive.js +64 -0
  32. package/dist/package/archive.js.map +1 -0
  33. package/dist/package/ignore.d.ts +2 -0
  34. package/dist/package/ignore.js +48 -0
  35. package/dist/package/ignore.js.map +1 -0
  36. package/dist/plugin/discover.d.ts +2 -0
  37. package/dist/plugin/discover.js +67 -0
  38. package/dist/plugin/discover.js.map +1 -0
  39. package/dist/plugin/headers.d.ts +3 -0
  40. package/dist/plugin/headers.js +54 -0
  41. package/dist/plugin/headers.js.map +1 -0
  42. package/dist/plugin/readme.d.ts +3 -0
  43. package/dist/plugin/readme.js +103 -0
  44. package/dist/plugin/readme.js.map +1 -0
  45. package/dist/svn/release.d.ts +16 -0
  46. package/dist/svn/release.js +147 -0
  47. package/dist/svn/release.js.map +1 -0
  48. package/dist/types.d.ts +55 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/ui.d.ts +15 -0
  52. package/dist/ui.js +50 -0
  53. package/dist/ui.js.map +1 -0
  54. package/dist/utils/format.d.ts +2 -0
  55. package/dist/utils/format.js +21 -0
  56. package/dist/utils/format.js.map +1 -0
  57. package/dist/utils/paths.d.ts +9 -0
  58. package/dist/utils/paths.js +48 -0
  59. package/dist/utils/paths.js.map +1 -0
  60. package/dist/utils/slug.d.ts +2 -0
  61. package/dist/utils/slug.js +15 -0
  62. package/dist/utils/slug.js.map +1 -0
  63. package/dist/wordpress-org/submit.d.ts +13 -0
  64. package/dist/wordpress-org/submit.js +169 -0
  65. package/dist/wordpress-org/submit.js.map +1 -0
  66. package/package.json +56 -0
package/dist/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { login } from "./auth/login.js";
4
+ import { logout } from "./auth/logout.js";
5
+ import { whoami } from "./auth/whoami.js";
6
+ import { submit } from "./wordpress-org/submit.js";
7
+ import { release } from "./svn/release.js";
8
+ const program = new Command();
9
+ program
10
+ .name("pressship")
11
+ .description("Submit and release WordPress.org plugins from the command line.")
12
+ .version("0.1.0");
13
+ program
14
+ .command("login")
15
+ .description("Open a browser and save a WordPress.org login session.")
16
+ .action(run(login));
17
+ program
18
+ .command("logout")
19
+ .description("Remove the saved WordPress.org browser session.")
20
+ .action(run(logout));
21
+ program
22
+ .command("whoami")
23
+ .description("Print the WordPress.org username for the saved login session.")
24
+ .option("--json", "Print account details as JSON")
25
+ .action((options) => run(() => whoami(options))());
26
+ program
27
+ .command("submit")
28
+ .description("Validate, package, and submit a plugin zip to WordPress.org for review.")
29
+ .argument("[plugin-path]", "Path to the WordPress plugin directory")
30
+ .option("--dry-run", "Run validation and packaging without uploading")
31
+ .option("--skip-plugin-check", "Skip `wp plugin check`")
32
+ .option("--skip-readme-validator", "Skip the remote WordPress.org readme validator")
33
+ .option("--output-dir <path>", "Directory where the submission zip should be written")
34
+ .option("--wp-path <path>", "WordPress installation path for `wp plugin check`")
35
+ .option("--ignore <glob>", "Ignore files in the package; repeat for multiple globs", collectValues, [])
36
+ .option("-y, --yes", "Continue without interactive confirmations where possible")
37
+ .action((pluginPath, options) => run(() => submit(pluginPath, options))());
38
+ program
39
+ .command("release")
40
+ .description("Publish an approved plugin release to WordPress.org SVN trunk and tags.")
41
+ .argument("[plugin-path]", "Path to the WordPress plugin directory")
42
+ .option("--slug <slug>", "Approved WordPress.org plugin slug")
43
+ .option("--version <version>", "Version tag to create")
44
+ .option("--svn-dir <path>", "Local SVN working copy directory")
45
+ .option("--username <username>", "WordPress.org SVN username")
46
+ .option("-m, --message <message>", "SVN commit message")
47
+ .option("--ignore <glob>", "Ignore files in the SVN release; repeat for multiple globs", collectValues, [])
48
+ .option("--dry-run", "Print the SVN command plan without changing SVN")
49
+ .option("-y, --yes", "Commit without the final confirmation prompt")
50
+ .action((pluginPath, options) => run(() => release(pluginPath, options))());
51
+ await program.parseAsync(process.argv);
52
+ function run(action) {
53
+ return async () => {
54
+ try {
55
+ await action();
56
+ }
57
+ catch (error) {
58
+ console.error(error instanceof Error ? error.message : String(error));
59
+ process.exitCode = 1;
60
+ }
61
+ };
62
+ }
63
+ function collectValues(value, previous) {
64
+ return [...previous, value];
65
+ }
66
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { PackageResult, PluginProject } from "../types.js";
2
+ export type PackageOptions = {
3
+ outputDir?: string;
4
+ ignore?: string[];
5
+ };
6
+ export type StageResult = {
7
+ path: string;
8
+ files: string[];
9
+ };
10
+ export declare function createPluginZip(project: PluginProject, options?: PackageOptions): Promise<PackageResult>;
11
+ export declare function listPackageFiles(rootDir: string, options?: Pick<PackageOptions, "ignore">): Promise<string[]>;
12
+ export declare function stagePluginDirectory(project: PluginProject, options?: PackageOptions): Promise<StageResult>;
@@ -0,0 +1,64 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { cp, mkdir, rm, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import fg from "fast-glob";
5
+ import { ZipFile } from "yazl";
6
+ import { getDefaultBuildDir } from "../utils/paths.js";
7
+ import { createIgnoreFilter } from "./ignore.js";
8
+ const maxSubmissionSizeBytes = 10 * 1024 * 1024;
9
+ export async function createPluginZip(project, options = {}) {
10
+ const outputDir = path.resolve(options.outputDir ?? getDefaultBuildDir());
11
+ await mkdir(outputDir, { recursive: true });
12
+ const zipPath = path.join(outputDir, `${project.slug}.zip`);
13
+ const files = await listPackageFiles(project.rootDir, { ignore: options.ignore });
14
+ await writeZip(project.rootDir, project.slug, files, zipPath);
15
+ const zipStat = await stat(zipPath);
16
+ if (zipStat.size > maxSubmissionSizeBytes) {
17
+ throw new Error(`Submission zip is larger than 10 MB (${zipStat.size} bytes). WordPress.org requires uploads under 10 MB.`);
18
+ }
19
+ return {
20
+ zipPath,
21
+ sizeBytes: zipStat.size,
22
+ files,
23
+ topLevelFolder: project.slug
24
+ };
25
+ }
26
+ export async function listPackageFiles(rootDir, options = {}) {
27
+ const includeFile = await createIgnoreFilter(rootDir, options.ignore);
28
+ const files = await fg("**/*", {
29
+ cwd: rootDir,
30
+ onlyFiles: true,
31
+ dot: true,
32
+ unique: true
33
+ });
34
+ return files.filter(includeFile).sort();
35
+ }
36
+ export async function stagePluginDirectory(project, options = {}) {
37
+ const stageRoot = path.join(path.resolve(options.outputDir ?? getDefaultBuildDir()), "plugin-check");
38
+ const stagePath = path.join(stageRoot, project.slug);
39
+ const files = await listPackageFiles(project.rootDir, { ignore: options.ignore });
40
+ await rm(stagePath, { recursive: true, force: true });
41
+ await mkdir(stagePath, { recursive: true });
42
+ for (const file of files) {
43
+ const source = path.join(project.rootDir, file);
44
+ const destination = path.join(stagePath, file);
45
+ await mkdir(path.dirname(destination), { recursive: true });
46
+ await cp(source, destination);
47
+ }
48
+ return { path: stagePath, files };
49
+ }
50
+ async function writeZip(rootDir, topLevelFolder, files, zipPath) {
51
+ await new Promise((resolve, reject) => {
52
+ const output = createWriteStream(zipPath);
53
+ const archive = new ZipFile();
54
+ output.on("close", resolve);
55
+ output.on("error", reject);
56
+ archive.outputStream.on("error", reject);
57
+ archive.outputStream.pipe(output);
58
+ for (const relativePath of files) {
59
+ archive.addFile(path.join(rootDir, relativePath), path.posix.join(topLevelFolder, relativePath.split(path.sep).join("/")));
60
+ }
61
+ archive.end();
62
+ });
63
+ }
64
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/package/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const defaultIgnorePatterns: string[];
2
+ export declare function createIgnoreFilter(rootDir: string, extraPatterns?: string[]): Promise<(relativePath: string) => boolean>;
@@ -0,0 +1,48 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import ignore from "ignore";
4
+ import { pathExists } from "../utils/paths.js";
5
+ export const defaultIgnorePatterns = [
6
+ ".DS_Store",
7
+ ".git",
8
+ ".git/**",
9
+ ".gitignore",
10
+ ".github",
11
+ ".github/**",
12
+ ".idea",
13
+ ".idea/**",
14
+ ".vscode",
15
+ ".vscode/**",
16
+ ".env",
17
+ ".env.*",
18
+ "node_modules",
19
+ "node_modules/**",
20
+ "dist",
21
+ "dist/**",
22
+ "build",
23
+ "build/**",
24
+ "coverage",
25
+ "coverage/**",
26
+ "tests",
27
+ "tests/**",
28
+ "*.log",
29
+ "*.zip",
30
+ ".pressportignore",
31
+ ".pressshipignore"
32
+ ];
33
+ export async function createIgnoreFilter(rootDir, extraPatterns = []) {
34
+ const matcher = ignore().add(defaultIgnorePatterns).add(extraPatterns);
35
+ const legacyIgnoreFile = path.join(rootDir, ".pressportignore");
36
+ const ignoreFile = path.join(rootDir, ".pressshipignore");
37
+ if (pathExists(legacyIgnoreFile)) {
38
+ matcher.add(await readFile(legacyIgnoreFile, "utf8"));
39
+ }
40
+ if (pathExists(ignoreFile)) {
41
+ matcher.add(await readFile(ignoreFile, "utf8"));
42
+ }
43
+ return (relativePath) => {
44
+ const normalized = relativePath.split(path.sep).join("/");
45
+ return !matcher.ignores(normalized);
46
+ };
47
+ }
48
+ //# sourceMappingURL=ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore.js","sourceRoot":"","sources":["../../src/package/ignore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,WAAW;IACX,MAAM;IACN,SAAS;IACT,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,OAAO;IACP,UAAU;IACV,SAAS;IACT,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,cAAc;IACd,iBAAiB;IACjB,MAAM;IACN,SAAS;IACT,OAAO;IACP,UAAU;IACV,UAAU;IACV,aAAa;IACb,OAAO;IACP,UAAU;IACV,OAAO;IACP,OAAO;IACP,kBAAkB;IAClB,kBAAkB;CACnB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAe,EAAE,gBAA0B,EAAE;IACpF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACvE,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IAE1D,IAAI,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,CAAC,YAAoB,EAAW,EAAE;QACvC,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1D,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PluginProject } from "../types.js";
2
+ export declare function discoverPluginProject(inputPath: string): Promise<PluginProject>;
@@ -0,0 +1,67 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import fg from "fast-glob";
4
+ import { assertPluginHeaders, parsePluginHeaders } from "./headers.js";
5
+ import { parseReadme } from "./readme.js";
6
+ import { inferSlug } from "../utils/slug.js";
7
+ const ignoredDirectories = ["node_modules", "vendor", ".git", "dist", "build", "tests"];
8
+ export async function discoverPluginProject(inputPath) {
9
+ const rootDir = path.resolve(inputPath);
10
+ const mainFile = await findMainPluginFile(rootDir);
11
+ const mainContents = await readFile(mainFile, "utf8");
12
+ const headers = assertPluginHeaders(parsePluginHeaders(mainContents));
13
+ const readmePath = await findReadme(rootDir);
14
+ const readme = readmePath ? parseReadme(await readFile(readmePath, "utf8")) : undefined;
15
+ const slug = inferSlug(headers.pluginName, headers.textDomain);
16
+ return {
17
+ rootDir,
18
+ mainFile,
19
+ headers,
20
+ readmePath,
21
+ readme,
22
+ slug,
23
+ version: headers.version
24
+ };
25
+ }
26
+ async function findMainPluginFile(rootDir) {
27
+ const phpFiles = await fg("**/*.php", {
28
+ cwd: rootDir,
29
+ absolute: true,
30
+ onlyFiles: true,
31
+ ignore: ignoredDirectories.map((directory) => `${directory}/**`)
32
+ });
33
+ const candidates = [];
34
+ for (const file of phpFiles) {
35
+ const contents = await readFile(file, "utf8");
36
+ const headers = parsePluginHeaders(contents);
37
+ if (headers.pluginName) {
38
+ candidates.push({ file, score: scoreMainFile(rootDir, file) });
39
+ }
40
+ }
41
+ candidates.sort((a, b) => b.score - a.score);
42
+ if (!candidates[0]) {
43
+ throw new Error(`No plugin main file found in ${rootDir}.`);
44
+ }
45
+ return candidates[0].file;
46
+ }
47
+ async function findReadme(rootDir) {
48
+ const matches = await fg(["readme.txt", "README.txt"], {
49
+ cwd: rootDir,
50
+ absolute: true,
51
+ onlyFiles: true,
52
+ caseSensitiveMatch: false
53
+ });
54
+ return matches[0];
55
+ }
56
+ function scoreMainFile(rootDir, file) {
57
+ const relative = path.relative(rootDir, file);
58
+ let score = 0;
59
+ if (!relative.includes(path.sep)) {
60
+ score += 10;
61
+ }
62
+ if (path.basename(file, ".php") === path.basename(rootDir)) {
63
+ score += 5;
64
+ }
65
+ return score;
66
+ }
67
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/plugin/discover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PluginHeaders } from "../types.js";
2
+ export declare function parsePluginHeaders(contents: string): Partial<PluginHeaders>;
3
+ export declare function assertPluginHeaders(headers: Partial<PluginHeaders>): PluginHeaders;
@@ -0,0 +1,54 @@
1
+ const headerMap = new Map([
2
+ ["Plugin Name", "pluginName"],
3
+ ["Plugin URI", "pluginUri"],
4
+ ["Description", "description"],
5
+ ["Version", "version"],
6
+ ["Author", "author"],
7
+ ["Author URI", "authorUri"],
8
+ ["Text Domain", "textDomain"],
9
+ ["Domain Path", "domainPath"],
10
+ ["Requires at least", "requiresAtLeast"],
11
+ ["Requires PHP", "requiresPhp"],
12
+ ["Update URI", "updateUri"],
13
+ ["License", "license"],
14
+ ["License URI", "licenseUri"]
15
+ ]);
16
+ export function parsePluginHeaders(contents) {
17
+ const headerBlock = contents.slice(0, 8192);
18
+ const headers = {};
19
+ for (const [label, key] of headerMap) {
20
+ const pattern = new RegExp(`^[ \\t/*#@]*${escapeRegExp(label)}\\s*:\\s*(.+?)\\s*$`, "im");
21
+ const match = headerBlock.match(pattern);
22
+ if (match?.[1]) {
23
+ headers[key] = normalizeHeaderValue(match[1]);
24
+ }
25
+ }
26
+ return headers;
27
+ }
28
+ export function assertPluginHeaders(headers) {
29
+ if (!headers.pluginName) {
30
+ throw new Error("No WordPress plugin header found. Expected a PHP file with `Plugin Name:`.");
31
+ }
32
+ return {
33
+ pluginName: headers.pluginName,
34
+ pluginUri: headers.pluginUri,
35
+ description: headers.description,
36
+ version: headers.version,
37
+ author: headers.author,
38
+ authorUri: headers.authorUri,
39
+ textDomain: headers.textDomain,
40
+ domainPath: headers.domainPath,
41
+ requiresAtLeast: headers.requiresAtLeast,
42
+ requiresPhp: headers.requiresPhp,
43
+ updateUri: headers.updateUri,
44
+ license: headers.license,
45
+ licenseUri: headers.licenseUri
46
+ };
47
+ }
48
+ function normalizeHeaderValue(value) {
49
+ return value.replace(/\s+\*\/$/, "").trim();
50
+ }
51
+ function escapeRegExp(value) {
52
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
53
+ }
54
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/plugin/headers.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG,IAAI,GAAG,CAA8B;IACrD,CAAC,aAAa,EAAE,YAAY,CAAC;IAC7B,CAAC,YAAY,EAAE,WAAW,CAAC;IAC3B,CAAC,aAAa,EAAE,aAAa,CAAC;IAC9B,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,YAAY,EAAE,WAAW,CAAC;IAC3B,CAAC,aAAa,EAAE,YAAY,CAAC;IAC7B,CAAC,aAAa,EAAE,YAAY,CAAC;IAC7B,CAAC,mBAAmB,EAAE,iBAAiB,CAAC;IACxC,CAAC,cAAc,EAAE,aAAa,CAAC;IAC/B,CAAC,YAAY,EAAE,WAAW,CAAC;IAC3B,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,aAAa,EAAE,YAAY,CAAC;CAC9B,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAA2B,EAAE,CAAC;IAE3C,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,eAAe,YAAY,CAAC,KAAK,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA+B;IACjE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAChG,CAAC;IAED,OAAO;QACL,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Finding, ReadmeMetadata } from "../types.js";
2
+ export declare function parseReadme(contents: string): ReadmeMetadata;
3
+ export declare function validateReadmeLocally(contents: string, metadata: ReadmeMetadata): Finding[];
@@ -0,0 +1,103 @@
1
+ const readmeHeaderMap = {
2
+ contributors: "contributors",
3
+ tags: "tags",
4
+ "requires at least": "requiresAtLeast",
5
+ "tested up to": "testedUpTo",
6
+ "stable tag": "stableTag",
7
+ "requires php": "requiresPhp",
8
+ license: "license",
9
+ "license uri": "licenseUri"
10
+ };
11
+ export function parseReadme(contents) {
12
+ const lines = contents.split(/\r?\n/);
13
+ const metadata = {};
14
+ const title = lines.find((line) => /^===\s+.+?\s+===$/.test(line.trim()));
15
+ if (title) {
16
+ metadata.name = title.replace(/^===\s+/, "").replace(/\s+===$/, "").trim();
17
+ }
18
+ for (const line of lines) {
19
+ const match = line.match(/^([^:\n]+):\s*(.*?)\s*$/);
20
+ if (!match) {
21
+ continue;
22
+ }
23
+ const key = readmeHeaderMap[match[1].trim().toLowerCase()];
24
+ if (!key) {
25
+ continue;
26
+ }
27
+ const value = match[2].trim();
28
+ if (key === "contributors" || key === "tags") {
29
+ metadata[key] = splitCsv(value);
30
+ }
31
+ else {
32
+ metadata[key] = value;
33
+ }
34
+ }
35
+ return metadata;
36
+ }
37
+ export function validateReadmeLocally(contents, metadata) {
38
+ const findings = [];
39
+ if (!metadata.name) {
40
+ findings.push({
41
+ severity: "error",
42
+ code: "readme.missing_title",
43
+ message: "readme.txt must start with a plugin title like `=== Plugin Name ===`."
44
+ });
45
+ }
46
+ if (!metadata.contributors || metadata.contributors.length === 0) {
47
+ findings.push({
48
+ severity: "warning",
49
+ code: "readme.missing_contributors",
50
+ message: "readme.txt should include a Contributors header."
51
+ });
52
+ }
53
+ if (!metadata.tags || metadata.tags.length === 0) {
54
+ findings.push({
55
+ severity: "warning",
56
+ code: "readme.missing_tags",
57
+ message: "readme.txt should include Tags for the plugin directory."
58
+ });
59
+ }
60
+ else if (metadata.tags.length > 5) {
61
+ findings.push({
62
+ severity: "error",
63
+ code: "readme.too_many_tags",
64
+ message: "WordPress.org allows at most 5 readme tags."
65
+ });
66
+ }
67
+ if (!metadata.stableTag) {
68
+ findings.push({
69
+ severity: "error",
70
+ code: "readme.missing_stable_tag",
71
+ message: "readme.txt must include a Stable tag header."
72
+ });
73
+ }
74
+ if (!metadata.requiresAtLeast) {
75
+ findings.push({
76
+ severity: "warning",
77
+ code: "readme.missing_requires_at_least",
78
+ message: "readme.txt should include a Requires at least header."
79
+ });
80
+ }
81
+ if (!metadata.testedUpTo) {
82
+ findings.push({
83
+ severity: "warning",
84
+ code: "readme.missing_tested_up_to",
85
+ message: "readme.txt should include a Tested up to header."
86
+ });
87
+ }
88
+ if (!/^==\s+Description\s+==/im.test(contents)) {
89
+ findings.push({
90
+ severity: "warning",
91
+ code: "readme.missing_description_section",
92
+ message: "readme.txt should include a Description section."
93
+ });
94
+ }
95
+ return findings;
96
+ }
97
+ function splitCsv(value) {
98
+ return value
99
+ .split(",")
100
+ .map((item) => item.trim())
101
+ .filter(Boolean);
102
+ }
103
+ //# sourceMappingURL=readme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readme.js","sourceRoot":"","sources":["../../src/plugin/readme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { CommandPlan } from "../types.js";
3
+ declare const releaseOptionsSchema: z.ZodObject<{
4
+ slug: z.ZodOptional<z.ZodString>;
5
+ version: z.ZodOptional<z.ZodString>;
6
+ svnDir: z.ZodOptional<z.ZodString>;
7
+ username: z.ZodOptional<z.ZodString>;
8
+ message: z.ZodOptional<z.ZodString>;
9
+ dryRun: z.ZodDefault<z.ZodBoolean>;
10
+ yes: z.ZodDefault<z.ZodBoolean>;
11
+ ignore: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ }, z.core.$strip>;
13
+ export type ReleaseOptions = z.input<typeof releaseOptionsSchema>;
14
+ export declare function release(pluginPath: string | undefined, rawOptions: ReleaseOptions): Promise<void>;
15
+ export declare function createReleaseCommandPlan(slug: string, svnDir: string, version: string, message: string, username?: string): CommandPlan[];
16
+ export {};
@@ -0,0 +1,147 @@
1
+ import { confirm, input } from "@inquirer/prompts";
2
+ import { execa } from "execa";
3
+ import { cp, mkdir, readdir, rm } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+ import { listPackageFiles } from "../package/archive.js";
7
+ import { discoverPluginProject } from "../plugin/discover.js";
8
+ import { ui } from "../ui.js";
9
+ import { pathExists } from "../utils/paths.js";
10
+ const releaseOptionsSchema = z.object({
11
+ slug: z.string().optional(),
12
+ version: z.string().optional(),
13
+ svnDir: z.string().optional(),
14
+ username: z.string().optional(),
15
+ message: z.string().optional(),
16
+ dryRun: z.boolean().default(false),
17
+ yes: z.boolean().default(false),
18
+ ignore: z.array(z.string()).default([])
19
+ });
20
+ export async function release(pluginPath, rawOptions) {
21
+ const options = releaseOptionsSchema.parse(rawOptions);
22
+ ui.intro(options.dryRun ? "Dry-run SVN release" : "Release plugin to WordPress.org SVN");
23
+ const rootDir = path.resolve(pluginPath ?? (await input({ message: "Plugin directory", default: process.cwd() })));
24
+ const project = await ui.task("Discovering WordPress plugin", () => discoverPluginProject(rootDir), (value) => `Discovered ${value.headers.pluginName}`);
25
+ const slug = options.slug ?? project.slug;
26
+ const version = options.version ?? project.version;
27
+ if (!version) {
28
+ throw new Error("Could not infer a plugin version. Pass --version or add Version to the plugin header.");
29
+ }
30
+ const svnDir = path.resolve(options.svnDir ?? path.join(process.cwd(), ".pressship-svn", slug));
31
+ const message = options.message ?? `Release ${slug} ${version}`;
32
+ const plan = createReleaseCommandPlan(slug, svnDir, version, message, options.username);
33
+ ui.section("Release");
34
+ ui.keyValue("SVN", ui.path(`https://plugins.svn.wordpress.org/${slug}`));
35
+ ui.keyValue("Working copy", ui.path(svnDir));
36
+ ui.keyValue("Version", ui.value(version));
37
+ if (options.dryRun) {
38
+ ui.section("Dry-run command plan");
39
+ for (const command of plan) {
40
+ console.log(` ${ui.muted(formatCommand(command))}`);
41
+ }
42
+ return;
43
+ }
44
+ await ui.task("Preparing SVN working copy", () => ensureWorkingCopy(slug, svnDir, options.username));
45
+ await ui.task("Syncing plugin files to trunk", () => syncTrunk(rootDir, path.join(svnDir, "trunk"), options.ignore));
46
+ await ui.task("Adding changed files to SVN", () => runSvn(["add", "--force", "trunk"], svnDir));
47
+ await ui.task("Removing deleted files from SVN", () => deleteMissingFiles(svnDir));
48
+ await ui.task(`Creating tag ${version}`, () => createTag(version, svnDir));
49
+ const status = await runSvn(["status"], svnDir, { capture: true });
50
+ ui.section("SVN status");
51
+ console.log(status || ui.muted("No SVN changes detected."));
52
+ if (!status.trim()) {
53
+ return;
54
+ }
55
+ if (!options.yes) {
56
+ const shouldCommit = await confirm({ message: "Commit these SVN changes?", default: false });
57
+ if (!shouldCommit) {
58
+ throw new Error("SVN release cancelled before commit.");
59
+ }
60
+ }
61
+ await ui.task("Committing SVN release", () => runSvn(["commit", "-m", message, ...usernameArgs(options.username)], svnDir));
62
+ }
63
+ export function createReleaseCommandPlan(slug, svnDir, version, message, username) {
64
+ return [
65
+ {
66
+ command: "svn",
67
+ args: ["checkout", `https://plugins.svn.wordpress.org/${slug}`, svnDir, ...usernameArgs(username)]
68
+ },
69
+ { command: "svn", args: ["add", "--force", "trunk"], cwd: svnDir },
70
+ { command: "svn", args: ["copy", "trunk", `tags/${version}`], cwd: svnDir },
71
+ { command: "svn", args: ["status"], cwd: svnDir },
72
+ { command: "svn", args: ["commit", "-m", message, ...usernameArgs(username)], cwd: svnDir }
73
+ ];
74
+ }
75
+ async function ensureWorkingCopy(slug, svnDir, username) {
76
+ await mkdir(path.dirname(svnDir), { recursive: true });
77
+ try {
78
+ await runSvn(["info"], svnDir, { capture: true });
79
+ await runSvn(["update"], svnDir);
80
+ }
81
+ catch {
82
+ await runSvn(["checkout", `https://plugins.svn.wordpress.org/${slug}`, svnDir, ...usernameArgs(username)], process.cwd());
83
+ }
84
+ }
85
+ async function syncTrunk(pluginRoot, trunkDir, ignore) {
86
+ await mkdir(trunkDir, { recursive: true });
87
+ await emptyDirectory(trunkDir);
88
+ const files = await listPackageFiles(pluginRoot, { ignore });
89
+ for (const file of files) {
90
+ const source = path.join(pluginRoot, file);
91
+ const destination = path.join(trunkDir, file);
92
+ await mkdir(path.dirname(destination), { recursive: true });
93
+ await cp(source, destination);
94
+ }
95
+ }
96
+ async function emptyDirectory(directory) {
97
+ const entries = await readdir(directory, { withFileTypes: true });
98
+ for (const entry of entries) {
99
+ if (entry.name === ".svn") {
100
+ continue;
101
+ }
102
+ await rm(path.join(directory, entry.name), { recursive: true, force: true });
103
+ }
104
+ }
105
+ async function deleteMissingFiles(svnDir) {
106
+ const status = await runSvn(["status"], svnDir, { capture: true });
107
+ const missing = status
108
+ .split(/\r?\n/)
109
+ .filter((line) => line.startsWith("!"))
110
+ .map((line) => line.slice(8).trim())
111
+ .filter(Boolean);
112
+ for (const file of missing) {
113
+ await runSvn(["delete", file], svnDir);
114
+ }
115
+ }
116
+ async function createTag(version, svnDir) {
117
+ const tagPath = path.join(svnDir, "tags", version);
118
+ if (pathExists(tagPath)) {
119
+ throw new Error(`SVN tag tags/${version} already exists. Choose a new version; tags should not be overwritten.`);
120
+ }
121
+ await mkdir(path.dirname(tagPath), { recursive: true });
122
+ await runSvn(["copy", "trunk", `tags/${version}`], svnDir);
123
+ }
124
+ async function runSvn(args, cwd, options = {}) {
125
+ const result = await execa("svn", args, {
126
+ cwd,
127
+ reject: false,
128
+ stdout: options.capture ? "pipe" : "inherit",
129
+ stderr: options.capture ? "pipe" : "inherit"
130
+ });
131
+ if (result.exitCode !== 0) {
132
+ throw new Error(result.stderr || result.stdout || `svn ${args.join(" ")} failed.`);
133
+ }
134
+ return `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
135
+ }
136
+ function usernameArgs(username) {
137
+ return username ? ["--username", username] : [];
138
+ }
139
+ function formatCommand(command) {
140
+ const prefix = command.cwd ? `(cd ${command.cwd} && ` : "";
141
+ const suffix = command.cwd ? ")" : "";
142
+ return `${prefix}${command.command} ${command.args.map(quoteArg).join(" ")}${suffix}`;
143
+ }
144
+ function quoteArg(value) {
145
+ return /\s/.test(value) ? JSON.stringify(value) : value;
146
+ }
147
+ //# sourceMappingURL=release.js.map