@epic-web/workshop-utils 6.71.3 → 6.72.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/dist/cache.server.d.ts +24 -6
- package/dist/env.server.d.ts +8 -0
- package/dist/env.server.js +4 -0
- package/dist/git.server.d.ts +95 -61
- package/dist/git.server.js +120 -45
- package/dist/offline-videos.server.d.ts +1 -0
- package/dist/offline-videos.server.js +33 -0
- package/dist/package-install/package-install-check.server.d.ts +23 -0
- package/dist/package-install/package-install-check.server.js +229 -0
- package/package.json +1 -1
package/dist/cache.server.d.ts
CHANGED
|
@@ -197,20 +197,38 @@ export declare const checkForUpdatesCache: {
|
|
|
197
197
|
name: string;
|
|
198
198
|
set: (key: string, value: C.CacheEntry<{
|
|
199
199
|
updatesAvailable: boolean;
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
repoUpdatesAvailable: boolean;
|
|
201
|
+
dependenciesNeedInstall: boolean;
|
|
202
|
+
updateNotificationId: string | null;
|
|
203
|
+
commitsAhead: number | null;
|
|
204
|
+
commitsBehind: number | null;
|
|
205
|
+
localCommit: string | null;
|
|
206
|
+
remoteCommit: string | null;
|
|
202
207
|
diffLink: string | null;
|
|
208
|
+
message: string | null;
|
|
203
209
|
}>) => C.CacheEntry<{
|
|
204
210
|
updatesAvailable: boolean;
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
repoUpdatesAvailable: boolean;
|
|
212
|
+
dependenciesNeedInstall: boolean;
|
|
213
|
+
updateNotificationId: string | null;
|
|
214
|
+
commitsAhead: number | null;
|
|
215
|
+
commitsBehind: number | null;
|
|
216
|
+
localCommit: string | null;
|
|
217
|
+
remoteCommit: string | null;
|
|
207
218
|
diffLink: string | null;
|
|
219
|
+
message: string | null;
|
|
208
220
|
}>;
|
|
209
221
|
get: (key: string) => C.CacheEntry<{
|
|
210
222
|
updatesAvailable: boolean;
|
|
211
|
-
|
|
212
|
-
|
|
223
|
+
repoUpdatesAvailable: boolean;
|
|
224
|
+
dependenciesNeedInstall: boolean;
|
|
225
|
+
updateNotificationId: string | null;
|
|
226
|
+
commitsAhead: number | null;
|
|
227
|
+
commitsBehind: number | null;
|
|
228
|
+
localCommit: string | null;
|
|
229
|
+
remoteCommit: string | null;
|
|
213
230
|
diffLink: string | null;
|
|
231
|
+
message: string | null;
|
|
214
232
|
}> | undefined;
|
|
215
233
|
delete: (key: string) => boolean;
|
|
216
234
|
};
|
package/dist/env.server.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ declare const schema: z.ZodPipe<z.ZodObject<{
|
|
|
10
10
|
EPICSHOP_GITHUB_REPO: z.ZodDefault<z.ZodString>;
|
|
11
11
|
EPICSHOP_GITHUB_ROOT: z.ZodDefault<z.ZodString>;
|
|
12
12
|
EPICSHOP_APP_VERSION: z.ZodOptional<z.ZodString>;
|
|
13
|
+
EPICSHOP_APP_COMMIT_SHA: z.ZodOptional<z.ZodString>;
|
|
13
14
|
EPICSHOP_PARENT_PORT: z.ZodOptional<z.ZodString>;
|
|
14
15
|
EPICSHOP_PARENT_TOKEN: z.ZodOptional<z.ZodString>;
|
|
15
16
|
EPICSHOP_APP_LOCATION: z.ZodOptional<z.ZodString>;
|
|
@@ -19,6 +20,7 @@ declare const schema: z.ZodPipe<z.ZodObject<{
|
|
|
19
20
|
SENTRY_ORG: z.ZodDefault<z.ZodString>;
|
|
20
21
|
SENTRY_PROJECT: z.ZodDefault<z.ZodString>;
|
|
21
22
|
SENTRY_PROJECT_ID: z.ZodDefault<z.ZodString>;
|
|
23
|
+
SENTRY_RELEASE: z.ZodOptional<z.ZodString>;
|
|
22
24
|
}, z.core.$strip>, z.ZodTransform<{
|
|
23
25
|
EPICSHOP_CONTEXT_CWD: string;
|
|
24
26
|
EPICSHOP_WORKSHOP_INSTANCE_ID: string;
|
|
@@ -32,9 +34,11 @@ declare const schema: z.ZodPipe<z.ZodObject<{
|
|
|
32
34
|
SENTRY_PROJECT: string;
|
|
33
35
|
SENTRY_PROJECT_ID: string;
|
|
34
36
|
EPICSHOP_APP_VERSION?: string | undefined;
|
|
37
|
+
EPICSHOP_APP_COMMIT_SHA?: string | undefined;
|
|
35
38
|
EPICSHOP_PARENT_PORT?: string | undefined;
|
|
36
39
|
EPICSHOP_PARENT_TOKEN?: string | undefined;
|
|
37
40
|
EPICSHOP_APP_LOCATION?: string | undefined;
|
|
41
|
+
SENTRY_RELEASE?: string | undefined;
|
|
38
42
|
}, {
|
|
39
43
|
EPICSHOP_CONTEXT_CWD: string;
|
|
40
44
|
EPICSHOP_WORKSHOP_INSTANCE_ID: string;
|
|
@@ -48,9 +52,11 @@ declare const schema: z.ZodPipe<z.ZodObject<{
|
|
|
48
52
|
SENTRY_PROJECT: string;
|
|
49
53
|
SENTRY_PROJECT_ID: string;
|
|
50
54
|
EPICSHOP_APP_VERSION?: string | undefined;
|
|
55
|
+
EPICSHOP_APP_COMMIT_SHA?: string | undefined;
|
|
51
56
|
EPICSHOP_PARENT_PORT?: string | undefined;
|
|
52
57
|
EPICSHOP_PARENT_TOKEN?: string | undefined;
|
|
53
58
|
EPICSHOP_APP_LOCATION?: string | undefined;
|
|
59
|
+
SENTRY_RELEASE?: string | undefined;
|
|
54
60
|
}>>;
|
|
55
61
|
declare global {
|
|
56
62
|
namespace NodeJS {
|
|
@@ -76,11 +82,13 @@ export declare function getEnv(): {
|
|
|
76
82
|
EPICSHOP_GITHUB_ROOT: string;
|
|
77
83
|
EPICSHOP_DEPLOYED: boolean;
|
|
78
84
|
EPICSHOP_APP_VERSION: string | undefined;
|
|
85
|
+
EPICSHOP_APP_COMMIT_SHA: string | undefined;
|
|
79
86
|
EPICSHOP_PARENT_PORT: string | undefined;
|
|
80
87
|
EPICSHOP_PARENT_TOKEN: string | undefined;
|
|
81
88
|
EPICSHOP_IS_PUBLISHED: boolean;
|
|
82
89
|
SENTRY_DSN: string;
|
|
83
90
|
SENTRY_PROJECT_ID: string;
|
|
91
|
+
SENTRY_RELEASE: string | undefined;
|
|
84
92
|
};
|
|
85
93
|
type ENV = ReturnType<typeof getEnv>;
|
|
86
94
|
declare global {
|
package/dist/env.server.js
CHANGED
|
@@ -16,6 +16,7 @@ const schema = z
|
|
|
16
16
|
EPICSHOP_GITHUB_REPO: z.string().default(''),
|
|
17
17
|
EPICSHOP_GITHUB_ROOT: z.string().default(''),
|
|
18
18
|
EPICSHOP_APP_VERSION: z.string().optional(),
|
|
19
|
+
EPICSHOP_APP_COMMIT_SHA: z.string().optional(),
|
|
19
20
|
EPICSHOP_PARENT_PORT: z.string().optional(),
|
|
20
21
|
EPICSHOP_PARENT_TOKEN: z.string().optional(),
|
|
21
22
|
EPICSHOP_APP_LOCATION: z.string().optional(),
|
|
@@ -30,6 +31,7 @@ const schema = z
|
|
|
30
31
|
SENTRY_ORG: z.string().default('kent-c-dodds-tech-llc'),
|
|
31
32
|
SENTRY_PROJECT: z.string().default('epicshop'),
|
|
32
33
|
SENTRY_PROJECT_ID: z.string().default('4509630082252800'),
|
|
34
|
+
SENTRY_RELEASE: z.string().optional(),
|
|
33
35
|
})
|
|
34
36
|
.transform(async (env) => {
|
|
35
37
|
if (env.EPICSHOP_CONTEXT_CWD === '') {
|
|
@@ -122,10 +124,12 @@ export function getEnv() {
|
|
|
122
124
|
EPICSHOP_DEPLOYED: process.env.EPICSHOP_DEPLOYED === 'true' ||
|
|
123
125
|
process.env.EPICSHOP_DEPLOYED === '1',
|
|
124
126
|
EPICSHOP_APP_VERSION: process.env.EPICSHOP_APP_VERSION,
|
|
127
|
+
EPICSHOP_APP_COMMIT_SHA: process.env.EPICSHOP_APP_COMMIT_SHA,
|
|
125
128
|
EPICSHOP_PARENT_PORT: process.env.EPICSHOP_PARENT_PORT,
|
|
126
129
|
EPICSHOP_PARENT_TOKEN: process.env.EPICSHOP_PARENT_TOKEN,
|
|
127
130
|
EPICSHOP_IS_PUBLISHED: process.env.EPICSHOP_IS_PUBLISHED === 'true',
|
|
128
131
|
SENTRY_DSN: process.env.SENTRY_DSN,
|
|
129
132
|
SENTRY_PROJECT_ID: process.env.SENTRY_PROJECT_ID,
|
|
133
|
+
SENTRY_RELEASE: process.env.SENTRY_RELEASE,
|
|
130
134
|
};
|
|
131
135
|
}
|
package/dist/git.server.d.ts
CHANGED
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
import "./init-env.js";
|
|
2
2
|
export declare function checkForUpdates(): Promise<{
|
|
3
3
|
readonly updatesAvailable: false;
|
|
4
|
+
readonly dependenciesNeedInstall: false;
|
|
5
|
+
readonly updateNotificationId: null;
|
|
4
6
|
readonly message: "The app is deployed";
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
7
|
+
readonly repoUpdatesAvailable: boolean;
|
|
8
|
+
readonly commitsAhead: null;
|
|
9
|
+
readonly commitsBehind: null;
|
|
10
|
+
readonly localCommit: null;
|
|
11
|
+
readonly remoteCommit: null;
|
|
12
|
+
readonly diffLink: null;
|
|
10
13
|
} | {
|
|
11
|
-
readonly updatesAvailable: false;
|
|
12
14
|
readonly message: "You are offline";
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
16
|
-
readonly
|
|
17
|
-
readonly
|
|
15
|
+
readonly updatesAvailable: boolean;
|
|
16
|
+
readonly repoUpdatesAvailable: boolean;
|
|
17
|
+
readonly dependenciesNeedInstall: boolean;
|
|
18
|
+
readonly updateNotificationId: string | null;
|
|
19
|
+
readonly commitsAhead: null;
|
|
20
|
+
readonly commitsBehind: null;
|
|
21
|
+
readonly localCommit: null;
|
|
22
|
+
readonly remoteCommit: null;
|
|
23
|
+
readonly diffLink: null;
|
|
18
24
|
} | {
|
|
19
|
-
readonly updatesAvailable: false;
|
|
20
25
|
readonly message: "Not in a git repo";
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
26
|
+
readonly updatesAvailable: boolean;
|
|
27
|
+
readonly repoUpdatesAvailable: boolean;
|
|
28
|
+
readonly dependenciesNeedInstall: boolean;
|
|
29
|
+
readonly updateNotificationId: string | null;
|
|
30
|
+
readonly commitsAhead: null;
|
|
31
|
+
readonly commitsBehind: null;
|
|
32
|
+
readonly localCommit: null;
|
|
33
|
+
readonly remoteCommit: null;
|
|
34
|
+
readonly diffLink: null;
|
|
26
35
|
} | {
|
|
27
|
-
readonly updatesAvailable: false;
|
|
28
36
|
readonly message: "Cannot find remote";
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
31
|
-
readonly
|
|
32
|
-
readonly
|
|
33
|
-
readonly
|
|
37
|
+
readonly updatesAvailable: boolean;
|
|
38
|
+
readonly repoUpdatesAvailable: boolean;
|
|
39
|
+
readonly dependenciesNeedInstall: boolean;
|
|
40
|
+
readonly updateNotificationId: string | null;
|
|
41
|
+
readonly commitsAhead: null;
|
|
42
|
+
readonly commitsBehind: null;
|
|
43
|
+
readonly localCommit: null;
|
|
44
|
+
readonly remoteCommit: null;
|
|
45
|
+
readonly diffLink: null;
|
|
34
46
|
} | {
|
|
35
47
|
readonly updatesAvailable: boolean;
|
|
48
|
+
readonly repoUpdatesAvailable: boolean;
|
|
49
|
+
readonly dependenciesNeedInstall: boolean;
|
|
50
|
+
readonly updateNotificationId: string | null;
|
|
36
51
|
readonly commitsAhead: number;
|
|
37
52
|
readonly commitsBehind: number;
|
|
38
53
|
readonly localCommit: string;
|
|
@@ -40,48 +55,66 @@ export declare function checkForUpdates(): Promise<{
|
|
|
40
55
|
readonly diffLink: string | null;
|
|
41
56
|
readonly message: null;
|
|
42
57
|
} | {
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
45
|
-
readonly remoteCommit: string | undefined;
|
|
58
|
+
readonly localCommit: string | null;
|
|
59
|
+
readonly remoteCommit: string | null;
|
|
46
60
|
readonly diffLink: string | null;
|
|
47
|
-
readonly
|
|
48
|
-
readonly
|
|
49
|
-
readonly
|
|
61
|
+
readonly updatesAvailable: boolean;
|
|
62
|
+
readonly repoUpdatesAvailable: boolean;
|
|
63
|
+
readonly dependenciesNeedInstall: boolean;
|
|
64
|
+
readonly updateNotificationId: string | null;
|
|
65
|
+
readonly commitsAhead: null;
|
|
66
|
+
readonly commitsBehind: null;
|
|
67
|
+
readonly message: null;
|
|
50
68
|
}>;
|
|
51
69
|
export declare function checkForUpdatesCached(): Promise<{
|
|
52
70
|
readonly updatesAvailable: false;
|
|
71
|
+
readonly dependenciesNeedInstall: false;
|
|
72
|
+
readonly updateNotificationId: null;
|
|
53
73
|
readonly message: "The app is deployed";
|
|
54
|
-
readonly
|
|
55
|
-
readonly
|
|
56
|
-
readonly
|
|
57
|
-
readonly
|
|
58
|
-
readonly
|
|
74
|
+
readonly repoUpdatesAvailable: boolean;
|
|
75
|
+
readonly commitsAhead: null;
|
|
76
|
+
readonly commitsBehind: null;
|
|
77
|
+
readonly localCommit: null;
|
|
78
|
+
readonly remoteCommit: null;
|
|
79
|
+
readonly diffLink: null;
|
|
59
80
|
} | {
|
|
60
|
-
readonly updatesAvailable: false;
|
|
61
81
|
readonly message: "You are offline";
|
|
62
|
-
readonly
|
|
63
|
-
readonly
|
|
64
|
-
readonly
|
|
65
|
-
readonly
|
|
66
|
-
readonly
|
|
82
|
+
readonly updatesAvailable: boolean;
|
|
83
|
+
readonly repoUpdatesAvailable: boolean;
|
|
84
|
+
readonly dependenciesNeedInstall: boolean;
|
|
85
|
+
readonly updateNotificationId: string | null;
|
|
86
|
+
readonly commitsAhead: null;
|
|
87
|
+
readonly commitsBehind: null;
|
|
88
|
+
readonly localCommit: null;
|
|
89
|
+
readonly remoteCommit: null;
|
|
90
|
+
readonly diffLink: null;
|
|
67
91
|
} | {
|
|
68
|
-
readonly updatesAvailable: false;
|
|
69
92
|
readonly message: "Not in a git repo";
|
|
70
|
-
readonly
|
|
71
|
-
readonly
|
|
72
|
-
readonly
|
|
73
|
-
readonly
|
|
74
|
-
readonly
|
|
93
|
+
readonly updatesAvailable: boolean;
|
|
94
|
+
readonly repoUpdatesAvailable: boolean;
|
|
95
|
+
readonly dependenciesNeedInstall: boolean;
|
|
96
|
+
readonly updateNotificationId: string | null;
|
|
97
|
+
readonly commitsAhead: null;
|
|
98
|
+
readonly commitsBehind: null;
|
|
99
|
+
readonly localCommit: null;
|
|
100
|
+
readonly remoteCommit: null;
|
|
101
|
+
readonly diffLink: null;
|
|
75
102
|
} | {
|
|
76
|
-
readonly updatesAvailable: false;
|
|
77
103
|
readonly message: "Cannot find remote";
|
|
78
|
-
readonly
|
|
79
|
-
readonly
|
|
80
|
-
readonly
|
|
81
|
-
readonly
|
|
82
|
-
readonly
|
|
104
|
+
readonly updatesAvailable: boolean;
|
|
105
|
+
readonly repoUpdatesAvailable: boolean;
|
|
106
|
+
readonly dependenciesNeedInstall: boolean;
|
|
107
|
+
readonly updateNotificationId: string | null;
|
|
108
|
+
readonly commitsAhead: null;
|
|
109
|
+
readonly commitsBehind: null;
|
|
110
|
+
readonly localCommit: null;
|
|
111
|
+
readonly remoteCommit: null;
|
|
112
|
+
readonly diffLink: null;
|
|
83
113
|
} | {
|
|
84
114
|
readonly updatesAvailable: boolean;
|
|
115
|
+
readonly repoUpdatesAvailable: boolean;
|
|
116
|
+
readonly dependenciesNeedInstall: boolean;
|
|
117
|
+
readonly updateNotificationId: string | null;
|
|
85
118
|
readonly commitsAhead: number;
|
|
86
119
|
readonly commitsBehind: number;
|
|
87
120
|
readonly localCommit: string;
|
|
@@ -89,15 +122,16 @@ export declare function checkForUpdatesCached(): Promise<{
|
|
|
89
122
|
readonly diffLink: string | null;
|
|
90
123
|
readonly message: null;
|
|
91
124
|
} | {
|
|
92
|
-
readonly
|
|
93
|
-
readonly
|
|
94
|
-
readonly remoteCommit: string | undefined;
|
|
125
|
+
readonly localCommit: string | null;
|
|
126
|
+
readonly remoteCommit: string | null;
|
|
95
127
|
readonly diffLink: string | null;
|
|
96
|
-
readonly
|
|
97
|
-
readonly
|
|
98
|
-
readonly
|
|
99
|
-
|
|
100
|
-
readonly
|
|
128
|
+
readonly updatesAvailable: boolean;
|
|
129
|
+
readonly repoUpdatesAvailable: boolean;
|
|
130
|
+
readonly dependenciesNeedInstall: boolean;
|
|
131
|
+
readonly updateNotificationId: string | null;
|
|
132
|
+
readonly commitsAhead: null;
|
|
133
|
+
readonly commitsBehind: null;
|
|
134
|
+
readonly message: null;
|
|
101
135
|
}>;
|
|
102
136
|
export declare function updateLocalRepo(): Promise<{
|
|
103
137
|
readonly status: "success";
|
|
@@ -107,7 +141,7 @@ export declare function updateLocalRepo(): Promise<{
|
|
|
107
141
|
readonly message: string;
|
|
108
142
|
} | {
|
|
109
143
|
readonly status: "success";
|
|
110
|
-
readonly message: "Updated successfully.";
|
|
144
|
+
readonly message: "Updated successfully." | "Dependencies updated successfully.";
|
|
111
145
|
}>;
|
|
112
146
|
export declare function getCommitInfo(): Promise<{
|
|
113
147
|
hash: string;
|
package/dist/git.server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { cachified, checkForUpdatesCache } from "./cache.server.js";
|
|
|
7
7
|
import { getWorkshopConfig } from "./config.server.js";
|
|
8
8
|
import { getEnv } from "./env.server.js";
|
|
9
9
|
import { logger } from "./logger.js";
|
|
10
|
+
import { getInstallCommand, getWorkspaceInstallStatus, } from "./package-install/package-install-check.server.js";
|
|
10
11
|
import { checkConnection } from "./utils.server.js";
|
|
11
12
|
import { getErrorMessage } from "./utils.js";
|
|
12
13
|
const gitLog = logger('epic:git');
|
|
@@ -16,6 +17,11 @@ function dirHasTrackedFiles(cwd, dirPath) {
|
|
|
16
17
|
function isDirectory(dirPath) {
|
|
17
18
|
return fs.stat(dirPath).then((s) => s.isDirectory(), () => false);
|
|
18
19
|
}
|
|
20
|
+
function getDependencyNotificationId(dependencyHash) {
|
|
21
|
+
if (!dependencyHash)
|
|
22
|
+
return null;
|
|
23
|
+
return `update-deps-${dependencyHash}`;
|
|
24
|
+
}
|
|
19
25
|
async function cleanupEmptyExerciseDirectories(cwd) {
|
|
20
26
|
console.log('🧹 Cleaning up empty exercise directories...');
|
|
21
27
|
try {
|
|
@@ -62,25 +68,48 @@ async function getDiffUrl(commitBefore, commitAfter) {
|
|
|
62
68
|
}
|
|
63
69
|
export async function checkForUpdates() {
|
|
64
70
|
const ENV = getEnv();
|
|
71
|
+
const cwd = getWorkshopRoot();
|
|
72
|
+
const dependencyStatus = await getWorkspaceInstallStatus(cwd);
|
|
73
|
+
const dependencyNotificationId = dependencyStatus.dependenciesNeedInstall
|
|
74
|
+
? getDependencyNotificationId(dependencyStatus.dependencyHash)
|
|
75
|
+
: null;
|
|
76
|
+
const baseResult = {
|
|
77
|
+
updatesAvailable: dependencyStatus.dependenciesNeedInstall,
|
|
78
|
+
repoUpdatesAvailable: false,
|
|
79
|
+
dependenciesNeedInstall: dependencyStatus.dependenciesNeedInstall,
|
|
80
|
+
updateNotificationId: dependencyNotificationId,
|
|
81
|
+
commitsAhead: null,
|
|
82
|
+
commitsBehind: null,
|
|
83
|
+
localCommit: null,
|
|
84
|
+
remoteCommit: null,
|
|
85
|
+
diffLink: null,
|
|
86
|
+
message: null,
|
|
87
|
+
};
|
|
65
88
|
if (ENV.EPICSHOP_DEPLOYED) {
|
|
66
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
...baseResult,
|
|
91
|
+
updatesAvailable: false,
|
|
92
|
+
dependenciesNeedInstall: false,
|
|
93
|
+
updateNotificationId: null,
|
|
94
|
+
message: 'The app is deployed',
|
|
95
|
+
};
|
|
67
96
|
}
|
|
68
|
-
const cwd = getWorkshopRoot();
|
|
69
97
|
const online = await checkConnection();
|
|
70
98
|
if (!online) {
|
|
71
|
-
return {
|
|
99
|
+
return { ...baseResult, message: 'You are offline' };
|
|
72
100
|
}
|
|
73
101
|
const isInRepo = await execaCommand('git rev-parse --is-inside-work-tree', {
|
|
74
102
|
cwd,
|
|
75
103
|
}).then(() => true, () => false);
|
|
76
104
|
if (!isInRepo) {
|
|
77
|
-
return {
|
|
105
|
+
return { ...baseResult, message: 'Not in a git repo' };
|
|
78
106
|
}
|
|
79
107
|
const { stdout: remote } = await execaCommand('git remote', { cwd });
|
|
80
108
|
if (!remote) {
|
|
81
|
-
return {
|
|
109
|
+
return { ...baseResult, message: 'Cannot find remote' };
|
|
82
110
|
}
|
|
83
|
-
let localCommit
|
|
111
|
+
let localCommit = null;
|
|
112
|
+
let remoteCommit = null;
|
|
84
113
|
try {
|
|
85
114
|
const currentBranch = (await execaCommand('git rev-parse --abbrev-ref HEAD', { cwd })).stdout.trim();
|
|
86
115
|
localCommit = (await execaCommand('git rev-parse --short HEAD', { cwd })).stdout.trim();
|
|
@@ -90,21 +119,28 @@ export async function checkForUpdates() {
|
|
|
90
119
|
})).stdout.trim();
|
|
91
120
|
const { stdout } = await execa('git', ['rev-list', '--count', '--left-right', 'HEAD...@{upstream}'], { cwd });
|
|
92
121
|
const [ahead = 0, behind = 0] = stdout.trim().split(/\s+/).map(Number);
|
|
93
|
-
const
|
|
122
|
+
const repoUpdatesAvailable = behind > 0;
|
|
123
|
+
const updatesAvailable = repoUpdatesAvailable || dependencyStatus.dependenciesNeedInstall;
|
|
124
|
+
const updateNotificationId = repoUpdatesAvailable
|
|
125
|
+
? `update-repo-${remoteCommit}`
|
|
126
|
+
: dependencyNotificationId;
|
|
94
127
|
return {
|
|
95
128
|
updatesAvailable,
|
|
129
|
+
repoUpdatesAvailable,
|
|
130
|
+
dependenciesNeedInstall: dependencyStatus.dependenciesNeedInstall,
|
|
131
|
+
updateNotificationId,
|
|
96
132
|
commitsAhead: ahead,
|
|
97
133
|
commitsBehind: behind,
|
|
98
134
|
localCommit,
|
|
99
135
|
remoteCommit,
|
|
100
136
|
diffLink: await getDiffUrl(localCommit, remoteCommit),
|
|
101
|
-
message:
|
|
137
|
+
message: baseResult.message,
|
|
102
138
|
};
|
|
103
139
|
}
|
|
104
140
|
catch (error) {
|
|
105
141
|
console.error('Unable to check for updates', getErrorMessage(error));
|
|
106
142
|
return {
|
|
107
|
-
|
|
143
|
+
...baseResult,
|
|
108
144
|
localCommit,
|
|
109
145
|
remoteCommit,
|
|
110
146
|
diffLink: localCommit && remoteCommit
|
|
@@ -116,7 +152,18 @@ export async function checkForUpdates() {
|
|
|
116
152
|
export async function checkForUpdatesCached() {
|
|
117
153
|
const ENV = getEnv();
|
|
118
154
|
if (ENV.EPICSHOP_DEPLOYED) {
|
|
119
|
-
return {
|
|
155
|
+
return {
|
|
156
|
+
updatesAvailable: false,
|
|
157
|
+
repoUpdatesAvailable: false,
|
|
158
|
+
dependenciesNeedInstall: false,
|
|
159
|
+
updateNotificationId: null,
|
|
160
|
+
commitsAhead: null,
|
|
161
|
+
commitsBehind: null,
|
|
162
|
+
localCommit: null,
|
|
163
|
+
remoteCommit: null,
|
|
164
|
+
diffLink: null,
|
|
165
|
+
message: 'The app is deployed',
|
|
166
|
+
};
|
|
120
167
|
}
|
|
121
168
|
const key = 'checkForUpdates';
|
|
122
169
|
return cachified({
|
|
@@ -127,10 +174,10 @@ export async function checkForUpdatesCached() {
|
|
|
127
174
|
cache: checkForUpdatesCache,
|
|
128
175
|
});
|
|
129
176
|
}
|
|
130
|
-
async function
|
|
177
|
+
async function runInstallWithRetry(cwd, command, args, maxRetries = 3, baseDelayMs = 1000) {
|
|
131
178
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
132
179
|
try {
|
|
133
|
-
await
|
|
180
|
+
await execa(command, args, { cwd, stdio: 'inherit' });
|
|
134
181
|
return;
|
|
135
182
|
}
|
|
136
183
|
catch (error) {
|
|
@@ -160,49 +207,77 @@ export async function updateLocalRepo() {
|
|
|
160
207
|
const cwd = getWorkshopRoot();
|
|
161
208
|
try {
|
|
162
209
|
const updates = await checkForUpdates();
|
|
163
|
-
|
|
210
|
+
const repoUpdatesAvailable = updates.repoUpdatesAvailable;
|
|
211
|
+
let dependencyStatus = await getWorkspaceInstallStatus(cwd);
|
|
212
|
+
let rootsNeedingInstall = dependencyStatus.roots.filter((status) => status.dependenciesNeedInstall);
|
|
213
|
+
if (!repoUpdatesAvailable && rootsNeedingInstall.length === 0) {
|
|
164
214
|
return {
|
|
165
215
|
status: 'success',
|
|
166
216
|
message: updates.message ?? 'No updates available.',
|
|
167
217
|
};
|
|
168
218
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
219
|
+
let didPull = false;
|
|
220
|
+
let didInstall = false;
|
|
221
|
+
if (repoUpdatesAvailable) {
|
|
222
|
+
const uncommittedChanges = (await execaCommand('git status --porcelain', { cwd })).stdout.trim()
|
|
223
|
+
.length > 0;
|
|
224
|
+
if (uncommittedChanges) {
|
|
225
|
+
console.log('👜 Stashing uncommitted changes...');
|
|
226
|
+
await execaCommand('git stash --include-untracked', { cwd });
|
|
227
|
+
}
|
|
228
|
+
console.log('⬇️ Pulling latest changes...');
|
|
229
|
+
await execaCommand('git pull origin HEAD', { cwd });
|
|
230
|
+
if (uncommittedChanges) {
|
|
231
|
+
console.log('👜 re-applying stashed changes...');
|
|
232
|
+
await execaCommand('git stash pop', { cwd });
|
|
233
|
+
}
|
|
234
|
+
didPull = true;
|
|
235
|
+
dependencyStatus = await getWorkspaceInstallStatus(cwd);
|
|
236
|
+
rootsNeedingInstall = dependencyStatus.roots.filter((status) => status.dependenciesNeedInstall);
|
|
174
237
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
238
|
+
if (rootsNeedingInstall.length > 0) {
|
|
239
|
+
for (const root of rootsNeedingInstall) {
|
|
240
|
+
const rootLabel = path.relative(cwd, root.rootDir).replace(/\\/g, '/') || '.';
|
|
241
|
+
const { command, args } = getInstallCommand(root.packageManager);
|
|
242
|
+
const commandLabel = `${command} ${args.join(' ')}`.trim();
|
|
243
|
+
console.log(`📦 Installing dependencies in ${rootLabel} using ${commandLabel}...`);
|
|
244
|
+
try {
|
|
245
|
+
await runInstallWithRetry(root.rootDir, command, args);
|
|
246
|
+
didInstall = true;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
const isEbusy = error instanceof Error &&
|
|
250
|
+
(error.message.includes('EBUSY') ||
|
|
251
|
+
error.code === 'EBUSY');
|
|
252
|
+
if (isEbusy) {
|
|
253
|
+
return {
|
|
254
|
+
status: 'error',
|
|
255
|
+
message: `${commandLabel} failed: files are locked. ` +
|
|
256
|
+
'Please close any editors or terminals using this directory, ' +
|
|
257
|
+
`then run: ${commandLabel}`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
180
263
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
await runNpmInstallWithRetry(cwd);
|
|
264
|
+
else if (repoUpdatesAvailable) {
|
|
265
|
+
console.log('📦 Dependencies already match package.json. Skipping install.');
|
|
184
266
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
status: 'error',
|
|
192
|
-
message: 'npm install failed: files are locked. ' +
|
|
193
|
-
'Please close any editors or terminals using this directory, ' +
|
|
194
|
-
'then run: npm install',
|
|
195
|
-
};
|
|
267
|
+
if (didPull || didInstall) {
|
|
268
|
+
await cleanupEmptyExerciseDirectories(cwd);
|
|
269
|
+
const postUpdateScript = getWorkshopConfig().scripts?.postupdate;
|
|
270
|
+
if (postUpdateScript) {
|
|
271
|
+
console.log('🏃 Running post update script...');
|
|
272
|
+
await execaCommand(postUpdateScript, { cwd, stdio: 'inherit' });
|
|
196
273
|
}
|
|
197
|
-
throw error;
|
|
198
274
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
return { status: 'success', message: 'Updated successfully.' };
|
|
275
|
+
return {
|
|
276
|
+
status: 'success',
|
|
277
|
+
message: repoUpdatesAvailable
|
|
278
|
+
? 'Updated successfully.'
|
|
279
|
+
: 'Dependencies updated successfully.',
|
|
280
|
+
};
|
|
206
281
|
}
|
|
207
282
|
catch (error) {
|
|
208
283
|
return { status: 'error', message: getErrorMessage(error) };
|
|
@@ -75,6 +75,7 @@ export declare function getOfflineVideoDownloadState(): OfflineVideoDownloadStat
|
|
|
75
75
|
export declare function getOfflineVideoSummary({ request, }?: {
|
|
76
76
|
request?: Request;
|
|
77
77
|
}): Promise<OfflineVideoSummary>;
|
|
78
|
+
export declare function getOfflineVideoPlaybackIds(): Promise<Array<string> | null>;
|
|
78
79
|
export declare function warmOfflineVideoSummary(): Promise<void>;
|
|
79
80
|
export declare function startOfflineVideoDownload({ request, }?: {
|
|
80
81
|
request?: Request;
|
|
@@ -563,6 +563,39 @@ export async function getOfflineVideoSummary({ request, } = {}) {
|
|
|
563
563
|
downloadState,
|
|
564
564
|
};
|
|
565
565
|
}
|
|
566
|
+
export async function getOfflineVideoPlaybackIds() {
|
|
567
|
+
try {
|
|
568
|
+
const workshop = getWorkshopIdentity();
|
|
569
|
+
const keyInfo = await getOfflineVideoKeyInfo({
|
|
570
|
+
userId: null,
|
|
571
|
+
allowUserIdUpdate: false,
|
|
572
|
+
});
|
|
573
|
+
if (!keyInfo)
|
|
574
|
+
return [];
|
|
575
|
+
const index = await readOfflineVideoIndex();
|
|
576
|
+
const playbackIds = [];
|
|
577
|
+
for (const [playbackId, entry] of Object.entries(index)) {
|
|
578
|
+
if (entry.status !== 'ready')
|
|
579
|
+
continue;
|
|
580
|
+
if (entry.keyId !== keyInfo.keyId)
|
|
581
|
+
continue;
|
|
582
|
+
if (entry.cryptoVersion !== keyInfo.config.version)
|
|
583
|
+
continue;
|
|
584
|
+
if (!hasWorkshop(entry, workshop.id))
|
|
585
|
+
continue;
|
|
586
|
+
if (typeof entry.size === 'number' && entry.size <= 0)
|
|
587
|
+
continue;
|
|
588
|
+
playbackIds.push(playbackId);
|
|
589
|
+
}
|
|
590
|
+
return playbackIds;
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
log.warn('Failed to load offline video playback ids', {
|
|
594
|
+
error: formatDownloadError(error),
|
|
595
|
+
});
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
566
599
|
export async function warmOfflineVideoSummary() {
|
|
567
600
|
await getWorkshopVideoCollection();
|
|
568
601
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RootPackageInstallStatus = {
|
|
2
|
+
rootDir: string;
|
|
3
|
+
packageJsonPath: string;
|
|
4
|
+
packageManager: string | null;
|
|
5
|
+
dependencyHash: string | null;
|
|
6
|
+
dependenciesNeedInstall: boolean;
|
|
7
|
+
missingDependencies: Array<string>;
|
|
8
|
+
missingDevDependencies: Array<string>;
|
|
9
|
+
missingOptionalDependencies: Array<string>;
|
|
10
|
+
reason: 'missing-node-modules' | 'missing-dependencies' | 'package-json-unreadable' | 'up-to-date';
|
|
11
|
+
};
|
|
12
|
+
export type WorkspaceInstallStatus = {
|
|
13
|
+
roots: Array<RootPackageInstallStatus>;
|
|
14
|
+
dependenciesNeedInstall: boolean;
|
|
15
|
+
dependencyHash: string | null;
|
|
16
|
+
};
|
|
17
|
+
export declare function getRootPackageJsonPaths(cwd: string): Promise<string[]>;
|
|
18
|
+
export declare function getRootPackageInstallStatus(packageJsonPath: string): Promise<RootPackageInstallStatus>;
|
|
19
|
+
export declare function getWorkspaceInstallStatus(cwd: string): Promise<WorkspaceInstallStatus>;
|
|
20
|
+
export declare function getInstallCommand(packageManager: string | null): {
|
|
21
|
+
command: string;
|
|
22
|
+
args: string[];
|
|
23
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { globby } from 'globby';
|
|
5
|
+
import { getErrorMessage } from "../utils.js";
|
|
6
|
+
const workspaceIgnorePatterns = [
|
|
7
|
+
'**/node_modules/**',
|
|
8
|
+
'**/.git/**',
|
|
9
|
+
'**/.cache/**',
|
|
10
|
+
'**/dist/**',
|
|
11
|
+
'**/build/**',
|
|
12
|
+
'**/coverage/**',
|
|
13
|
+
];
|
|
14
|
+
function hashString(value) {
|
|
15
|
+
return createHash('sha256').update(value).digest('hex').slice(0, 8);
|
|
16
|
+
}
|
|
17
|
+
function normalizeDependencyMap(dependencies) {
|
|
18
|
+
const entries = Object.entries(dependencies ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
19
|
+
return Object.fromEntries(entries);
|
|
20
|
+
}
|
|
21
|
+
function getDependencySnapshot(packageJson) {
|
|
22
|
+
return {
|
|
23
|
+
dependencies: normalizeDependencyMap(packageJson.dependencies),
|
|
24
|
+
devDependencies: normalizeDependencyMap(packageJson.devDependencies),
|
|
25
|
+
optionalDependencies: normalizeDependencyMap(packageJson.optionalDependencies),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function parsePackageManager(value) {
|
|
29
|
+
if (!value)
|
|
30
|
+
return null;
|
|
31
|
+
const [name] = value.split('@');
|
|
32
|
+
return name || null;
|
|
33
|
+
}
|
|
34
|
+
function normalizeWorkspacePattern(pattern) {
|
|
35
|
+
const trimmed = pattern.trim();
|
|
36
|
+
if (!trimmed)
|
|
37
|
+
return trimmed;
|
|
38
|
+
const isNegated = trimmed.startsWith('!');
|
|
39
|
+
const raw = isNegated ? trimmed.slice(1) : trimmed;
|
|
40
|
+
const normalized = raw.replace(/\\/g, '/');
|
|
41
|
+
const withPackageJson = normalized.endsWith('package.json')
|
|
42
|
+
? normalized
|
|
43
|
+
: path.posix.join(normalized, 'package.json');
|
|
44
|
+
return isNegated ? `!${withPackageJson}` : withPackageJson;
|
|
45
|
+
}
|
|
46
|
+
async function readPackageJson(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const contents = await fs.readFile(filePath, 'utf8');
|
|
49
|
+
return JSON.parse(contents);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.warn(`⚠️ Failed to read package.json at ${filePath}:`, getErrorMessage(error));
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function getWorkspacePackageJsonPaths(packageJsonPath) {
|
|
57
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
58
|
+
if (!packageJson)
|
|
59
|
+
return [];
|
|
60
|
+
const workspaces = Array.isArray(packageJson.workspaces)
|
|
61
|
+
? packageJson.workspaces
|
|
62
|
+
: (packageJson.workspaces?.packages ?? []);
|
|
63
|
+
if (!workspaces.length)
|
|
64
|
+
return [];
|
|
65
|
+
const workspacePatterns = workspaces.map(normalizeWorkspacePattern);
|
|
66
|
+
return globby(workspacePatterns, {
|
|
67
|
+
cwd: path.dirname(packageJsonPath),
|
|
68
|
+
absolute: true,
|
|
69
|
+
ignore: workspaceIgnorePatterns,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function listPackageJsonPaths(cwd) {
|
|
73
|
+
return globby('**/package.json', {
|
|
74
|
+
cwd,
|
|
75
|
+
absolute: true,
|
|
76
|
+
ignore: workspaceIgnorePatterns,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function listInstalledPackages(nodeModulesPath) {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fs.readdir(nodeModulesPath, { withFileTypes: true });
|
|
82
|
+
const packages = new Set();
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (entry.name.startsWith('.'))
|
|
85
|
+
continue;
|
|
86
|
+
if (entry.name.startsWith('@')) {
|
|
87
|
+
if (!entry.isDirectory())
|
|
88
|
+
continue;
|
|
89
|
+
const scopePath = path.join(nodeModulesPath, entry.name);
|
|
90
|
+
const scopeEntries = await fs.readdir(scopePath, {
|
|
91
|
+
withFileTypes: true,
|
|
92
|
+
});
|
|
93
|
+
for (const scopedEntry of scopeEntries) {
|
|
94
|
+
if (scopedEntry.name.startsWith('.'))
|
|
95
|
+
continue;
|
|
96
|
+
if (scopedEntry.isDirectory() || scopedEntry.isSymbolicLink()) {
|
|
97
|
+
packages.add(`${entry.name}/${scopedEntry.name}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
103
|
+
packages.add(entry.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return packages;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getExpectedDependencies(dependencies) {
|
|
113
|
+
return Object.keys(dependencies ?? {}).sort();
|
|
114
|
+
}
|
|
115
|
+
export async function getRootPackageJsonPaths(cwd) {
|
|
116
|
+
const allPackageJsonPaths = await listPackageJsonPaths(cwd);
|
|
117
|
+
const workspacePackageJsonPaths = new Set();
|
|
118
|
+
for (const packageJsonPath of allPackageJsonPaths) {
|
|
119
|
+
const workspacePaths = await getWorkspacePackageJsonPaths(packageJsonPath);
|
|
120
|
+
for (const workspacePath of workspacePaths) {
|
|
121
|
+
workspacePackageJsonPaths.add(path.resolve(workspacePath));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return allPackageJsonPaths
|
|
125
|
+
.map((packageJsonPath) => path.resolve(packageJsonPath))
|
|
126
|
+
.filter((packageJsonPath) => !workspacePackageJsonPaths.has(packageJsonPath))
|
|
127
|
+
.sort();
|
|
128
|
+
}
|
|
129
|
+
export async function getRootPackageInstallStatus(packageJsonPath) {
|
|
130
|
+
const rootDir = path.dirname(packageJsonPath);
|
|
131
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
132
|
+
if (!packageJson) {
|
|
133
|
+
return {
|
|
134
|
+
rootDir,
|
|
135
|
+
packageJsonPath,
|
|
136
|
+
packageManager: null,
|
|
137
|
+
dependencyHash: null,
|
|
138
|
+
dependenciesNeedInstall: false,
|
|
139
|
+
missingDependencies: [],
|
|
140
|
+
missingDevDependencies: [],
|
|
141
|
+
missingOptionalDependencies: [],
|
|
142
|
+
reason: 'package-json-unreadable',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const dependencySnapshot = getDependencySnapshot(packageJson);
|
|
146
|
+
const dependencyHash = hashString(JSON.stringify(dependencySnapshot));
|
|
147
|
+
const packageManager = parsePackageManager(packageJson.packageManager);
|
|
148
|
+
const dependencies = getExpectedDependencies(packageJson.dependencies);
|
|
149
|
+
const devDependencies = getExpectedDependencies(packageJson.devDependencies);
|
|
150
|
+
const optionalDependencies = getExpectedDependencies(packageJson.optionalDependencies);
|
|
151
|
+
const expectedDependencies = [
|
|
152
|
+
...dependencies,
|
|
153
|
+
...devDependencies,
|
|
154
|
+
...optionalDependencies,
|
|
155
|
+
];
|
|
156
|
+
if (expectedDependencies.length === 0) {
|
|
157
|
+
return {
|
|
158
|
+
rootDir,
|
|
159
|
+
packageJsonPath,
|
|
160
|
+
packageManager,
|
|
161
|
+
dependencyHash,
|
|
162
|
+
dependenciesNeedInstall: false,
|
|
163
|
+
missingDependencies: [],
|
|
164
|
+
missingDevDependencies: [],
|
|
165
|
+
missingOptionalDependencies: [],
|
|
166
|
+
reason: 'up-to-date',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const installedPackages = await listInstalledPackages(path.join(rootDir, 'node_modules'));
|
|
170
|
+
if (!installedPackages) {
|
|
171
|
+
return {
|
|
172
|
+
rootDir,
|
|
173
|
+
packageJsonPath,
|
|
174
|
+
packageManager,
|
|
175
|
+
dependencyHash,
|
|
176
|
+
dependenciesNeedInstall: true,
|
|
177
|
+
missingDependencies: dependencies,
|
|
178
|
+
missingDevDependencies: devDependencies,
|
|
179
|
+
missingOptionalDependencies: optionalDependencies,
|
|
180
|
+
reason: 'missing-node-modules',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const missingDependencies = dependencies.filter((dep) => !installedPackages.has(dep));
|
|
184
|
+
const missingDevDependencies = devDependencies.filter((dep) => !installedPackages.has(dep));
|
|
185
|
+
const missingOptionalDependencies = optionalDependencies.filter((dep) => !installedPackages.has(dep));
|
|
186
|
+
const dependenciesNeedInstall = missingDependencies.length > 0 || missingDevDependencies.length > 0;
|
|
187
|
+
return {
|
|
188
|
+
rootDir,
|
|
189
|
+
packageJsonPath,
|
|
190
|
+
packageManager,
|
|
191
|
+
dependencyHash,
|
|
192
|
+
dependenciesNeedInstall,
|
|
193
|
+
missingDependencies,
|
|
194
|
+
missingDevDependencies,
|
|
195
|
+
missingOptionalDependencies,
|
|
196
|
+
reason: dependenciesNeedInstall ? 'missing-dependencies' : 'up-to-date',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export async function getWorkspaceInstallStatus(cwd) {
|
|
200
|
+
const rootPackageJsonPaths = await getRootPackageJsonPaths(cwd);
|
|
201
|
+
const rootStatuses = await Promise.all(rootPackageJsonPaths.map(getRootPackageInstallStatus));
|
|
202
|
+
const dependenciesNeedInstall = rootStatuses.some((status) => status.dependenciesNeedInstall);
|
|
203
|
+
const dependencyHash = rootStatuses.length > 0
|
|
204
|
+
? hashString(JSON.stringify(rootStatuses
|
|
205
|
+
.map((status) => ({
|
|
206
|
+
path: path.relative(cwd, status.packageJsonPath),
|
|
207
|
+
hash: status.dependencyHash,
|
|
208
|
+
}))
|
|
209
|
+
.sort((a, b) => a.path.localeCompare(b.path))))
|
|
210
|
+
: null;
|
|
211
|
+
return {
|
|
212
|
+
roots: rootStatuses,
|
|
213
|
+
dependenciesNeedInstall,
|
|
214
|
+
dependencyHash,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function getInstallCommand(packageManager) {
|
|
218
|
+
switch (packageManager) {
|
|
219
|
+
case 'pnpm':
|
|
220
|
+
return { command: 'pnpm', args: ['install'] };
|
|
221
|
+
case 'yarn':
|
|
222
|
+
return { command: 'yarn', args: ['install'] };
|
|
223
|
+
case 'bun':
|
|
224
|
+
return { command: 'bun', args: ['install'] };
|
|
225
|
+
case 'npm':
|
|
226
|
+
default:
|
|
227
|
+
return { command: 'npm', args: ['install'] };
|
|
228
|
+
}
|
|
229
|
+
}
|