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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pressship contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # Pressship
2
+
3
+ Pressship is a CLI for preparing, submitting, and releasing WordPress.org plugins.
4
+
5
+ ```bash
6
+ npx pressship login
7
+ npx pressship whoami
8
+ npx pressship logout
9
+ npx pressship submit ./my-plugin
10
+ npx pressship release ./my-plugin --slug my-plugin --username WpOrgUser
11
+ ```
12
+
13
+ ## What It Does
14
+
15
+ - Opens a real browser for WordPress.org login and stores only the browser session.
16
+ - Installs Playwright Chromium automatically the first time browser automation needs it.
17
+ - Discovers the main plugin file from WordPress plugin headers.
18
+ - Parses `readme.txt` and runs local readme checks.
19
+ - Validates `readme.txt` against the WordPress.org readme validator.
20
+ - Builds a WordPress-installable zip with a single top-level plugin folder.
21
+ - Runs `wp plugin check` when WP-CLI and the Plugin Check plugin are available.
22
+ - Uploads the zip to the WordPress.org "Add your plugin" form after confirmation.
23
+ - Publishes approved plugin updates to WordPress.org SVN trunk and tags.
24
+
25
+ ## Requirements
26
+
27
+ - Node.js 20 or newer.
28
+ - A WordPress.org account.
29
+ - Internet access the first time Pressship installs its browser automation runtime.
30
+ - PHP for Pressship's managed Plugin Check runner when system WP-CLI is unavailable.
31
+ - `svn` for `pressship release`.
32
+
33
+ ## Development
34
+
35
+ ```bash
36
+ npm install
37
+ npm run dev -- --help
38
+ npm run typecheck
39
+ npm test
40
+ npm run build
41
+ ```
42
+
43
+ Until the package is published, use the local dev command:
44
+
45
+ ```bash
46
+ npm run dev -- login
47
+ npm run dev -- whoami
48
+ npm run dev -- logout
49
+ npm run dev -- submit ./my-plugin --dry-run
50
+ npm run dev -- release ./my-plugin --dry-run
51
+ ```
52
+
53
+ ## Commands
54
+
55
+ ### `pressship login`
56
+
57
+ Opens `login.wordpress.org` in a browser. Complete login manually, including any
58
+ two-factor or account checks. Pressship waits for the page to show `Logged in user:`,
59
+ verifies the WordPress.org account, saves Playwright storage state under your user
60
+ config directory, and closes the browser. It does not store your WordPress.org
61
+ password.
62
+
63
+ If Chromium is not available yet, Pressship installs it automatically and retries
64
+ the browser launch. As a fallback, run `npm run browsers:install` locally or
65
+ `npx playwright install chromium` for a published install.
66
+
67
+ ### `pressship whoami`
68
+
69
+ Prints the WordPress.org username for the saved browser session:
70
+
71
+ ```bash
72
+ pressship whoami
73
+ pressship whoami --json
74
+ ```
75
+
76
+ If no valid session is available, run `pressship login`.
77
+
78
+ ### `pressship logout`
79
+
80
+ Removes the saved WordPress.org browser session from Pressship's local config:
81
+
82
+ ```bash
83
+ pressship logout
84
+ ```
85
+
86
+ This does not change your WordPress.org account password or revoke other browser
87
+ sessions. It only makes Pressship forget the saved session.
88
+
89
+ ### `pressship submit [plugin-path]`
90
+
91
+ Runs the full new-plugin review preparation flow:
92
+
93
+ 1. Detect the plugin main file and read `readme.txt`.
94
+ 2. Run local readme checks.
95
+ 3. Submit `readme.txt` content to the WordPress.org readme validator.
96
+ 4. Create a submission zip.
97
+ 5. Run Plugin Check against the staged package contents.
98
+ 6. Ask for a final confirmation.
99
+ 7. Upload the zip to `https://wordpress.org/plugins/developers/add/`.
100
+
101
+ Useful options:
102
+
103
+ ```bash
104
+ pressship submit ./my-plugin --dry-run
105
+ pressship submit ./my-plugin --skip-plugin-check
106
+ pressship submit ./my-plugin --skip-readme-validator
107
+ pressship submit ./my-plugin --wp-path /path/to/wordpress
108
+ pressship submit ./my-plugin --ignore "assets/**/*.mp4"
109
+ pressship submit ./my-plugin --output-dir ./build
110
+ ```
111
+
112
+ By default, Pressship prepares a managed Plugin Check environment in its cache. It
113
+ downloads WP-CLI when needed, downloads WordPress core, downloads the Plugin Check
114
+ plugin, and runs `wp plugin check` with the correct `--path` and `--require`
115
+ arguments. Pass `--wp-path /path/to/wordpress` only if you want to use your own
116
+ WordPress installation instead.
117
+
118
+ The WordPress.org submission form is not a documented public API. Pressship uses
119
+ browser automation and fails loudly, with a debug screenshot, if the form changes.
120
+
121
+ ### `pressship release [plugin-path]`
122
+
123
+ Publishes an approved plugin release to WordPress.org SVN:
124
+
125
+ 1. Checkout or update `https://plugins.svn.wordpress.org/<slug>`.
126
+ 2. Sync the packaged plugin files into `trunk/`.
127
+ 3. Create `tags/<version>` from trunk.
128
+ 4. Show `svn status`.
129
+ 5. Commit after confirmation.
130
+
131
+ Useful options:
132
+
133
+ ```bash
134
+ pressship release ./my-plugin --slug my-plugin --username WpOrgUser
135
+ pressship release ./my-plugin --version 1.2.3 --message "Release 1.2.3"
136
+ pressship release ./my-plugin --ignore "assets/**/*.mp4"
137
+ pressship release ./my-plugin --dry-run
138
+ ```
139
+
140
+ ## Packaging Rules
141
+
142
+ Pressship excludes common development artifacts by default, including `.git`,
143
+ `.gitignore`, `.github`, `node_modules`, `dist`, `build`, `coverage`, `tests`,
144
+ `.env*`, log files, existing zips, `.pressshipignore`, and legacy `.pressportignore`.
145
+
146
+ Add a `.pressshipignore` file in your plugin directory for project-specific
147
+ exclusions.
148
+
149
+ You can also ignore files per command with repeatable glob patterns:
150
+
151
+ ```bash
152
+ pressship submit ./my-plugin --ignore "assets/**/*.mp4" --ignore "docs/raw/**"
153
+ pressship release ./my-plugin --ignore "assets/**/*.mp4"
154
+ ```
155
+
156
+ ## Notes
157
+
158
+ Initial WordPress.org plugin review uses a zip upload. Approved plugin releases
159
+ use SVN trunk and tags. Pressship keeps those workflows separate with `submit`
160
+ and `release`.
@@ -0,0 +1 @@
1
+ export declare function login(): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { openBrowserSession, saveBrowserSession } from "./session.js";
2
+ import { accountFromLoggedInCookie, accountFromLoggedInText, accountFromCurrentPageProfileLink } from "./whoami.js";
3
+ import { ui } from "../ui.js";
4
+ const loginTimeoutMs = 5 * 60 * 1000;
5
+ const loginPollMs = 1_000;
6
+ export async function login() {
7
+ ui.intro("Login to WordPress.org");
8
+ const { context, page } = await openBrowserSession({ headless: false });
9
+ try {
10
+ await page.goto("https://login.wordpress.org/", { waitUntil: "domcontentloaded" });
11
+ ui.info("Complete the WordPress.org login in the opened browser. Pressship will continue automatically.");
12
+ const account = await ui.task("Waiting for WordPress.org login", waitForLoggedInAccount, (value) => `Logged in as ${value.username}`);
13
+ await ui.task("Saving browser session", () => saveBrowserSession(context), () => `Saved WordPress.org browser session with user ${account.username}.`);
14
+ }
15
+ finally {
16
+ await context.browser()?.close();
17
+ }
18
+ async function waitForLoggedInAccount() {
19
+ const deadline = Date.now() + loginTimeoutMs;
20
+ while (Date.now() < deadline) {
21
+ const cookieAccount = await accountFromLoggedInCookie(context);
22
+ if (cookieAccount) {
23
+ return cookieAccount;
24
+ }
25
+ const linkAccount = await accountFromCurrentPageProfileLink(page).catch(() => undefined);
26
+ if (linkAccount) {
27
+ return linkAccount;
28
+ }
29
+ const pageText = await page.locator("body").innerText({ timeout: 1_000 }).catch(() => "");
30
+ const textAccount = accountFromLoggedInText(pageText);
31
+ if (textAccount) {
32
+ return textAccount;
33
+ }
34
+ await page.waitForTimeout(loginPollMs);
35
+ }
36
+ throw new Error("Timed out waiting for WordPress.org login. Run `pressship login` again.");
37
+ }
38
+ }
39
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,iCAAiC,EAElC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAE9B,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACrC,MAAM,WAAW,GAAG,KAAK,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAkB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAExE,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACnF,EAAE,CAAC,IAAI,CAAC,gGAAgG,CAAC,CAAC;QAE1G,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,iCAAiC,EAAE,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE,CACjG,gBAAgB,KAAK,CAAC,QAAQ,EAAE,CACjC,CAAC;QAEF,MAAM,EAAE,CAAC,IAAI,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAC9E,iDAAiD,OAAO,CAAC,QAAQ,GAAG,CACrE,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,KAAK,UAAU,sBAAsB;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;QAE7C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,aAAa,GAAG,MAAM,yBAAyB,CAAC,OAAO,CAAC,CAAC;YAC/D,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,aAAa,CAAC;YACvB,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,iCAAiC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACzF,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC1F,MAAM,WAAW,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACtD,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC;YACrB,CAAC;YAED,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACzC,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function logout(): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { clearBrowserSession } from "./session.js";
2
+ import { ui } from "../ui.js";
3
+ export async function logout() {
4
+ ui.intro("Logout");
5
+ const removed = await clearBrowserSession();
6
+ if (removed) {
7
+ ui.success("Logged out of Pressship. Saved WordPress.org browser session removed.");
8
+ return;
9
+ }
10
+ ui.info("No saved WordPress.org browser session found.");
11
+ }
12
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/auth/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnB,MAAM,OAAO,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAE5C,IAAI,OAAO,EAAE,CAAC;QACZ,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IAED,EAAE,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type BrowserContext, type Page } from "playwright";
2
+ export type BrowserSessionOptions = {
3
+ headless?: boolean;
4
+ };
5
+ export declare function openBrowserSession(options?: BrowserSessionOptions): Promise<{
6
+ context: BrowserContext;
7
+ page: Page;
8
+ }>;
9
+ export declare function saveBrowserSession(context: BrowserContext): Promise<void>;
10
+ export declare function clearBrowserSession(): Promise<boolean>;
11
+ export declare function hasSavedSession(): Promise<boolean>;
12
+ export declare function isLoggedIn(page: Page): Promise<boolean>;
@@ -0,0 +1,72 @@
1
+ import { readFile, rm } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import { execa } from "execa";
4
+ import { chromium } from "playwright";
5
+ import { ensureConfigDir, getStorageStatePath, pathExists } from "../utils/paths.js";
6
+ const require = createRequire(import.meta.url);
7
+ export async function openBrowserSession(options = {}) {
8
+ await ensureConfigDir();
9
+ const browser = await launchChromium(options);
10
+ const storageStatePath = getStorageStatePath();
11
+ const context = await browser.newContext(pathExists(storageStatePath) ? { storageState: storageStatePath } : undefined);
12
+ const page = await context.newPage();
13
+ return { context, page };
14
+ }
15
+ export async function saveBrowserSession(context) {
16
+ await ensureConfigDir();
17
+ await context.storageState({ path: getStorageStatePath() });
18
+ }
19
+ export async function clearBrowserSession() {
20
+ const storageStatePath = getStorageStatePath();
21
+ if (!pathExists(storageStatePath)) {
22
+ return false;
23
+ }
24
+ await rm(storageStatePath, { force: true });
25
+ return true;
26
+ }
27
+ export async function hasSavedSession() {
28
+ const storageStatePath = getStorageStatePath();
29
+ if (!pathExists(storageStatePath)) {
30
+ return false;
31
+ }
32
+ try {
33
+ const state = JSON.parse(await readFile(storageStatePath, "utf8"));
34
+ return Array.isArray(state.cookies) && state.cookies.length > 0;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ export async function isLoggedIn(page) {
41
+ await page.goto("https://wordpress.org/plugins/developers/add/", { waitUntil: "domcontentloaded" });
42
+ const bodyText = await page.locator("body").innerText({ timeout: 10_000 });
43
+ return !/please log in|log in to submit|before you can upload/i.test(bodyText);
44
+ }
45
+ function isMissingPlaywrightBrowserError(error) {
46
+ return error instanceof Error && /executable doesn't exist|playwright install/i.test(error.message);
47
+ }
48
+ async function launchChromium(options) {
49
+ try {
50
+ return await chromium.launch({ headless: options.headless ?? false });
51
+ }
52
+ catch (error) {
53
+ if (!isMissingPlaywrightBrowserError(error)) {
54
+ throw error;
55
+ }
56
+ await installChromium();
57
+ return chromium.launch({ headless: options.headless ?? false });
58
+ }
59
+ }
60
+ async function installChromium() {
61
+ console.log("Playwright Chromium is missing. Installing it now...");
62
+ try {
63
+ const playwrightCli = require.resolve("playwright/cli");
64
+ await execa(process.execPath, [playwrightCli, "install", "chromium"], {
65
+ stdio: "inherit"
66
+ });
67
+ }
68
+ catch (error) {
69
+ throw new Error(`Could not install Playwright Chromium automatically. Run \`npx playwright install chromium\` and try again. ${error instanceof Error ? error.message : String(error)}`);
70
+ }
71
+ }
72
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { BrowserContext, Page } from "playwright";
2
+ export type WhoamiOptions = {
3
+ json?: boolean;
4
+ };
5
+ export type WordPressOrgAccount = {
6
+ username: string;
7
+ profileUrl: string;
8
+ displayName?: string;
9
+ };
10
+ export declare function whoami(options?: WhoamiOptions): Promise<void>;
11
+ export declare function getWordPressOrgAccount(): Promise<WordPressOrgAccount>;
12
+ export declare function detectWordPressOrgAccount(context: BrowserContext, page?: Page): Promise<WordPressOrgAccount>;
13
+ export declare function usernameFromProfileUrl(profileUrl: string): string | undefined;
14
+ export declare function usernameFromLoggedInCookieValue(value: string): string | undefined;
15
+ export declare function accountFromLoggedInCookie(context: BrowserContext): Promise<WordPressOrgAccount | undefined>;
16
+ export declare function accountFromLoggedInText(text: string): WordPressOrgAccount | undefined;
17
+ export declare function accountFromCurrentPageProfileLink(page: Page): Promise<WordPressOrgAccount | undefined>;
@@ -0,0 +1,108 @@
1
+ import { hasSavedSession, openBrowserSession } from "./session.js";
2
+ import { ui } from "../ui.js";
3
+ export async function whoami(options = {}) {
4
+ if (!(await hasSavedSession())) {
5
+ throw new Error("Not logged in. Run `pressship login` first.");
6
+ }
7
+ const account = await ui.task("Checking saved WordPress.org session", getWordPressOrgAccount, (value) => `Authenticated as ${value.username}`);
8
+ if (options.json) {
9
+ console.log(JSON.stringify(account, null, 2));
10
+ return;
11
+ }
12
+ ui.success(account.username);
13
+ }
14
+ export async function getWordPressOrgAccount() {
15
+ const { context, page } = await openBrowserSession({ headless: true });
16
+ try {
17
+ return await detectWordPressOrgAccount(context, page);
18
+ }
19
+ finally {
20
+ await context.browser()?.close();
21
+ }
22
+ }
23
+ export async function detectWordPressOrgAccount(context, page) {
24
+ const cookieAccount = await accountFromLoggedInCookie(context);
25
+ if (cookieAccount) {
26
+ return cookieAccount;
27
+ }
28
+ if (page) {
29
+ const account = await accountFromCurrentPageProfileLink(page);
30
+ if (account) {
31
+ return account;
32
+ }
33
+ await page.goto("https://wordpress.org/plugins/developers/add/", { waitUntil: "domcontentloaded" });
34
+ const navigatedAccount = await accountFromCurrentPageProfileLink(page);
35
+ if (navigatedAccount) {
36
+ return navigatedAccount;
37
+ }
38
+ }
39
+ throw new Error("Saved WordPress.org session is not logged in. Run `pressship login` again.");
40
+ }
41
+ export function usernameFromProfileUrl(profileUrl) {
42
+ const url = new URL(profileUrl);
43
+ if (url.hostname !== "profiles.wordpress.org") {
44
+ return undefined;
45
+ }
46
+ const username = url.pathname.split("/").filter(Boolean)[0];
47
+ return username && username !== "me" ? username : undefined;
48
+ }
49
+ export function usernameFromLoggedInCookieValue(value) {
50
+ const decoded = safeDecodeURIComponent(value);
51
+ const rawUsername = decoded.split("|")[0];
52
+ if (!rawUsername) {
53
+ return undefined;
54
+ }
55
+ return rawUsername;
56
+ }
57
+ export async function accountFromLoggedInCookie(context) {
58
+ const cookies = await context.cookies([
59
+ "https://wordpress.org",
60
+ "https://login.wordpress.org",
61
+ "https://profiles.wordpress.org"
62
+ ]);
63
+ const loggedInCookie = cookies.find((cookie) => cookie.domain.endsWith("wordpress.org") && /logged_in/i.test(cookie.name));
64
+ const username = loggedInCookie ? usernameFromLoggedInCookieValue(loggedInCookie.value) : undefined;
65
+ return username
66
+ ? {
67
+ username,
68
+ profileUrl: `https://profiles.wordpress.org/${username}/`
69
+ }
70
+ : undefined;
71
+ }
72
+ export function accountFromLoggedInText(text) {
73
+ const normalized = text.replace(/\s+/g, " ");
74
+ const match = normalized.match(/\b(?:Logged in (?:user|as)|You are logged in as):?\s*([A-Za-z0-9_.@-]+)/i);
75
+ const username = match?.[1];
76
+ return username
77
+ ? {
78
+ username,
79
+ profileUrl: `https://profiles.wordpress.org/${username}/`
80
+ }
81
+ : undefined;
82
+ }
83
+ export async function accountFromCurrentPageProfileLink(page) {
84
+ const profileUrl = await page
85
+ .locator('a[href*="profiles.wordpress.org/"]')
86
+ .evaluateAll((links) => links
87
+ .map((link) => link.getAttribute("href"))
88
+ .filter((href) => Boolean(href))
89
+ .find((href) => {
90
+ const url = new URL(href, "https://profiles.wordpress.org");
91
+ const username = url.pathname.split("/").filter(Boolean)[0];
92
+ return username && username !== "me";
93
+ }));
94
+ if (!profileUrl) {
95
+ return undefined;
96
+ }
97
+ const username = usernameFromProfileUrl(profileUrl);
98
+ return username ? { username, profileUrl } : undefined;
99
+ }
100
+ function safeDecodeURIComponent(value) {
101
+ try {
102
+ return decodeURIComponent(value);
103
+ }
104
+ catch {
105
+ return value;
106
+ }
107
+ }
108
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/auth/whoami.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export type WpCliCommand = {
2
+ command: string;
3
+ baseArgs: string[];
4
+ };
5
+ export type ManagedPluginCheckEnvironment = {
6
+ wpCli: WpCliCommand;
7
+ wpPath: string;
8
+ requirePath: string;
9
+ };
10
+ export declare function prepareManagedPluginCheckEnvironment(): Promise<ManagedPluginCheckEnvironment>;
11
+ export declare function resolveWpCli(cacheDir: string): Promise<WpCliCommand>;
12
+ export declare function ensureWpConfig(wpPath: string): Promise<void>;
13
+ export declare function getManagedWpConfig(): string;
14
+ export declare function ensureSqliteIntegration(wpPath: string): Promise<void>;
15
+ export declare function getSqliteDropIn(dropInTemplatePath: string, pluginDir: string): Promise<string>;
16
+ export declare function getSqliteDropInFromTemplate(template: string, pluginDir: string): string;