pressship 0.1.14 → 0.2.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.
- package/README.md +9 -6
- package/assets/web/app.js +1276 -79
- package/assets/web/style.css +663 -79
- package/composer.json +3 -3
- package/dist/checks/plugin-check.d.ts +1 -0
- package/dist/checks/plugin-check.js +72 -1
- package/dist/checks/plugin-check.js.map +1 -1
- package/dist/package/archive.d.ts +12 -0
- package/dist/package/archive.js +44 -6
- package/dist/package/archive.js.map +1 -1
- package/dist/package/ignore.d.ts +18 -0
- package/dist/package/ignore.js +156 -8
- package/dist/package/ignore.js.map +1 -1
- package/dist/svn/release.js +105 -8
- package/dist/svn/release.js.map +1 -1
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +76 -0
- package/dist/ui.js.map +1 -1
- package/dist/web/index.js +1 -0
- package/dist/web/index.js.map +1 -1
- package/dist/web/server.js +427 -26
- package/dist/web/server.js.map +1 -1
- package/dist/wordpress-org/submit.js +4 -2
- package/dist/wordpress-org/submit.js.map +1 -1
- package/package.json +1 -1
package/dist/web/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import { cp, mkdir, mkdtemp, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { cp, mkdir, mkdtemp, readdir, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
@@ -12,7 +12,8 @@ import fg from "fast-glob";
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { hasSavedSession } from "../auth/session.js";
|
|
14
14
|
import { getWordPressOrgAccount } from "../auth/whoami.js";
|
|
15
|
-
import { stagePluginDirectory } from "../package/archive.js";
|
|
15
|
+
import { analyzePluginPackage, stagePluginDirectory } from "../package/archive.js";
|
|
16
|
+
import { addPressshipIgnorePattern, createPressshipIgnoreMatcher, hardIgnoreDirectories, isHardIgnoredPath, pressshipIgnoreFile, readPressshipIgnorePatterns, removePressshipIgnorePattern } from "../package/ignore.js";
|
|
16
17
|
import { createPluginPack, summarizePackResult, validatePluginPack } from "../package/pack.js";
|
|
17
18
|
import { discoverPluginProject, resolvePluginProjectPath } from "../plugin/discover.js";
|
|
18
19
|
import { assertDemoLaunchPlanSupported, createDemoLaunchPlan, prepareDemoRuntime, publicDemoLaunchPlan, resetPlaygroundSite } from "../plugin/demo.js";
|
|
@@ -26,7 +27,7 @@ import { publish } from "../wordpress-org/publish.js";
|
|
|
26
27
|
import { fetchPluginStates, matchesPluginState } from "../wordpress-org/state.js";
|
|
27
28
|
import { runPluginCheck } from "../checks/plugin-check.js";
|
|
28
29
|
import { hasBlockingFindings } from "../checks/summary.js";
|
|
29
|
-
import { ensureCacheDir, getConfigDir } from "../utils/paths.js";
|
|
30
|
+
import { ensureCacheDir, getConfigDir, pathExists } from "../utils/paths.js";
|
|
30
31
|
import { addLocalPluginPath, getLocalPlugin, listLocalPlugins, removeLocalPlugin } from "./registry.js";
|
|
31
32
|
import { WebJobManager } from "./jobs.js";
|
|
32
33
|
import { getVersionState } from "./version-state.js";
|
|
@@ -44,6 +45,7 @@ const bumpVersionSchema = z.object({ bump: z.enum(["patch", "minor", "major"]) }
|
|
|
44
45
|
const setVersionSchema = z.object({ version: z.string().min(1) });
|
|
45
46
|
const createSvnTagSchema = z.object({ name: z.string().min(1) });
|
|
46
47
|
const switchSvnTagSchema = z.object({ conflictResolution: z.enum(["override", "revert"]).optional() });
|
|
48
|
+
const ignoreRuleSchema = z.object({ pattern: z.string().min(1) });
|
|
47
49
|
const writeStudioFileSchema = z.object({
|
|
48
50
|
path: z.string().min(1),
|
|
49
51
|
content: z.string()
|
|
@@ -104,6 +106,7 @@ export async function startWebServer(options = {}) {
|
|
|
104
106
|
const approvals = new Map();
|
|
105
107
|
const playgrounds = new Map();
|
|
106
108
|
const playgroundPortReservations = new Set();
|
|
109
|
+
const packageSizeCache = new Map();
|
|
107
110
|
const staticDir = resolveStaticDir();
|
|
108
111
|
const dependencies = options.dependencies ?? {};
|
|
109
112
|
const server = createServer((request, response) => {
|
|
@@ -113,6 +116,7 @@ export async function startWebServer(options = {}) {
|
|
|
113
116
|
approvals,
|
|
114
117
|
playgrounds,
|
|
115
118
|
playgroundPortReservations,
|
|
119
|
+
packageSizeCache,
|
|
116
120
|
staticDir,
|
|
117
121
|
dependencies
|
|
118
122
|
});
|
|
@@ -275,6 +279,62 @@ async function handleApi(request, response, url, context) {
|
|
|
275
279
|
sendJson(response, 200, await listStudioFiles(plugin.path));
|
|
276
280
|
return;
|
|
277
281
|
}
|
|
282
|
+
const studioFilesDirectoryMatch = url.pathname.match(/^\/api\/plugins\/local\/([^/]+)\/files\/directory$/);
|
|
283
|
+
if (method === "GET" && studioFilesDirectoryMatch) {
|
|
284
|
+
const plugin = await requireLocalPlugin(decodeURIComponent(studioFilesDirectoryMatch[1]));
|
|
285
|
+
const relativePath = url.searchParams.get("path") ?? "";
|
|
286
|
+
sendJson(response, 200, await listStudioDirectory(plugin.path, relativePath));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const studioIgnoreStateMatch = url.pathname.match(/^\/api\/plugins\/local\/([^/]+)\/ignore-state$/);
|
|
290
|
+
if (method === "GET" && studioIgnoreStateMatch) {
|
|
291
|
+
const plugin = await requireLocalPlugin(decodeURIComponent(studioIgnoreStateMatch[1]));
|
|
292
|
+
sendJson(response, 200, await readStudioIgnoreState(plugin.path));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const studioPackageSizeMatch = url.pathname.match(/^\/api\/plugins\/local\/([^/]+)\/package-size$/);
|
|
296
|
+
if (method === "GET" && studioPackageSizeMatch) {
|
|
297
|
+
const plugin = await requireLocalPlugin(decodeURIComponent(studioPackageSizeMatch[1]));
|
|
298
|
+
sendJson(response, 200, readStudioPackageSize(plugin.path, context.packageSizeCache));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const studioIgnoreRulesMatch = url.pathname.match(/^\/api\/plugins\/local\/([^/]+)\/ignore-rules$/);
|
|
302
|
+
if (method === "POST" && studioIgnoreRulesMatch) {
|
|
303
|
+
const plugin = await requireLocalPlugin(decodeURIComponent(studioIgnoreRulesMatch[1]));
|
|
304
|
+
const body = ignoreRuleSchema.parse(await readJson(request));
|
|
305
|
+
try {
|
|
306
|
+
await addPressshipIgnorePattern(path.resolve(plugin.path), body.pattern);
|
|
307
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
308
|
+
sendJson(response, 200, await readStudioIgnoreState(plugin.path));
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
sendJson(response, 400, {
|
|
312
|
+
error: {
|
|
313
|
+
message: error instanceof Error ? error.message : String(error),
|
|
314
|
+
code: "invalid_ignore_pattern"
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (method === "DELETE" && studioIgnoreRulesMatch) {
|
|
321
|
+
const plugin = await requireLocalPlugin(decodeURIComponent(studioIgnoreRulesMatch[1]));
|
|
322
|
+
const body = ignoreRuleSchema.parse(await readJson(request));
|
|
323
|
+
try {
|
|
324
|
+
await removePressshipIgnorePattern(path.resolve(plugin.path), body.pattern);
|
|
325
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
326
|
+
sendJson(response, 200, await readStudioIgnoreState(plugin.path));
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
sendJson(response, 400, {
|
|
330
|
+
error: {
|
|
331
|
+
message: error instanceof Error ? error.message : String(error),
|
|
332
|
+
code: "invalid_ignore_pattern"
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
278
338
|
const studioCheckStateMatch = url.pathname.match(/^\/api\/plugins\/local\/([^/]+)\/check-state$/);
|
|
279
339
|
if (method === "GET" && studioCheckStateMatch) {
|
|
280
340
|
const localId = decodeURIComponent(studioCheckStateMatch[1]);
|
|
@@ -294,6 +354,7 @@ async function handleApi(request, response, url, context) {
|
|
|
294
354
|
const plugin = await requireLocalPlugin(localId);
|
|
295
355
|
const body = writeStudioFileSchema.parse(await readJson(request));
|
|
296
356
|
const saved = await writeStudioFile(plugin.path, body.path, body.content);
|
|
357
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
297
358
|
sendJson(response, 200, {
|
|
298
359
|
...saved,
|
|
299
360
|
checkState: await removeStudioPluginCheckFindingsForFiles(localId, [saved.path])
|
|
@@ -307,6 +368,7 @@ async function handleApi(request, response, url, context) {
|
|
|
307
368
|
const body = studioAiChangeSchema.parse(await readJson(request));
|
|
308
369
|
try {
|
|
309
370
|
const applied = await applyStudioAiChange(plugin.path, body);
|
|
371
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
310
372
|
const checkState = await removeStudioPluginCheckFindingsForFiles(localId, [applied.path]);
|
|
311
373
|
sendJson(response, 200, {
|
|
312
374
|
...applied,
|
|
@@ -329,6 +391,7 @@ async function handleApi(request, response, url, context) {
|
|
|
329
391
|
const localId = decodeURIComponent(bumpMatch[1]);
|
|
330
392
|
const plugin = await requireLocalPlugin(localId);
|
|
331
393
|
await bumpLocalPluginVersion(plugin.path, body.bump);
|
|
394
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
332
395
|
await addLocalPluginPath(plugin.path, plugin.source);
|
|
333
396
|
await removeStudioPluginCheckState(localId);
|
|
334
397
|
sendJson(response, 200, { ...(await getVersionState(plugin.path)), checkState: null });
|
|
@@ -351,6 +414,7 @@ async function handleApi(request, response, url, context) {
|
|
|
351
414
|
}
|
|
352
415
|
try {
|
|
353
416
|
await setLocalPluginVersion(plugin.path, trimmed);
|
|
417
|
+
invalidateStudioPackageSize(plugin.path, context.packageSizeCache);
|
|
354
418
|
await addLocalPluginPath(plugin.path, plugin.source);
|
|
355
419
|
await removeStudioPluginCheckState(localId);
|
|
356
420
|
sendJson(response, 200, { ...(await getVersionState(plugin.path)), checkState: null });
|
|
@@ -693,12 +757,13 @@ async function dryRunPublishJob(localId, requestedAction, approvals, context) {
|
|
|
693
757
|
if (route.action === "release" && versionState.releaseBlocked) {
|
|
694
758
|
context.log("Release is blocked by version state.", versionState);
|
|
695
759
|
}
|
|
760
|
+
const ignorePatterns = await readPressshipIgnorePatterns(source.rootDir);
|
|
696
761
|
context.status("Validating package.");
|
|
697
|
-
const validation = await validatePluginPack(project, {});
|
|
762
|
+
const validation = await validatePluginPack(project, { ignore: ignorePatterns });
|
|
698
763
|
const validationBlocked = hasBlockingFindings(validation.readmeFindings) || hasBlockingFindings(validation.pluginCheckFindings);
|
|
699
764
|
const cacheDir = path.join(await ensureCacheDir(), "studio-packages");
|
|
700
765
|
await mkdir(cacheDir, { recursive: true, mode: 0o700 });
|
|
701
|
-
const pack = summarizePackResult(await createPluginPack(source.rootDir, { outputDir: cacheDir }), validation);
|
|
766
|
+
const pack = summarizePackResult(await createPluginPack(source.rootDir, { outputDir: cacheDir, ignore: ignorePatterns }), validation);
|
|
702
767
|
const releasePlan = route.action === "release"
|
|
703
768
|
? createReleaseCommandPlan(project.slug, path.resolve(source.svnRootDir ?? path.join(process.cwd(), ".pressship-svn", project.slug)), project.version ?? "unknown", `Release ${project.slug} ${project.version ?? "unknown"}`, await inferWordPressOrgUsername())
|
|
704
769
|
: undefined;
|
|
@@ -708,7 +773,8 @@ async function dryRunPublishJob(localId, requestedAction, approvals, context) {
|
|
|
708
773
|
localId,
|
|
709
774
|
pluginPath: source.inputDir,
|
|
710
775
|
action: route.action,
|
|
711
|
-
version: project.version
|
|
776
|
+
version: project.version,
|
|
777
|
+
ignore: ignorePatterns
|
|
712
778
|
})
|
|
713
779
|
: undefined;
|
|
714
780
|
return {
|
|
@@ -741,6 +807,7 @@ async function confirmPublishJob(approvalId, overview, approvals, context) {
|
|
|
741
807
|
yes: true,
|
|
742
808
|
submit: approval.action === "submit",
|
|
743
809
|
release: approval.action === "release",
|
|
810
|
+
ignore: approval.ignore ?? [],
|
|
744
811
|
overview: overview ?? project.headers.description ?? ""
|
|
745
812
|
});
|
|
746
813
|
approvals.delete(approvalId);
|
|
@@ -762,39 +829,366 @@ async function readPluginDetail(scope, id) {
|
|
|
762
829
|
readme: await fetchHostedReadme(id).catch(() => undefined)
|
|
763
830
|
};
|
|
764
831
|
}
|
|
832
|
+
const studioHiddenFilePatterns = [
|
|
833
|
+
"**/.git/**",
|
|
834
|
+
"**/.svn/**",
|
|
835
|
+
"**/node_modules/**",
|
|
836
|
+
"**/vendor/**",
|
|
837
|
+
"**/build/**",
|
|
838
|
+
"**/dist/**",
|
|
839
|
+
"**/playground/**",
|
|
840
|
+
"**/.wordpress-playground/**"
|
|
841
|
+
];
|
|
842
|
+
const maxStudioEditableFileBytes = 1_000_000;
|
|
843
|
+
const studioAlwaysHiddenDirectoryNames = new Set([".git"]);
|
|
844
|
+
const studioDeferredDirectoryNames = new Set([
|
|
845
|
+
...hardIgnoreDirectories,
|
|
846
|
+
"vendor",
|
|
847
|
+
".wordpress-playground",
|
|
848
|
+
"playground"
|
|
849
|
+
]);
|
|
850
|
+
const studioDeferredDirectoryPatterns = Array.from(studioDeferredDirectoryNames).flatMap((directory) => [
|
|
851
|
+
`${directory}/**`,
|
|
852
|
+
`**/${directory}/**`
|
|
853
|
+
]);
|
|
854
|
+
const studioEditableExtensions = new Set([
|
|
855
|
+
".php",
|
|
856
|
+
".js",
|
|
857
|
+
".jsx",
|
|
858
|
+
".ts",
|
|
859
|
+
".tsx",
|
|
860
|
+
".css",
|
|
861
|
+
".scss",
|
|
862
|
+
".sass",
|
|
863
|
+
".html",
|
|
864
|
+
".htm",
|
|
865
|
+
".json",
|
|
866
|
+
".md",
|
|
867
|
+
".txt",
|
|
868
|
+
".xml",
|
|
869
|
+
".yml",
|
|
870
|
+
".yaml",
|
|
871
|
+
".po",
|
|
872
|
+
".pot",
|
|
873
|
+
".ini",
|
|
874
|
+
".sh",
|
|
875
|
+
".svg"
|
|
876
|
+
]);
|
|
877
|
+
const studioEditableFileNames = new Set([
|
|
878
|
+
"composer.json",
|
|
879
|
+
"package.json",
|
|
880
|
+
"readme.txt",
|
|
881
|
+
"license",
|
|
882
|
+
"license.txt",
|
|
883
|
+
pressshipIgnoreFile
|
|
884
|
+
]);
|
|
765
885
|
async function listStudioFiles(pluginPath) {
|
|
766
886
|
const root = path.resolve(pluginPath);
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
"
|
|
771
|
-
|
|
772
|
-
|
|
887
|
+
const patterns = await readPressshipIgnorePatterns(root);
|
|
888
|
+
const matcher = createPressshipIgnoreMatcher(patterns);
|
|
889
|
+
const [files, deferredDirectories, ignoredDirectories] = await Promise.all([
|
|
890
|
+
fg("**/*", {
|
|
891
|
+
cwd: root,
|
|
892
|
+
onlyFiles: true,
|
|
893
|
+
dot: true,
|
|
894
|
+
unique: true,
|
|
895
|
+
ignore: studioDeferredDirectoryPatterns
|
|
896
|
+
}),
|
|
897
|
+
listStudioDeferredDirectories(root, matcher),
|
|
898
|
+
listStudioIgnoredPatternDirectories(root, patterns, matcher)
|
|
899
|
+
]);
|
|
900
|
+
const directories = mergeStudioDirectoryEntries([...deferredDirectories, ...ignoredDirectories]);
|
|
901
|
+
const entries = await Promise.all(files.sort((a, b) => a.localeCompare(b)).map(async (relativePath) => {
|
|
902
|
+
const fileStats = await stat(path.join(root, relativePath));
|
|
903
|
+
const ignoredBy = matcher.ignoredBy(relativePath);
|
|
904
|
+
return {
|
|
905
|
+
path: relativePath,
|
|
906
|
+
name: path.basename(relativePath),
|
|
907
|
+
directory: path.dirname(relativePath) === "." ? "" : path.dirname(relativePath),
|
|
908
|
+
size: fileStats.size,
|
|
909
|
+
hardIgnored: isHardIgnoredPath(relativePath),
|
|
910
|
+
ignored: Boolean(ignoredBy),
|
|
911
|
+
ignoredBy
|
|
912
|
+
};
|
|
913
|
+
}));
|
|
914
|
+
return { files: entries, directories };
|
|
915
|
+
}
|
|
916
|
+
async function listStudioDirectory(pluginPath, relativePath) {
|
|
917
|
+
const root = path.resolve(pluginPath);
|
|
918
|
+
const normalized = normalizeStudioRelativePath(relativePath);
|
|
919
|
+
if (!normalized) {
|
|
920
|
+
throw new Error("Choose a folder first.");
|
|
921
|
+
}
|
|
922
|
+
if (studioExplorerDirectoryIsAlwaysHidden(normalized)) {
|
|
923
|
+
return { files: [], directories: [] };
|
|
924
|
+
}
|
|
925
|
+
const directoryPath = path.resolve(root, normalized);
|
|
926
|
+
if (directoryPath !== root && !directoryPath.startsWith(`${root}${path.sep}`)) {
|
|
927
|
+
throw new Error("Folder is outside the plugin directory.");
|
|
928
|
+
}
|
|
929
|
+
const directoryStats = await stat(directoryPath);
|
|
930
|
+
if (!directoryStats.isDirectory()) {
|
|
931
|
+
throw new Error("Studio can only load folders.");
|
|
932
|
+
}
|
|
933
|
+
const patterns = await readPressshipIgnorePatterns(root);
|
|
934
|
+
const matcher = createPressshipIgnoreMatcher(patterns);
|
|
935
|
+
const ignoredBy = studioIgnoredBy(normalized, matcher);
|
|
936
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
937
|
+
const files = [];
|
|
938
|
+
const directories = [
|
|
939
|
+
{
|
|
940
|
+
path: normalized,
|
|
941
|
+
name: path.basename(normalized),
|
|
942
|
+
directory: path.dirname(normalized) === "." ? "" : path.dirname(normalized),
|
|
943
|
+
deferred: false,
|
|
944
|
+
ignored: Boolean(ignoredBy),
|
|
945
|
+
ignoredBy,
|
|
946
|
+
hardIgnored: isHardIgnoredPath(normalized)
|
|
947
|
+
}
|
|
948
|
+
];
|
|
949
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
950
|
+
const childPath = `${normalized}/${entry.name}`;
|
|
951
|
+
if (studioExplorerDirectoryIsAlwaysHidden(childPath)) {
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
const childIgnoredBy = studioIgnoredBy(childPath, matcher);
|
|
955
|
+
if (entry.isDirectory()) {
|
|
956
|
+
directories.push({
|
|
957
|
+
path: childPath,
|
|
958
|
+
name: entry.name,
|
|
959
|
+
directory: normalized,
|
|
960
|
+
deferred: true,
|
|
961
|
+
ignored: Boolean(childIgnoredBy),
|
|
962
|
+
ignoredBy: childIgnoredBy,
|
|
963
|
+
hardIgnored: isHardIgnoredPath(childPath)
|
|
964
|
+
});
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (!entry.isFile()) {
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
const fileStats = await stat(path.join(directoryPath, entry.name));
|
|
971
|
+
files.push({
|
|
972
|
+
path: childPath,
|
|
973
|
+
name: entry.name,
|
|
974
|
+
directory: normalized,
|
|
975
|
+
size: fileStats.size,
|
|
976
|
+
hardIgnored: isHardIgnoredPath(childPath),
|
|
977
|
+
ignored: Boolean(childIgnoredBy),
|
|
978
|
+
ignoredBy: childIgnoredBy
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
return { files, directories };
|
|
982
|
+
}
|
|
983
|
+
async function listStudioDeferredDirectories(root, matcher) {
|
|
984
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
985
|
+
return entries
|
|
986
|
+
.filter((entry) => entry.isDirectory() &&
|
|
987
|
+
studioDeferredDirectoryNames.has(entry.name) &&
|
|
988
|
+
!studioExplorerDirectoryIsAlwaysHidden(entry.name))
|
|
989
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
990
|
+
.map((entry) => {
|
|
991
|
+
const ignoredBy = isHardIgnoredPath(entry.name)
|
|
992
|
+
? "Pressship package rules"
|
|
993
|
+
: matcher.ignoredBy(entry.name) ?? matcher.ignoredBy(`${entry.name}/.pressship-directory`);
|
|
994
|
+
return {
|
|
995
|
+
path: entry.name,
|
|
996
|
+
name: entry.name,
|
|
997
|
+
directory: "",
|
|
998
|
+
deferred: true,
|
|
999
|
+
ignored: Boolean(ignoredBy),
|
|
1000
|
+
ignoredBy,
|
|
1001
|
+
hardIgnored: isHardIgnoredPath(entry.name)
|
|
1002
|
+
};
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
async function listStudioIgnoredPatternDirectories(root, patterns, matcher) {
|
|
1006
|
+
const candidates = Array.from(new Set(patterns.flatMap(studioIgnoredDirectoryCandidates)));
|
|
1007
|
+
const entries = await Promise.all(candidates.map(async (relativePath) => {
|
|
1008
|
+
if (studioExplorerDirectoryIsAlwaysHidden(relativePath)) {
|
|
1009
|
+
return undefined;
|
|
1010
|
+
}
|
|
1011
|
+
const absolutePath = path.join(root, relativePath);
|
|
1012
|
+
if (!pathExists(absolutePath)) {
|
|
1013
|
+
return undefined;
|
|
1014
|
+
}
|
|
1015
|
+
const entryStats = await stat(absolutePath);
|
|
1016
|
+
if (!entryStats.isDirectory()) {
|
|
1017
|
+
return undefined;
|
|
1018
|
+
}
|
|
1019
|
+
const ignoredBy = matcher.ignoredBy(relativePath) ?? matcher.ignoredBy(`${relativePath}/.pressship-directory`);
|
|
1020
|
+
if (!ignoredBy) {
|
|
1021
|
+
return undefined;
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
path: relativePath,
|
|
1025
|
+
name: path.basename(relativePath),
|
|
1026
|
+
directory: path.dirname(relativePath) === "." ? "" : path.dirname(relativePath),
|
|
1027
|
+
deferred: false,
|
|
1028
|
+
ignored: true,
|
|
1029
|
+
ignoredBy,
|
|
1030
|
+
hardIgnored: isHardIgnoredPath(relativePath)
|
|
1031
|
+
};
|
|
1032
|
+
}));
|
|
1033
|
+
return entries
|
|
1034
|
+
.filter((entry) => Boolean(entry))
|
|
1035
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
1036
|
+
}
|
|
1037
|
+
function studioIgnoredDirectoryCandidates(pattern) {
|
|
1038
|
+
const trimmed = pattern.trim();
|
|
1039
|
+
if (!trimmed || trimmed.startsWith("!")) {
|
|
1040
|
+
return [];
|
|
1041
|
+
}
|
|
1042
|
+
let candidate = "";
|
|
1043
|
+
if (trimmed.endsWith("/**")) {
|
|
1044
|
+
candidate = trimmed.slice(0, -3);
|
|
1045
|
+
}
|
|
1046
|
+
else if (trimmed.endsWith("/")) {
|
|
1047
|
+
candidate = trimmed.slice(0, -1);
|
|
1048
|
+
}
|
|
1049
|
+
else if (!hasStudioGlobMagic(trimmed)) {
|
|
1050
|
+
candidate = trimmed;
|
|
1051
|
+
}
|
|
1052
|
+
candidate = candidate.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
1053
|
+
if (!candidate || candidate === "." || hasStudioGlobMagic(candidate)) {
|
|
1054
|
+
return [];
|
|
1055
|
+
}
|
|
1056
|
+
return [candidate];
|
|
1057
|
+
}
|
|
1058
|
+
function hasStudioGlobMagic(value) {
|
|
1059
|
+
return /[*?[\]{}]/.test(value);
|
|
1060
|
+
}
|
|
1061
|
+
function studioIgnoredBy(relativePath, matcher) {
|
|
1062
|
+
return isHardIgnoredPath(relativePath)
|
|
1063
|
+
? "Pressship package rules"
|
|
1064
|
+
: matcher.ignoredBy(relativePath);
|
|
1065
|
+
}
|
|
1066
|
+
function studioExplorerDirectoryIsAlwaysHidden(relativePath) {
|
|
1067
|
+
return relativePath
|
|
1068
|
+
.split("/")
|
|
1069
|
+
.filter(Boolean)
|
|
1070
|
+
.some((segment) => studioAlwaysHiddenDirectoryNames.has(segment));
|
|
1071
|
+
}
|
|
1072
|
+
function mergeStudioDirectoryEntries(entries) {
|
|
1073
|
+
const merged = new Map();
|
|
1074
|
+
for (const entry of entries) {
|
|
1075
|
+
const existing = merged.get(entry.path);
|
|
1076
|
+
if (!existing) {
|
|
1077
|
+
merged.set(entry.path, entry);
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
merged.set(entry.path, {
|
|
1081
|
+
...existing,
|
|
1082
|
+
...entry,
|
|
1083
|
+
deferred: Boolean(existing.deferred || entry.deferred),
|
|
1084
|
+
ignored: Boolean(existing.ignored || entry.ignored),
|
|
1085
|
+
ignoredBy: existing.hardIgnored ? existing.ignoredBy : entry.ignoredBy ?? existing.ignoredBy,
|
|
1086
|
+
hardIgnored: Boolean(existing.hardIgnored || entry.hardIgnored)
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return Array.from(merged.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
1090
|
+
}
|
|
1091
|
+
async function readStudioIgnoreState(pluginPath) {
|
|
1092
|
+
const root = path.resolve(pluginPath);
|
|
1093
|
+
const patterns = await readPressshipIgnorePatterns(root);
|
|
1094
|
+
const ignoredFiles = await listStudioIgnoredFiles(root, patterns);
|
|
1095
|
+
return {
|
|
1096
|
+
ignorePath: path.join(root, pressshipIgnoreFile),
|
|
1097
|
+
patterns,
|
|
1098
|
+
ignoredFiles
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
async function listStudioIgnoredFiles(root, patterns) {
|
|
1102
|
+
if (!patterns.length) {
|
|
1103
|
+
return [];
|
|
1104
|
+
}
|
|
1105
|
+
const matcher = createPressshipIgnoreMatcher(patterns);
|
|
1106
|
+
const files = await fg("**/*", {
|
|
773
1107
|
cwd: root,
|
|
774
1108
|
onlyFiles: true,
|
|
775
1109
|
dot: false,
|
|
776
1110
|
unique: true,
|
|
777
|
-
ignore:
|
|
778
|
-
"**/.git/**",
|
|
779
|
-
"**/.svn/**",
|
|
780
|
-
"**/node_modules/**",
|
|
781
|
-
"**/vendor/**",
|
|
782
|
-
"**/build/**",
|
|
783
|
-
"**/dist/**",
|
|
784
|
-
"**/playground/**",
|
|
785
|
-
"**/.wordpress-playground/**"
|
|
786
|
-
]
|
|
1111
|
+
ignore: studioHiddenFilePatterns
|
|
787
1112
|
});
|
|
788
|
-
const
|
|
1113
|
+
const ignored = files
|
|
1114
|
+
.map((relativePath) => ({ relativePath, ignoredBy: matcher.ignoredBy(relativePath) }))
|
|
1115
|
+
.filter((entry) => Boolean(entry.ignoredBy));
|
|
1116
|
+
const entries = await Promise.all(ignored.sort((a, b) => a.relativePath.localeCompare(b.relativePath)).map(async ({ relativePath, ignoredBy }) => {
|
|
789
1117
|
const fileStats = await stat(path.join(root, relativePath));
|
|
790
1118
|
return {
|
|
791
1119
|
path: relativePath,
|
|
792
1120
|
name: path.basename(relativePath),
|
|
793
1121
|
directory: path.dirname(relativePath) === "." ? "" : path.dirname(relativePath),
|
|
794
|
-
size: fileStats.size
|
|
1122
|
+
size: fileStats.size,
|
|
1123
|
+
ignoredBy
|
|
795
1124
|
};
|
|
796
1125
|
}));
|
|
797
|
-
return
|
|
1126
|
+
return entries;
|
|
1127
|
+
}
|
|
1128
|
+
function readStudioPackageSize(pluginPath, cache) {
|
|
1129
|
+
const source = resolvePluginProjectPath(pluginPath);
|
|
1130
|
+
const key = source.rootDir;
|
|
1131
|
+
const cached = cache.get(key);
|
|
1132
|
+
if (cached) {
|
|
1133
|
+
return publicStudioPackageSizeEntry(cached);
|
|
1134
|
+
}
|
|
1135
|
+
const entry = {
|
|
1136
|
+
status: "calculating",
|
|
1137
|
+
requestedAt: new Date().toISOString()
|
|
1138
|
+
};
|
|
1139
|
+
cache.set(key, entry);
|
|
1140
|
+
entry.promise = calculateStudioPackageSize(source.rootDir)
|
|
1141
|
+
.then((result) => {
|
|
1142
|
+
entry.status = "ready";
|
|
1143
|
+
entry.result = result;
|
|
1144
|
+
entry.error = undefined;
|
|
1145
|
+
entry.updatedAt = new Date().toISOString();
|
|
1146
|
+
})
|
|
1147
|
+
.catch((error) => {
|
|
1148
|
+
entry.status = "error";
|
|
1149
|
+
entry.error = error instanceof Error ? error.message : String(error);
|
|
1150
|
+
entry.updatedAt = new Date().toISOString();
|
|
1151
|
+
});
|
|
1152
|
+
return publicStudioPackageSizeEntry(entry);
|
|
1153
|
+
}
|
|
1154
|
+
function invalidateStudioPackageSize(pluginPath, cache) {
|
|
1155
|
+
cache.delete(resolvePluginProjectPath(pluginPath).rootDir);
|
|
1156
|
+
}
|
|
1157
|
+
async function calculateStudioPackageSize(rootDir) {
|
|
1158
|
+
const project = await discoverPluginProject(rootDir);
|
|
1159
|
+
const ignorePatterns = await readPressshipIgnorePatterns(rootDir);
|
|
1160
|
+
const analysis = await analyzePluginPackage(project, { ignore: ignorePatterns });
|
|
1161
|
+
return {
|
|
1162
|
+
sizeBytes: analysis.sizeBytes,
|
|
1163
|
+
maxSizeBytes: analysis.maxSizeBytes,
|
|
1164
|
+
overLimit: analysis.overLimit,
|
|
1165
|
+
fileCount: analysis.files.length,
|
|
1166
|
+
topLevelFolder: analysis.topLevelFolder,
|
|
1167
|
+
largestFiles: analysis.largestFiles
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function publicStudioPackageSizeEntry(entry) {
|
|
1171
|
+
if (entry.status === "ready" && entry.result) {
|
|
1172
|
+
return {
|
|
1173
|
+
status: "ready",
|
|
1174
|
+
cached: true,
|
|
1175
|
+
calculatedAt: entry.updatedAt,
|
|
1176
|
+
...entry.result
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
if (entry.status === "error") {
|
|
1180
|
+
return {
|
|
1181
|
+
status: "error",
|
|
1182
|
+
cached: true,
|
|
1183
|
+
error: entry.error ?? "Package size could not be calculated.",
|
|
1184
|
+
calculatedAt: entry.updatedAt
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
status: "calculating",
|
|
1189
|
+
cached: false,
|
|
1190
|
+
requestedAt: entry.requestedAt
|
|
1191
|
+
};
|
|
798
1192
|
}
|
|
799
1193
|
async function readStudioFile(pluginPath, relativePath) {
|
|
800
1194
|
const filePath = await resolveStudioFilePath(pluginPath, relativePath);
|
|
@@ -900,11 +1294,18 @@ async function resolveStudioFilePath(pluginPath, relativePath) {
|
|
|
900
1294
|
if (!fileStats.isFile()) {
|
|
901
1295
|
throw new Error("Studio can only open files.");
|
|
902
1296
|
}
|
|
1297
|
+
if (fileStats.size > maxStudioEditableFileBytes) {
|
|
1298
|
+
throw new Error(`Studio can list ${normalized}, but files larger than 1 MB are not opened in the editor.`);
|
|
1299
|
+
}
|
|
903
1300
|
return filePath;
|
|
904
1301
|
}
|
|
905
1302
|
function normalizeStudioRelativePath(relativePath) {
|
|
906
1303
|
return relativePath.replace(/\\/g, "/").replace(/^\/+/, "").split("/").filter(Boolean).join("/");
|
|
907
1304
|
}
|
|
1305
|
+
function isStudioEditablePath(relativePath) {
|
|
1306
|
+
const name = path.basename(relativePath).toLowerCase();
|
|
1307
|
+
return studioEditableFileNames.has(name) || studioEditableExtensions.has(path.extname(name));
|
|
1308
|
+
}
|
|
908
1309
|
async function selectFolder() {
|
|
909
1310
|
if (process.platform === "darwin") {
|
|
910
1311
|
const result = await execa("osascript", [
|
|
@@ -1261,7 +1662,7 @@ async function createStudioAiPreviewWorkspace(pluginPath) {
|
|
|
1261
1662
|
async function snapshotStudioFileContents(pluginPath) {
|
|
1262
1663
|
const root = path.resolve(pluginPath);
|
|
1263
1664
|
const { files } = await listStudioFiles(root);
|
|
1264
|
-
const entries = await Promise.all(files.map(async (file) => {
|
|
1665
|
+
const entries = await Promise.all(files.filter((file) => file.size <= maxStudioEditableFileBytes && isStudioEditablePath(file.path)).map(async (file) => {
|
|
1265
1666
|
const filePath = path.join(root, file.path);
|
|
1266
1667
|
const [fileStats, content] = await Promise.all([stat(filePath), readFile(filePath, "utf8")]);
|
|
1267
1668
|
return [
|