@playdrop/playdrop-cli 0.8.8 → 0.9.1
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 +20 -0
- package/config/client-meta.json +1 -1
- package/dist/commands/agents.d.ts +62 -0
- package/dist/commands/agents.js +470 -0
- package/dist/commands/clients.d.ts +44 -0
- package/dist/commands/clients.js +257 -0
- package/dist/commands/doctor.d.ts +51 -0
- package/dist/commands/doctor.js +266 -0
- package/dist/commands/marketing.d.ts +137 -0
- package/dist/commands/marketing.js +1102 -0
- package/dist/commands/workspaces.d.ts +47 -0
- package/dist/commands/workspaces.js +498 -0
- package/dist/index.js +153 -10
- package/dist/shellProbe.d.ts +6 -0
- package/dist/shellProbe.js +17 -0
- package/dist/versionCompare.d.ts +2 -0
- package/dist/versionCompare.js +30 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +6 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -0
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +22 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +12 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +15 -0
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +28 -1
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type InitSummary } from './init';
|
|
2
|
+
export type WorkspaceSource = 'created' | 'found' | 'inspected' | 'cached';
|
|
3
|
+
export interface WorkspaceEntry {
|
|
4
|
+
path: string;
|
|
5
|
+
name: string;
|
|
6
|
+
source: WorkspaceSource;
|
|
7
|
+
exists?: boolean;
|
|
8
|
+
lastSeenAt?: string;
|
|
9
|
+
lastScannedAt?: string;
|
|
10
|
+
appsCount: number;
|
|
11
|
+
assetsCount: number;
|
|
12
|
+
packsCount: number;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkspaceCache {
|
|
15
|
+
version: number;
|
|
16
|
+
defaultWorkspacePath: string;
|
|
17
|
+
workspaces: WorkspaceEntry[];
|
|
18
|
+
}
|
|
19
|
+
export interface WorkspaceCacheView extends WorkspaceCache {
|
|
20
|
+
currentWorkspacePath?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface WorkspaceCommandOptions {
|
|
23
|
+
json?: boolean;
|
|
24
|
+
homeDir?: string;
|
|
25
|
+
platform?: NodeJS.Platform | string;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
initProject?: (targetPath: string, options?: {
|
|
28
|
+
quiet?: boolean;
|
|
29
|
+
}) => Promise<InitSummary | null>;
|
|
30
|
+
}
|
|
31
|
+
export declare function getDefaultWorkspacePath(homeDir?: string): string;
|
|
32
|
+
export declare function getWorkspaceCachePath(): string;
|
|
33
|
+
export declare function loadWorkspaceCache(): WorkspaceCache;
|
|
34
|
+
export declare function saveWorkspaceCache(cache: WorkspaceCache): WorkspaceCache;
|
|
35
|
+
export declare function findCurrentWorkspaceRoot(startPath?: string): string | null;
|
|
36
|
+
export declare function loadWorkspaceCacheWithCurrentContext(options?: WorkspaceCommandOptions): WorkspaceCacheView;
|
|
37
|
+
export declare function listWorkspaces(options?: WorkspaceCommandOptions): Promise<WorkspaceCacheView | null>;
|
|
38
|
+
export declare function initWorkspace(targetPath?: string, options?: WorkspaceCommandOptions): Promise<WorkspaceEntry | null>;
|
|
39
|
+
export declare function inspectWorkspace(targetPath?: string, options?: WorkspaceCommandOptions): Promise<WorkspaceEntry | null>;
|
|
40
|
+
export declare function refreshWorkspaces(rootArgs?: string[], options?: WorkspaceCommandOptions): Promise<{
|
|
41
|
+
scannedRoots: string[];
|
|
42
|
+
skippedRoots: string[];
|
|
43
|
+
workspaces: WorkspaceEntry[];
|
|
44
|
+
} | null>;
|
|
45
|
+
export declare function isMacProtectedWorkspaceRoot(rootPath: string, homeDir?: string, platform?: NodeJS.Platform | string): boolean;
|
|
46
|
+
export declare function printNoCachedWorkspacesGuidance(defaultWorkspacePath: string, platform?: NodeJS.Platform | string): void;
|
|
47
|
+
export declare function getWorkspaceRefreshExample(platform?: NodeJS.Platform | string): string;
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDefaultWorkspacePath = getDefaultWorkspacePath;
|
|
7
|
+
exports.getWorkspaceCachePath = getWorkspaceCachePath;
|
|
8
|
+
exports.loadWorkspaceCache = loadWorkspaceCache;
|
|
9
|
+
exports.saveWorkspaceCache = saveWorkspaceCache;
|
|
10
|
+
exports.findCurrentWorkspaceRoot = findCurrentWorkspaceRoot;
|
|
11
|
+
exports.loadWorkspaceCacheWithCurrentContext = loadWorkspaceCacheWithCurrentContext;
|
|
12
|
+
exports.listWorkspaces = listWorkspaces;
|
|
13
|
+
exports.initWorkspace = initWorkspace;
|
|
14
|
+
exports.inspectWorkspace = inspectWorkspace;
|
|
15
|
+
exports.refreshWorkspaces = refreshWorkspaces;
|
|
16
|
+
exports.isMacProtectedWorkspaceRoot = isMacProtectedWorkspaceRoot;
|
|
17
|
+
exports.printNoCachedWorkspacesGuidance = printNoCachedWorkspacesGuidance;
|
|
18
|
+
exports.getWorkspaceRefreshExample = getWorkspaceRefreshExample;
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
21
|
+
const node_path_1 = require("node:path");
|
|
22
|
+
const init_1 = require("./init");
|
|
23
|
+
const output_1 = require("../output");
|
|
24
|
+
const CACHE_VERSION = 1;
|
|
25
|
+
const CATALOGUE_FILE = 'catalogue.json';
|
|
26
|
+
const SKIP_DIRECTORY_NAMES = new Set(['node_modules', 'build', 'dist', '.next', '.git', 'output', 'tmp', 'Pods']);
|
|
27
|
+
const WORKSPACE_SEARCH_DEPTH = 6;
|
|
28
|
+
const WORKSPACE_CONTENT_DEPTH = 6;
|
|
29
|
+
class WorkspaceScanError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'WorkspaceScanError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function getDefaultWorkspacePath(homeDir = node_os_1.default.homedir()) {
|
|
36
|
+
return (0, node_path_1.join)(homeDir, 'PlayDrop');
|
|
37
|
+
}
|
|
38
|
+
function getWorkspaceCachePath() {
|
|
39
|
+
const configPath = process.env.PLAYDROP_CONFIG_PATH;
|
|
40
|
+
if (typeof configPath === 'string' && configPath.trim().length > 0) {
|
|
41
|
+
return (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.resolve)(configPath.trim())), 'workspaces.json');
|
|
42
|
+
}
|
|
43
|
+
return (0, node_path_1.join)(node_os_1.default.homedir(), '.playdrop', 'workspaces.json');
|
|
44
|
+
}
|
|
45
|
+
function nowIso() {
|
|
46
|
+
return new Date().toISOString();
|
|
47
|
+
}
|
|
48
|
+
function emptyCache() {
|
|
49
|
+
return {
|
|
50
|
+
version: CACHE_VERSION,
|
|
51
|
+
defaultWorkspacePath: getDefaultWorkspacePath(),
|
|
52
|
+
workspaces: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function loadWorkspaceCache() {
|
|
56
|
+
const cachePath = getWorkspaceCachePath();
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(cachePath, 'utf8'));
|
|
59
|
+
const defaultWorkspacePath = typeof parsed.defaultWorkspacePath === 'string' && parsed.defaultWorkspacePath.trim().length > 0
|
|
60
|
+
? (0, node_path_1.resolve)(parsed.defaultWorkspacePath)
|
|
61
|
+
: getDefaultWorkspacePath();
|
|
62
|
+
const entries = Array.isArray(parsed.workspaces)
|
|
63
|
+
? parsed.workspaces.map(normalizeWorkspaceEntry).filter((entry) => Boolean(entry))
|
|
64
|
+
: [];
|
|
65
|
+
return {
|
|
66
|
+
version: CACHE_VERSION,
|
|
67
|
+
defaultWorkspacePath,
|
|
68
|
+
workspaces: dedupeWorkspaces(entries),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return emptyCache();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function saveWorkspaceCache(cache) {
|
|
76
|
+
const next = {
|
|
77
|
+
version: CACHE_VERSION,
|
|
78
|
+
defaultWorkspacePath: (0, node_path_1.resolve)(cache.defaultWorkspacePath || getDefaultWorkspacePath()),
|
|
79
|
+
workspaces: dedupeWorkspaces(cache.workspaces.map(normalizeWorkspaceEntry).filter((entry) => Boolean(entry))),
|
|
80
|
+
};
|
|
81
|
+
const cachePath = getWorkspaceCachePath();
|
|
82
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(cachePath), { recursive: true });
|
|
83
|
+
(0, node_fs_1.writeFileSync)(cachePath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
function normalizeWorkspaceEntry(raw) {
|
|
87
|
+
if (!raw || typeof raw !== 'object') {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const record = raw;
|
|
91
|
+
const rawPath = typeof record.path === 'string' ? record.path.trim() : '';
|
|
92
|
+
if (!rawPath) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const absolutePath = (0, node_path_1.resolve)(rawPath);
|
|
96
|
+
const source = record.source === 'created' || record.source === 'found' || record.source === 'inspected' || record.source === 'cached'
|
|
97
|
+
? record.source
|
|
98
|
+
: 'cached';
|
|
99
|
+
return {
|
|
100
|
+
path: absolutePath,
|
|
101
|
+
name: typeof record.name === 'string' && record.name.trim().length > 0
|
|
102
|
+
? record.name.trim()
|
|
103
|
+
: basename(absolutePath),
|
|
104
|
+
source,
|
|
105
|
+
exists: typeof record.exists === 'boolean' ? record.exists : undefined,
|
|
106
|
+
lastSeenAt: typeof record.lastSeenAt === 'string' ? record.lastSeenAt : undefined,
|
|
107
|
+
lastScannedAt: typeof record.lastScannedAt === 'string' ? record.lastScannedAt : undefined,
|
|
108
|
+
appsCount: numberOrZero(record.appsCount),
|
|
109
|
+
assetsCount: numberOrZero(record.assetsCount),
|
|
110
|
+
packsCount: numberOrZero(record.packsCount),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function numberOrZero(value) {
|
|
114
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
|
|
115
|
+
}
|
|
116
|
+
function basename(absolutePath) {
|
|
117
|
+
const parts = absolutePath.split(/[\\/]/).filter(Boolean);
|
|
118
|
+
return parts[parts.length - 1] ?? absolutePath;
|
|
119
|
+
}
|
|
120
|
+
function dedupeWorkspaces(entries) {
|
|
121
|
+
const byPath = new Map();
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
byPath.set((0, node_path_1.resolve)(entry.path), { ...entry, path: (0, node_path_1.resolve)(entry.path) });
|
|
124
|
+
}
|
|
125
|
+
return [...byPath.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
126
|
+
}
|
|
127
|
+
function entryWithExists(entry) {
|
|
128
|
+
return { ...entry, exists: (0, node_fs_1.existsSync)(entry.path) };
|
|
129
|
+
}
|
|
130
|
+
function findCurrentWorkspaceRoot(startPath = process.cwd()) {
|
|
131
|
+
let currentPath = (0, node_path_1.resolve)(startPath);
|
|
132
|
+
try {
|
|
133
|
+
const stats = (0, node_fs_1.statSync)(currentPath);
|
|
134
|
+
if (!stats.isDirectory()) {
|
|
135
|
+
currentPath = (0, node_path_1.dirname)(currentPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
while (true) {
|
|
142
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(currentPath, CATALOGUE_FILE))) {
|
|
143
|
+
return currentPath;
|
|
144
|
+
}
|
|
145
|
+
const parentPath = (0, node_path_1.dirname)(currentPath);
|
|
146
|
+
if (parentPath === currentPath) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
currentPath = parentPath;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function loadWorkspaceCacheWithCurrentContext(options = {}) {
|
|
153
|
+
const cache = normalizeCacheDefaultPath(loadWorkspaceCache(), options.homeDir);
|
|
154
|
+
const currentWorkspacePath = findCurrentWorkspaceRoot(options.cwd ?? process.cwd());
|
|
155
|
+
if (!currentWorkspacePath) {
|
|
156
|
+
return cache;
|
|
157
|
+
}
|
|
158
|
+
const entry = inspectWorkspacePath(currentWorkspacePath, 'inspected');
|
|
159
|
+
const saved = saveWorkspaceCache({
|
|
160
|
+
...cache,
|
|
161
|
+
workspaces: upsertWorkspace(cache.workspaces, entry),
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
...saved,
|
|
165
|
+
currentWorkspacePath,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function normalizeCacheDefaultPath(cache, homeDir) {
|
|
169
|
+
if (!homeDir || cache.workspaces.length > 0) {
|
|
170
|
+
return cache;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
...cache,
|
|
174
|
+
defaultWorkspacePath: getDefaultWorkspacePath(homeDir),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function listWorkspaces(options = {}) {
|
|
178
|
+
let cache;
|
|
179
|
+
try {
|
|
180
|
+
cache = loadWorkspaceCacheWithCurrentContext(options);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
printWorkspaceScanError(error);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const listed = {
|
|
187
|
+
...cache,
|
|
188
|
+
workspaces: cache.workspaces.map(entryWithExists),
|
|
189
|
+
};
|
|
190
|
+
if (options.json) {
|
|
191
|
+
(0, output_1.printJson)(listed);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
printWorkspaceList(listed, options.platform);
|
|
195
|
+
}
|
|
196
|
+
return listed;
|
|
197
|
+
}
|
|
198
|
+
async function initWorkspace(targetPath, options = {}) {
|
|
199
|
+
const homeDir = options.homeDir ?? node_os_1.default.homedir();
|
|
200
|
+
const hasExplicitTarget = Boolean(targetPath && targetPath.trim().length > 0);
|
|
201
|
+
const workspacePath = (0, node_path_1.resolve)(hasExplicitTarget ? targetPath : getDefaultWorkspacePath(homeDir));
|
|
202
|
+
const bootstrapWorkspace = options.initProject ?? init_1.init;
|
|
203
|
+
const summary = await bootstrapWorkspace(workspacePath, { quiet: Boolean(options.json) });
|
|
204
|
+
if (!summary) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const entry = inspectWorkspacePath(workspacePath, 'created');
|
|
208
|
+
const cache = loadWorkspaceCache();
|
|
209
|
+
saveWorkspaceCache({
|
|
210
|
+
...cache,
|
|
211
|
+
defaultWorkspacePath: hasExplicitTarget ? cache.defaultWorkspacePath || getDefaultWorkspacePath(homeDir) : workspacePath,
|
|
212
|
+
workspaces: upsertWorkspace(cache.workspaces, entry),
|
|
213
|
+
});
|
|
214
|
+
if (options.json) {
|
|
215
|
+
(0, output_1.printJson)(entry);
|
|
216
|
+
}
|
|
217
|
+
return entry;
|
|
218
|
+
}
|
|
219
|
+
async function inspectWorkspace(targetPath, options = {}) {
|
|
220
|
+
const workspacePath = (0, node_path_1.resolve)(targetPath && targetPath.trim().length > 0 ? targetPath : process.cwd());
|
|
221
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(workspacePath, CATALOGUE_FILE))) {
|
|
222
|
+
console.error(`No catalogue.json found at ${workspacePath}.`);
|
|
223
|
+
console.error('Run "playdrop workspaces init" to create a workspace, or pass a workspace path.');
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
let entry;
|
|
228
|
+
try {
|
|
229
|
+
entry = inspectWorkspacePath(workspacePath, 'inspected');
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
printWorkspaceScanError(error);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const cache = loadWorkspaceCache();
|
|
236
|
+
saveWorkspaceCache({
|
|
237
|
+
...cache,
|
|
238
|
+
workspaces: upsertWorkspace(cache.workspaces, entry),
|
|
239
|
+
});
|
|
240
|
+
if (options.json) {
|
|
241
|
+
(0, output_1.printJson)(entry);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
printWorkspaceEntry(entry);
|
|
245
|
+
}
|
|
246
|
+
return entry;
|
|
247
|
+
}
|
|
248
|
+
async function refreshWorkspaces(rootArgs = [], options = {}) {
|
|
249
|
+
const cache = loadWorkspaceCache();
|
|
250
|
+
const explicitRoots = rootArgs.length > 0;
|
|
251
|
+
const roots = explicitRoots
|
|
252
|
+
? rootArgs
|
|
253
|
+
: dedupeStrings([cache.defaultWorkspacePath, ...cache.workspaces.map((entry) => entry.path)]);
|
|
254
|
+
const resolvedRoots = roots.map((root) => (0, node_path_1.resolve)(root));
|
|
255
|
+
const initiallySkippedRoots = explicitRoots
|
|
256
|
+
? []
|
|
257
|
+
: resolvedRoots.filter((root) => isMacProtectedWorkspaceRoot(root, options.homeDir, options.platform));
|
|
258
|
+
const skippedSet = new Set(initiallySkippedRoots);
|
|
259
|
+
const scannedRoots = resolvedRoots.filter((root) => !skippedSet.has(root));
|
|
260
|
+
const context = {
|
|
261
|
+
explicitRoots,
|
|
262
|
+
homeDir: options.homeDir,
|
|
263
|
+
platform: options.platform,
|
|
264
|
+
allowedProtectedRoots: explicitRoots
|
|
265
|
+
? resolvedRoots.filter((root) => isMacProtectedWorkspaceRoot(root, options.homeDir, options.platform))
|
|
266
|
+
: [],
|
|
267
|
+
skippedRoots: skippedSet,
|
|
268
|
+
};
|
|
269
|
+
const found = [];
|
|
270
|
+
try {
|
|
271
|
+
for (const root of scannedRoots) {
|
|
272
|
+
findWorkspacesAt(root, 0, new Set(), found, context);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
printWorkspaceScanError(error);
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const nextWorkspaces = dedupeWorkspaces([
|
|
280
|
+
...cache.workspaces,
|
|
281
|
+
...found,
|
|
282
|
+
]);
|
|
283
|
+
saveWorkspaceCache({
|
|
284
|
+
...cache,
|
|
285
|
+
workspaces: nextWorkspaces,
|
|
286
|
+
});
|
|
287
|
+
const skippedRoots = [...context.skippedRoots];
|
|
288
|
+
const payload = { scannedRoots, skippedRoots, workspaces: dedupeWorkspaces(found) };
|
|
289
|
+
if (options.json) {
|
|
290
|
+
(0, output_1.printJson)(payload);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log(`Scanned ${scannedRoots.length} root${scannedRoots.length === 1 ? '' : 's'}.`);
|
|
294
|
+
if (skippedRoots.length > 0) {
|
|
295
|
+
console.log(`Skipped ${skippedRoots.length} macOS protected root${skippedRoots.length === 1 ? '' : 's'}; pass the path explicitly to scan it.`);
|
|
296
|
+
}
|
|
297
|
+
printWorkspaceEntries(payload.workspaces);
|
|
298
|
+
}
|
|
299
|
+
return payload;
|
|
300
|
+
}
|
|
301
|
+
function isMacProtectedWorkspaceRoot(rootPath, homeDir = node_os_1.default.homedir(), platform = process.platform) {
|
|
302
|
+
if (platform !== 'darwin') {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
const absoluteRoot = (0, node_path_1.resolve)(rootPath);
|
|
306
|
+
const protectedRoots = ['Desktop', 'Documents', 'Downloads'].map((name) => (0, node_path_1.resolve)((0, node_path_1.join)(homeDir, name)));
|
|
307
|
+
return protectedRoots.some((protectedRoot) => absoluteRoot === protectedRoot || isPathInside(absoluteRoot, protectedRoot));
|
|
308
|
+
}
|
|
309
|
+
function isPathInside(candidate, parent) {
|
|
310
|
+
return candidate.startsWith(`${parent}/`);
|
|
311
|
+
}
|
|
312
|
+
function isAllowedProtectedPath(candidate, allowedProtectedRoots) {
|
|
313
|
+
return allowedProtectedRoots.some((allowedRoot) => candidate === allowedRoot || isPathInside(candidate, allowedRoot));
|
|
314
|
+
}
|
|
315
|
+
function dedupeStrings(values) {
|
|
316
|
+
return [...new Set(values.filter((value) => value.trim().length > 0).map((value) => (0, node_path_1.resolve)(value)))];
|
|
317
|
+
}
|
|
318
|
+
function upsertWorkspace(entries, entry) {
|
|
319
|
+
return dedupeWorkspaces([...entries, entry]);
|
|
320
|
+
}
|
|
321
|
+
function findWorkspacesAt(rootPath, depth, seen, found, context) {
|
|
322
|
+
if (depth > WORKSPACE_SEARCH_DEPTH) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const absolutePath = (0, node_path_1.resolve)(rootPath);
|
|
326
|
+
if (isMacProtectedWorkspaceRoot(absolutePath, context.homeDir, context.platform)
|
|
327
|
+
&& !isAllowedProtectedPath(absolutePath, context.allowedProtectedRoots)) {
|
|
328
|
+
context.skippedRoots.add(absolutePath);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (seen.has(absolutePath)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
seen.add(absolutePath);
|
|
335
|
+
let stats;
|
|
336
|
+
try {
|
|
337
|
+
stats = (0, node_fs_1.statSync)(absolutePath);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (depth === 0 && context.explicitRoots) {
|
|
341
|
+
throw new WorkspaceScanError(`Cannot scan ${absolutePath}: ${formatErrorMessage(error)}`);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!stats.isDirectory()) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(absolutePath, CATALOGUE_FILE))) {
|
|
349
|
+
found.push(inspectWorkspacePath(absolutePath, 'found'));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
let children;
|
|
353
|
+
try {
|
|
354
|
+
children = (0, node_fs_1.readdirSync)(absolutePath);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
throw new WorkspaceScanError(`Cannot read ${absolutePath}: ${formatErrorMessage(error)}`);
|
|
358
|
+
}
|
|
359
|
+
for (const child of children) {
|
|
360
|
+
if (child.startsWith('.') || SKIP_DIRECTORY_NAMES.has(child)) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
findWorkspacesAt((0, node_path_1.join)(absolutePath, child), depth + 1, seen, found, context);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function inspectWorkspacePath(workspacePath, source) {
|
|
367
|
+
const absolutePath = (0, node_path_1.resolve)(workspacePath);
|
|
368
|
+
const counts = countWorkspaceCatalogues(absolutePath, 0);
|
|
369
|
+
const timestamp = nowIso();
|
|
370
|
+
return {
|
|
371
|
+
path: absolutePath,
|
|
372
|
+
name: basename(absolutePath),
|
|
373
|
+
source,
|
|
374
|
+
exists: (0, node_fs_1.existsSync)(absolutePath),
|
|
375
|
+
lastSeenAt: timestamp,
|
|
376
|
+
lastScannedAt: timestamp,
|
|
377
|
+
appsCount: counts.apps,
|
|
378
|
+
assetsCount: counts.assets,
|
|
379
|
+
packsCount: counts.packs,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function countWorkspaceCatalogues(rootPath, depth) {
|
|
383
|
+
const counts = { apps: 0, assets: 0, packs: 0 };
|
|
384
|
+
if (depth > WORKSPACE_CONTENT_DEPTH) {
|
|
385
|
+
return counts;
|
|
386
|
+
}
|
|
387
|
+
const cataloguePath = (0, node_path_1.join)(rootPath, CATALOGUE_FILE);
|
|
388
|
+
if ((0, node_fs_1.existsSync)(cataloguePath)) {
|
|
389
|
+
const catalogueCounts = readCatalogueCounts(cataloguePath);
|
|
390
|
+
counts.apps += catalogueCounts.apps;
|
|
391
|
+
counts.assets += catalogueCounts.assets;
|
|
392
|
+
counts.packs += catalogueCounts.packs;
|
|
393
|
+
}
|
|
394
|
+
let children;
|
|
395
|
+
try {
|
|
396
|
+
children = (0, node_fs_1.readdirSync)(rootPath);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return counts;
|
|
400
|
+
}
|
|
401
|
+
for (const child of children) {
|
|
402
|
+
if (child.startsWith('.') || SKIP_DIRECTORY_NAMES.has(child)) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const childPath = (0, node_path_1.join)(rootPath, child);
|
|
406
|
+
try {
|
|
407
|
+
if (!(0, node_fs_1.statSync)(childPath).isDirectory()) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const childCounts = countWorkspaceCatalogues(childPath, depth + 1);
|
|
415
|
+
counts.apps += childCounts.apps;
|
|
416
|
+
counts.assets += childCounts.assets;
|
|
417
|
+
counts.packs += childCounts.packs;
|
|
418
|
+
}
|
|
419
|
+
return counts;
|
|
420
|
+
}
|
|
421
|
+
function readCatalogueCounts(cataloguePath) {
|
|
422
|
+
try {
|
|
423
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(cataloguePath, 'utf8'));
|
|
424
|
+
return {
|
|
425
|
+
apps: collectionCount(parsed.apps),
|
|
426
|
+
assets: collectionCount(parsed.assets),
|
|
427
|
+
packs: Math.max(collectionCount(parsed.packs), collectionCount(parsed.assetPacks)),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
throw new WorkspaceScanError(`Invalid catalogue.json at ${cataloguePath}: ${formatErrorMessage(error)}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function formatErrorMessage(error) {
|
|
435
|
+
return error instanceof Error ? error.message : String(error);
|
|
436
|
+
}
|
|
437
|
+
function printWorkspaceScanError(error) {
|
|
438
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
439
|
+
console.error(message);
|
|
440
|
+
process.exitCode = 1;
|
|
441
|
+
}
|
|
442
|
+
function collectionCount(value) {
|
|
443
|
+
if (Array.isArray(value)) {
|
|
444
|
+
return value.length;
|
|
445
|
+
}
|
|
446
|
+
if (value && typeof value === 'object') {
|
|
447
|
+
return Object.keys(value).length;
|
|
448
|
+
}
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
function printWorkspaceList(cache, platform = process.platform) {
|
|
452
|
+
if (cache.workspaces.length === 0) {
|
|
453
|
+
printNoCachedWorkspacesGuidance(cache.defaultWorkspacePath, platform);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
console.log(`Cached workspaces: ${cache.workspaces.length}`);
|
|
457
|
+
if (cache.currentWorkspacePath) {
|
|
458
|
+
console.log(`Current workspace: ${cache.currentWorkspacePath}`);
|
|
459
|
+
}
|
|
460
|
+
printWorkspaceEntries(cache.workspaces);
|
|
461
|
+
}
|
|
462
|
+
function printNoCachedWorkspacesGuidance(defaultWorkspacePath, platform = process.platform) {
|
|
463
|
+
console.log('Cached workspaces: 0');
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log('No cached PlayDrop workspaces yet.');
|
|
466
|
+
console.log('To find existing workspaces:');
|
|
467
|
+
console.log(' Run: playdrop workspaces refresh <folder>');
|
|
468
|
+
console.log(` Example: ${getWorkspaceRefreshExample(platform)}`);
|
|
469
|
+
if (platform === 'darwin') {
|
|
470
|
+
console.log(' This scans the folder you choose and macOS may ask for permission.');
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
console.log(' This scans only the folder you choose.');
|
|
474
|
+
}
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log('To create the recommended first workspace:');
|
|
477
|
+
console.log(' Run: playdrop workspaces init');
|
|
478
|
+
console.log(` Creates: ${defaultWorkspacePath}`);
|
|
479
|
+
}
|
|
480
|
+
function getWorkspaceRefreshExample(platform = process.platform) {
|
|
481
|
+
return platform === 'win32'
|
|
482
|
+
? 'playdrop workspaces refresh %USERPROFILE%\\Documents'
|
|
483
|
+
: 'playdrop workspaces refresh ~/Documents';
|
|
484
|
+
}
|
|
485
|
+
function printWorkspaceEntries(entries) {
|
|
486
|
+
if (entries.length === 0) {
|
|
487
|
+
console.log('No PlayDrop workspaces found.');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
printWorkspaceEntry(entry);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function printWorkspaceEntry(entry) {
|
|
495
|
+
const existsLabel = entry.exists === false ? 'missing' : 'available';
|
|
496
|
+
console.log(`${entry.path} (${existsLabel})`);
|
|
497
|
+
console.log(` ${entry.appsCount} apps, ${entry.assetsCount} assets, ${entry.packsCount} packs`);
|
|
498
|
+
}
|