PluginProbe
Extendify / 3.1.3
Extendify v3.1.3
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / AutoLaunch / functions / plugins.js

plugins.js in Extendify 3.1.3, at src/AutoLaunch/functions/plugins.js

173 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { recordPluginActivity } from '@shared/api/DataApi';
2 import { digest } from '@shared/api/digest';
3 import { enableAutoUpdate } from '@shared/api/wp';
4 import apiFetch from '@wordpress/api-fetch';
5 import { addQueryArgs } from '@wordpress/url';
6
7 export const getActivePlugins = () =>
8 apiFetch({ path: 'extendify/v1/auto-launch/active-plugins' });
9
10 export const alreadyActive = (activePlugins, pluginSlug) =>
11 activePlugins?.filter((p) => p.includes(pluginSlug))?.length;
12
13 export const installPlugin = async (slug) => {
14 const fn = async () => {
15 const p = await apiFetch({
16 path: '/wp/v2/plugins',
17 method: 'POST',
18 data: { slug },
19 });
20 await recordPluginActivity({ slug, source: 'auto-launch' });
21 await enableAutoUpdate(p?.plugin);
22 return p;
23 };
24 try {
25 return await fn();
26 } catch (error) {
27 if (error?.code === 'folder_exists') {
28 // Already on disk — fetch its record so the caller can activate it.
29 return await getPlugin(slug);
30 }
31 try {
32 return await fn();
33 } catch (error) {
34 digest({
35 error,
36 details: { source: 'auto-launch', caller: 'installPlugin' },
37 });
38 return null;
39 }
40 }
41 };
42
43 export const getPlugin = async (slug) => {
44 const response = await apiFetch({
45 path: addQueryArgs('/wp/v2/plugins', { search: slug }),
46 });
47 return response?.find((p) => p.plugin?.split('/')[0] === slug);
48 };
49
50 export const activatePlugin = async (slug) => {
51 const fn = (s) =>
52 apiFetch({
53 path: `/wp/v2/plugins/${s}`,
54 method: 'POST',
55 data: { status: 'active' },
56 });
57
58 try {
59 await fn(slug);
60 return true;
61 } catch (_) {
62 try {
63 // try once more but get the slug first
64 const { plugin } = await getPlugin(slug);
65 await fn(plugin);
66 return true;
67 } catch (error) {
68 digest({
69 error,
70 details: { source: 'auto-launch', caller: 'activatePlugin' },
71 });
72 return false;
73 }
74 }
75 };
76
77 // Isolates per-plugin failures so one bad plugin can't block the rest;
78 // returns the slugs that never went active.
79 export const ensurePluginsActive = async (
80 slugs,
81 { installedSlugs = [] } = {},
82 ) => {
83 const failed = [];
84 for (const slug of slugs) {
85 try {
86 const plugin = installedSlugs.includes(slug)
87 ? null
88 : await installPlugin(slug);
89 const activated = await activatePlugin(plugin?.plugin ?? slug);
90 if (!activated) failed.push(slug);
91 } catch (_) {
92 failed.push(slug);
93 }
94 }
95 return { failed };
96 };
97
98 // Last-chance guarantee before dependent work builds on these plugins: the
99 // optimistic install pass is best-effort and unverified.
100 export const verifyPluginsActive = async (
101 slugs,
102 { installedSlugs = [] } = {},
103 ) => {
104 const activePlugins = await getActivePlugins();
105 // Exact slug match here (not alreadyActive's substring test): matching
106 // woocommerce against an active woocommerce-payments would skip a real miss.
107 const missing = slugs.filter(
108 (slug) => !activePlugins?.some((path) => path.split('/')[0] === slug),
109 );
110 if (!missing.length) return;
111
112 const { failed } = await ensurePluginsActive(missing, { installedSlugs });
113 if (!failed.length) return;
114
115 digest({
116 error: { message: `Plugins inactive after verify: ${failed.join(', ')}` },
117 details: { source: 'auto-launch', caller: 'verifyPluginsActive', failed },
118 });
119 };
120
121 // Currently this only processes patterns with placeholders
122 // by swapping out the placeholders with the actual code
123 // returns the patterns as blocks with the placeholders replaced
124 export const replacePlaceholderPatterns = async (patterns) => {
125 // Directly replace "blog-section" patterns using their replacement code, skipping the API call
126 patterns = patterns.map((pattern) => {
127 if (
128 pattern.patternTypes.includes('blog-section') &&
129 pattern.patternReplacementCode
130 ) {
131 return {
132 ...pattern,
133 code: pattern.patternReplacementCode,
134 };
135 }
136 return pattern;
137 });
138
139 const hasPlaceholders = patterns.filter((p) => p.patternReplacementCode);
140 if (!hasPlaceholders?.length) return patterns;
141
142 const activePlugins =
143 (await getActivePlugins())?.data?.map((path) => path.split('/')[0]) || [];
144
145 const pluginsActivity = patterns
146 .filter((p) => p.pluginDependency)
147 .map((p) => p.pluginDependency)
148 .filter((p) => !activePlugins.includes(p));
149
150 for (const plugin of pluginsActivity) {
151 recordPluginActivity({
152 slug: plugin,
153 source: 'auto-launch',
154 });
155 }
156
157 try {
158 return await processPlaceholders(patterns);
159 } catch (_e) {
160 // Try one more time (plugins installed may not be fully loaded)
161 return await processPlaceholders(patterns)
162 // If this fails, just return the original patterns
163 .catch(() => patterns);
164 }
165 };
166
167 export const processPlaceholders = (patterns) =>
168 apiFetch({
169 path: '/extendify/v1/shared/process-placeholders',
170 method: 'POST',
171 data: { patterns },
172 });
173