Skip to content

Commit c9e38d5

Browse files
committedOct 28, 2024
fix main thread in local addon to handle json files and new config and add bruno/postman files
1 parent 858e97d commit c9e38d5

38 files changed

+2537
-110
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
meta {
2+
name: Create User
3+
type: http
4+
seq: 1
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/create-user
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Content-Type: application/json
15+
}
16+
17+
body:json {
18+
{
19+
"username": "testuser",
20+
"inviteCode": "{{inviteCode}}",
21+
"github_username": "githubuser",
22+
"email": "user@example.com"
23+
}
24+
}
25+
26+
docs {
27+
Create a new user account with the plugin publishing system
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
meta {
2+
name: Initiate Key Roll
3+
type: http
4+
seq: 3
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/initiate-key-roll
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Content-Type: application/json
15+
}
16+
17+
body:json {
18+
{
19+
"username": "{{username}}",
20+
"email": "{{email}}"
21+
}
22+
}
23+
24+
docs {
25+
Start the key recovery process
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
meta {
2+
name: Rotate API Key
3+
type: http
4+
seq: 2
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/rotate-key
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"username": "{{username}}",
21+
"currentApiKey": "{{apiKey}}"
22+
}
23+
}
24+
25+
docs {
26+
Generate a new API key and invalidate the current one
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
meta {
2+
name: Verify Key Roll
3+
type: http
4+
seq: 4
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/verify-key-roll
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Content-Type: application/json
15+
}
16+
17+
body:json {
18+
{
19+
"gistUrl": "https://gist.github.com/username/gistid",
20+
"verificationToken": "{{verificationToken}}"
21+
}
22+
}
23+
24+
docs {
25+
Complete the key recovery process with GitHub verification
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Authentication
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
meta {
2+
name: Delete Author
3+
type: http
4+
seq: 2
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/delete-author
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"authorName": "{{username}}"
21+
}
22+
}
23+
24+
docs {
25+
Delete an author and all associated plugins
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
meta {
2+
name: Update Author Info
3+
type: http
4+
seq: 1
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/update-author-info
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"userId": "{{username}}",
21+
"pluginName": "{{pluginName}}",
22+
"authorData": {
23+
"username": "{{username}}",
24+
"email": "{{email}}",
25+
"avatar_url": "https://example.com/avatar.jpg",
26+
"bio": "Developer bio",
27+
"website": "https://example.com",
28+
"twitter": "twitterhandle",
29+
"github": "githubhandle"
30+
}
31+
}
32+
}
33+
34+
docs {
35+
Update author profile information
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Author Management
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
meta {
2+
name: Clear Cache -Authenticated-
3+
type: http
4+
seq: 1
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/clear-cache
9+
body: none
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
}
16+
17+
docs {
18+
Clear cached responses (requires authentication)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
meta {
2+
name: Clear Cache -Public-
3+
type: http
4+
seq: 2
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/clear-cache
9+
body: none
10+
auth: none
11+
}
12+
13+
docs {
14+
Clear cached responses (public endpoint)
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Cache Management
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
meta {
2+
name: Get Author Data
3+
type: http
4+
seq: 2
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/author-data?author={{username}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
}
16+
17+
docs {
18+
Get author profile and plugins
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
meta {
2+
name: Get Plugin Data
3+
type: http
4+
seq: 1
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/plugin-data?author={{username}}&slug={{pluginSlug}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
slug: {{pluginSlug}}
16+
}
17+
18+
docs {
19+
Get detailed information about a specific plugin
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Plugin Data
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
meta {
2+
name: Backup Plugin
3+
type: http
4+
seq: 6
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/backup-plugin
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"author": "{{username}}",
21+
"slug": "{{pluginSlug}}",
22+
"version": "1.0.0"
23+
}
24+
}
25+
26+
docs {
27+
Create a backup of the current plugin version
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
meta {
2+
name: Finalize Plugin Upload
3+
type: http
4+
seq: 4
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/plugin-upload-complete
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"userId": "{{username}}",
21+
"pluginName": "{{pluginName}}",
22+
"metadata": {
23+
"name": "Example Plugin",
24+
"version": "1.0.0",
25+
"short_description": "A sample plugin description",
26+
"tags": ["example", "test"]
27+
}
28+
}
29+
}
30+
31+
docs {
32+
Complete the plugin upload process
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
meta {
2+
name: Upload Plugin Assets
3+
type: http
4+
seq: 3
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/plugin-upload-assets
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"userId": "{{username}}",
21+
"pluginName": "{{pluginName}}",
22+
"fileName": "icon-256x256.jpg",
23+
"fileData": "{{base64ImageData}}",
24+
"assetType": "icon"
25+
}
26+
}
27+
28+
docs {
29+
Upload plugin icons or banner images
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
meta {
2+
name: Upload Plugin Chunk
3+
type: http
4+
seq: 1
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/plugin-upload-chunk
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"userId": "{{username}}",
21+
"pluginName": "{{pluginName}}",
22+
"fileData": "{{base64ChunkData}}",
23+
"chunkNumber": 1,
24+
"totalChunks": 3
25+
}
26+
}
27+
28+
docs {
29+
Upload a chunk of the plugin ZIP file
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
meta {
2+
name: Upload Plugin JSON
3+
type: http
4+
seq: 2
5+
}
6+
7+
post {
8+
url: {{baseUrl}}/plugin-upload-json
9+
body: json
10+
auth: none
11+
}
12+
13+
headers {
14+
Authorization: Bearer {{apiKey}}
15+
Content-Type: application/json
16+
}
17+
18+
body:json {
19+
{
20+
"userId": "{{username}}",
21+
"pluginName": "{{pluginName}}",
22+
"jsonData": {
23+
"name": "Example Plugin",
24+
"version": "1.0.0",
25+
"short_description": "A sample plugin description",
26+
"tags": ["example", "test"],
27+
"contributors": {
28+
"{{username}}": {
29+
"profile": "https://example.com",
30+
"avatar": "https://example.com/avatar.jpg",
31+
"display_name": "Test User"
32+
}
33+
}
34+
}
35+
}
36+
}
37+
38+
docs {
39+
Upload plugin metadata in JSON format
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
meta {
2+
name: Version Check
3+
type: http
4+
seq: 5
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/version-check?author={{username}}&pluginName={{pluginName}}&newVersion=1.1.0
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
pluginName: {{pluginName}}
16+
newVersion: 1.1.0
17+
}
18+
19+
docs {
20+
Check if a new version can be uploaded
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Plugin Management
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
meta {
2+
name: Search Plugins
3+
type: http
4+
seq: 1
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/search?q={{searchQuery}}&tag={{tag}}&limit={{limit}}&offset={{offset}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
q: {{searchQuery}}
15+
tag: {{tag}}
16+
limit: 20
17+
offset: 0
18+
}
19+
20+
docs {
21+
Search for plugins by text and tags
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
meta {
2+
name: Search Results Page
3+
type: http
4+
seq: 2
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/directory/search?q={{searchQuery}}&tag={{tag}}&limit={{limit}}&offset={{offset}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
q: {{searchQuery}}
15+
tag: {{tag}}
16+
limit: 20
17+
offset: 0
18+
}
19+
20+
docs {
21+
Get HTML page with search results
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Search
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
meta {
2+
name: Download Plugin
3+
type: http
4+
seq: 1
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/download?author={{username}}&slug={{pluginSlug}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
slug: {{pluginSlug}}
16+
}
17+
18+
docs {
19+
Download a plugin and record the download count
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
meta {
2+
name: Get Download Count
3+
type: http
4+
seq: 2
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/download-count?author={{username}}&slug={{pluginSlug}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
slug: {{pluginSlug}}
16+
}
17+
18+
docs {
19+
Get the number of downloads for a plugin
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
meta {
2+
name: Record Activation
3+
type: http
4+
seq: 3
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/activate?author={{username}}&slug={{pluginSlug}}
9+
body: none
10+
auth: none
11+
}
12+
13+
params:query {
14+
author: {{username}}
15+
slug: {{pluginSlug}}
16+
}
17+
18+
docs {
19+
Record a plugin activation
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
meta {
2+
name: Statistics
3+
}

‎api/Plugin Publisher API/bruno.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"version": "1",
3+
"name": "Plugin Publisher API",
4+
"type": "collection",
5+
"ignore": [
6+
"node_modules",
7+
".git"
8+
]
9+
}

‎api/Plugin Publisher API/collection.bru

Whitespace-only changes.

‎api/plugin-publisher-api-bruno.json

+827
Large diffs are not rendered by default.

‎api/plugin-publisher-api-postman.json

+902
Large diffs are not rendered by default.

‎api/src/userAuthDO.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,28 @@ export class UserAuthDO {
218218

219219
async deleteUser(username) {
220220
try {
221+
// First delete any pending key roll verifications
222+
await this.sql.exec(
223+
"DELETE FROM key_roll_verifications WHERE username = ?",
224+
username
225+
);
226+
227+
// Then delete the user
221228
const result = await this.sql.exec(
222229
"DELETE FROM users WHERE username = ?",
223230
username
224231
);
225-
return result.changes > 0;
232+
233+
// Return success even if no user was found (idempotent delete)
234+
return {
235+
success: true,
236+
deleted: result.changes > 0
237+
};
226238
} catch (error) {
227-
console.error("Error deleting user:", error);
228-
throw error;
239+
console.error("Error deleting user from auth database:", error);
240+
throw new Error(`Failed to delete user from auth database: ${error.message}`);
229241
}
230-
}
231-
242+
}
232243

233244
// Verify API key
234245
async verifyApiKey(apiKey) {

‎api/src/worker.js

+74-49
Original file line numberDiff line numberDiff line change
@@ -1609,73 +1609,98 @@ export default {
16091609

16101610
async handleDeleteUser(request, env) {
16111611
try {
1612-
// This endpont requires admin API_SECRET
1612+
// This endpoint requires admin API_SECRET
16131613
const authHeader = request.headers.get('Authorization');
16141614
if (!authHeader || authHeader !== `Bearer ${env.API_SECRET}`) {
16151615
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
16161616
status: 401,
16171617
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }
16181618
});
16191619
}
1620-
1621-
// Parse request once
1620+
16221621
const data = await request.json();
16231622
const { username } = data;
1624-
1623+
16251624
if (!username) {
16261625
return new Response(JSON.stringify({ error: 'Missing username' }), {
16271626
status: 400,
16281627
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }
16291628
});
16301629
}
1631-
1632-
// First delete the user from UserAuthDO
1633-
const authId = env.USER_AUTH.idFromName("global");
1634-
const auth = env.USER_AUTH.get(authId);
1635-
1636-
const authResponse = await auth.fetch(new Request('http://internal/delete-user', {
1637-
method: 'POST',
1638-
headers: {
1639-
'Content-Type': 'application/json'
1640-
},
1641-
body: JSON.stringify({ username })
1642-
}));
1643-
1644-
if (!authResponse.ok) {
1645-
throw new Error('Failed to delete user authentication data');
1646-
}
1647-
1648-
// Then delete all their author data and resources
1649-
const registryId = env.PLUGIN_REGISTRY.idFromName("global");
1650-
const registry = env.PLUGIN_REGISTRY.get(registryId);
1651-
1652-
const registryResponse = await registry.fetch(new Request('http://internal/delete-author', {
1653-
method: 'POST',
1654-
headers: {
1655-
'Content-Type': 'application/json'
1656-
},
1657-
body: JSON.stringify({ authorName: username })
1658-
}));
1659-
1660-
if (!registryResponse.ok) {
1661-
throw new Error('Failed to delete author data');
1662-
}
1663-
1664-
// Delete all their files from bucket
1665-
const prefix = `${username}/`;
1666-
const files = await env.PLUGIN_BUCKET.list({ prefix });
1667-
for (const file of files.objects) {
1668-
await env.PLUGIN_BUCKET.delete(file.key);
1669-
}
1670-
1671-
return new Response(JSON.stringify({
1630+
1631+
// Delete user data in parallel for better performance
1632+
const [authResult, registryResult, bucketResult] = await Promise.allSettled([
1633+
// 1. Delete from UserAuthDO
1634+
(async () => {
1635+
const authId = env.USER_AUTH.idFromName("global");
1636+
const auth = env.USER_AUTH.get(authId);
1637+
const response = await auth.fetch(new Request('http://internal/delete-user', {
1638+
method: 'POST',
1639+
headers: { 'Content-Type': 'application/json' },
1640+
body: JSON.stringify({ username })
1641+
}));
1642+
1643+
if (!response.ok) {
1644+
const error = await response.text();
1645+
throw new Error(`Auth deletion failed: ${error}`);
1646+
}
1647+
return await response.json();
1648+
})(),
1649+
1650+
// 2. Delete from PluginRegistryDO
1651+
(async () => {
1652+
const registryId = env.PLUGIN_REGISTRY.idFromName("global");
1653+
const registry = env.PLUGIN_REGISTRY.get(registryId);
1654+
const response = await registry.fetch(new Request('http://internal/delete-author', {
1655+
method: 'POST',
1656+
headers: { 'Content-Type': 'application/json' },
1657+
body: JSON.stringify({ authorName: username })
1658+
}));
1659+
1660+
if (!response.ok) {
1661+
const error = await response.text();
1662+
throw new Error(`Registry deletion failed: ${error}`);
1663+
}
1664+
return await response.json();
1665+
})(),
1666+
1667+
// 3. Delete files from bucket
1668+
(async () => {
1669+
const prefix = `${username}/`;
1670+
const files = await env.PLUGIN_BUCKET.list({ prefix });
1671+
const deletionResults = await Promise.all(
1672+
files.objects.map(file => env.PLUGIN_BUCKET.delete(file.key))
1673+
);
1674+
return { deletedFiles: files.objects.length };
1675+
})()
1676+
]);
1677+
1678+
// Process results and build response
1679+
const response = {
16721680
success: true,
1673-
message: `User ${username} and all associated data have been deleted`
1674-
}), {
1681+
details: {
1682+
auth: authResult.status === 'fulfilled' ? authResult.value : { error: authResult.reason?.message },
1683+
registry: registryResult.status === 'fulfilled' ? registryResult.value : { error: registryResult.reason?.message },
1684+
storage: bucketResult.status === 'fulfilled' ? bucketResult.value : { error: bucketResult.reason?.message }
1685+
}
1686+
};
1687+
1688+
// If any operation failed, mark overall success as false but continue with others
1689+
if (authResult.status === 'rejected' || registryResult.status === 'rejected' || bucketResult.status === 'rejected') {
1690+
response.success = false;
1691+
response.message = 'Some deletion operations failed. Check details for more information.';
1692+
return new Response(JSON.stringify(response), {
1693+
status: 207, // 207 Multi-Status
1694+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }
1695+
});
1696+
}
1697+
1698+
response.message = `User ${username} and all associated data have been deleted`;
1699+
return new Response(JSON.stringify(response), {
16751700
status: 200,
16761701
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }
16771702
});
1678-
1703+
16791704
} catch (error) {
16801705
console.error('Error deleting user:', error);
16811706
return new Response(JSON.stringify({
@@ -1688,7 +1713,7 @@ export default {
16881713
});
16891714
}
16901715
},
1691-
1716+
16921717
async fetch(request, env) {
16931718
const url = new URL(request.url);
16941719
const path = url.pathname;

‎examples/example-author.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"username": "bilbo",
3+
"email": "somemail@broken.place",
4+
"avatar_url": "https://assets.pluginpublisher.com/default_pfp.jpg",
5+
"bio": "Just a burglar in the shire.",
6+
"member_since": "",
7+
"website": "https://pluginpublisher.com",
8+
"twitter": "antpb",
9+
"github": "antpb",
10+
"plugins": [
11+
]
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"username": "bilbo",
3+
"email": "somemail@broken.place",
4+
"avatar_url": "https://assets.pluginpublisher.com/default_pfp.jpg",
5+
"bio": "Just a burglar in the shire.",
6+
"member_since": "",
7+
"website": "https://pluginpublisher.com",
8+
"twitter": "antpb",
9+
"github": "antpb",
10+
"plugins": [
11+
]
12+
}

‎examples/example-plugin.json

+72-41
Large diffs are not rendered by default.

‎src/main.ts

+64-15
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default function (context: LocalMain.AddonMainContext): void {
8686
logger.info(`JSON file not found at ${jsonFilePath}, loading example template`);
8787

8888
// Get the example template path
89-
const exampleJsonPath = path.join(__dirname, '..', 'examples', 'example-plugin-info.json');
89+
const exampleJsonPath = path.join(__dirname, '..', 'examples', 'example-plugin.json');
9090
logger.info(`Example JSON file path: ${exampleJsonPath}`);
9191

9292
if (!fs.existsSync(exampleJsonPath)) {
@@ -373,14 +373,18 @@ export default function (context: LocalMain.AddonMainContext): void {
373373

374374
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
375375

376-
377376
// Create plugin-build directory and subdirectories
378377
const pluginBuildDir = path.join(pluginDir, 'plugin-build');
379378
await fs.ensureDir(pluginBuildDir);
380379
await fs.ensureDir(path.join(pluginBuildDir, 'json'));
381380
await fs.ensureDir(path.join(pluginBuildDir, 'zip'));
382381
await fs.ensureDir(path.join(pluginBuildDir, 'assets'));
383382

383+
// Copy example-author.json to author_info.json in the plugin-build directory
384+
const exampleAuthorSourcePath = path.join(__dirname, '..', 'examples', 'example-plugin-dir-structure', 'plugin-build', 'example-author.json');
385+
const authorInfoDestPath = path.join(pluginBuildDir, 'author_info.json');
386+
await fs.copy(exampleAuthorSourcePath, authorInfoDestPath);
387+
384388
// Create empty .env file
385389
await fs.writeFile(path.join(pluginDir, '.env'), '');
386390

@@ -429,19 +433,64 @@ export default function (context: LocalMain.AddonMainContext): void {
429433

430434
// Append the upgrader class inclusion and initialization code
431435
const updaterCode = `
432-
// Include the Upgrader class
433-
require_once plugin_dir_path(__FILE__) . 'class-example-upgrader.php';
434-
435-
function initialize_Micro_Plugin_Publisher_Updater() {
436-
$plugin_slug = '${pluginSlug}';
437-
$plugin_name = plugin_basename(__FILE__);
438-
$version = '${pluginVersion}';
439-
$metadata_url = 'https://plugins.sxp.digital/e188bdf1-1cad-4a40-b8d8-fa2a354beea0/${pluginSlug}/${pluginSlug}.json';
440-
$zip_url = 'https://plugins.sxp.digital/e188bdf1-1cad-4a40-b8d8-fa2a354beea0/${pluginSlug}/${pluginSlug}.zip';
441-
new microUpgrader\\Micro_Plugin_Publisher_Updater($plugin_slug, $plugin_name, $version, $metadata_url, $zip_url);
442-
}
443-
add_action('init', 'initialize_Micro_Plugin_Publisher_Updater');
444-
`;
436+
if (!defined('ABSPATH')) {
437+
exit;
438+
}
439+
440+
require_once plugin_dir_path(__FILE__) . 'class-example-upgrader.php';
441+
442+
define('MY_NEW_AWESOME_PLUGIN_VERSION', '${pluginVersion}');
443+
define('MY_NEW_AWESOME_PLUGIN_JSON', 'https://assets.pluginpublisher.com/${authorName}/${pluginSlug}/${pluginSlug}.zip');
444+
define('MY_NEW_AWESOME_PLUGIN_WORKER_ENDPOINT', 'https://pluginpublisher.com');
445+
446+
register_activation_hook(__FILE__, 'handle_plugin_activation');
447+
448+
function handle_plugin_activation() {
449+
add_option('my_new_awesome_plugin_pending_activation', true);
450+
}
451+
452+
function handle_pending_activation() {
453+
if (get_option('my_new_awesome_plugin_pending_activation')) {
454+
delete_option('my_new_awesome_plugin_pending_activation');
455+
456+
if (!get_option('my_new_awesome_plugin_activation_tracked')) {
457+
$activation_url = MY_NEW_AWESOME_PLUGIN_WORKER_ENDPOINT . '/activate';
458+
$activation_url = add_query_arg(array(
459+
'author' => '',
460+
'slug' => '${pluginSlug}'
461+
), $activation_url);
462+
463+
$response = wp_remote_get($activation_url, array(
464+
'timeout' => 30,
465+
'sslverify' => true
466+
));
467+
468+
if (!is_wp_error($response)) {
469+
$response_code = wp_remote_retrieve_response_code($response);
470+
if ($response_code === 200) {
471+
update_option('my_new_awesome_plugin_activation_tracked', true);
472+
error_log('My New Awesome Plugin: Activation tracked successfully');
473+
} else {
474+
error_log('My New Awesome Plugin: Activation failed with response code ' . $response_code);
475+
}
476+
} else {
477+
error_log('My New Awesome Plugin: Activation error - ' . $response->get_error_message());
478+
}
479+
}
480+
}
481+
}
482+
add_action('admin_init', 'handle_pending_activation');
483+
484+
function initialize_Micro_Plugin_Publisher_Updater() {
485+
$plugin_slug = '${pluginSlug}';
486+
$plugin_name = plugin_basename(__FILE__);
487+
$version = MY_NEW_AWESOME_PLUGIN_VERSION;
488+
$metadata_url = MY_NEW_AWESOME_PLUGIN_JSON;
489+
$zip_url = 'https://assets.pluginpublisher.com/${authorName}/${pluginSlug}/${pluginSlug}.zip';
490+
new microUpgrader\\Micro_Plugin_Publisher_Updater($plugin_slug, $plugin_name, $version, $metadata_url, $zip_url);
491+
}
492+
add_action('init', 'initialize_Micro_Plugin_Publisher_Updater');
493+
`;
445494

446495
mainPluginContent += updaterCode;
447496

0 commit comments

Comments
 (0)
Please sign in to comment.