@epic-web/workshop-utils 6.68.0 → 6.69.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.
@@ -102,12 +102,12 @@ export declare function checkForUpdatesCached(): Promise<{
102
102
  export declare function updateLocalRepo(): Promise<{
103
103
  readonly status: "success";
104
104
  readonly message: "The app is deployed" | "You are offline" | "Not in a git repo" | "Cannot find remote" | "No updates available.";
105
- } | {
106
- readonly status: "success";
107
- readonly message: "Updated successfully.";
108
105
  } | {
109
106
  readonly status: "error";
110
107
  readonly message: string;
108
+ } | {
109
+ readonly status: "success";
110
+ readonly message: "Updated successfully.";
111
111
  }>;
112
112
  export declare function getCommitInfo(): Promise<{
113
113
  hash: string;
@@ -127,6 +127,28 @@ export async function checkForUpdatesCached() {
127
127
  cache: checkForUpdatesCache,
128
128
  });
129
129
  }
130
+ async function runNpmInstallWithRetry(cwd, maxRetries = 3, baseDelayMs = 1000) {
131
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
132
+ try {
133
+ await execaCommand('npm install', { cwd, stdio: 'inherit' });
134
+ return;
135
+ }
136
+ catch (error) {
137
+ const isEbusy = error instanceof Error &&
138
+ (error.message.includes('EBUSY') ||
139
+ error.code === 'EBUSY');
140
+ if (isEbusy && attempt < maxRetries) {
141
+ const delay = baseDelayMs * Math.pow(2, attempt - 1);
142
+ console.log(`⚠️ File busy error (attempt ${attempt}/${maxRetries}). ` +
143
+ `Retrying in ${delay / 1000}s...`);
144
+ await new Promise((resolve) => setTimeout(resolve, delay));
145
+ }
146
+ else {
147
+ throw error;
148
+ }
149
+ }
150
+ }
151
+ }
130
152
  export async function updateLocalRepo() {
131
153
  const ENV = getEnv();
132
154
  if (ENV.EPICSHOP_DEPLOYED) {
@@ -157,7 +179,23 @@ export async function updateLocalRepo() {
157
179
  await execaCommand('git stash pop', { cwd });
158
180
  }
159
181
  console.log('📦 Re-installing dependencies...');
160
- await execaCommand('npm install', { cwd, stdio: 'inherit' });
182
+ try {
183
+ await runNpmInstallWithRetry(cwd);
184
+ }
185
+ catch (error) {
186
+ const isEbusy = error instanceof Error &&
187
+ (error.message.includes('EBUSY') ||
188
+ error.code === 'EBUSY');
189
+ if (isEbusy) {
190
+ return {
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
+ };
196
+ }
197
+ throw error;
198
+ }
161
199
  await cleanupEmptyExerciseDirectories(cwd);
162
200
  const postUpdateScript = getWorkshopConfig().scripts?.postupdate;
163
201
  if (postUpdateScript) {
@@ -3,12 +3,6 @@ export declare const PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
3
3
  export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
4
4
  declare const ConfigSchema: z.ZodObject<{
5
5
  reposDirectory: z.ZodOptional<z.ZodString>;
6
- packageManager: z.ZodOptional<z.ZodEnum<{
7
- npm: "npm";
8
- pnpm: "pnpm";
9
- yarn: "yarn";
10
- bun: "bun";
11
- }>>;
12
6
  }, z.core.$strip>;
13
7
  export type Workshop = {
14
8
  name: string;
@@ -25,10 +19,6 @@ export declare function getReposDirectory(): Promise<string>;
25
19
  export declare function isReposDirectoryConfigured(): Promise<boolean>;
26
20
  export declare function getDefaultReposDir(): string;
27
21
  export declare function setReposDirectory(directory: string): Promise<void>;
28
- export declare function getPackageManager(): Promise<PackageManager>;
29
- export declare function isPackageManagerConfigured(): Promise<boolean>;
30
- export declare function setPackageManager(packageManager: PackageManager): Promise<void>;
31
- export declare function clearPackageManager(): Promise<void>;
32
22
  export type ReposDirectoryStatus = {
33
23
  accessible: true;
34
24
  } | {
@@ -20,11 +20,9 @@ const EpicshopConfigSchema = z.object({
20
20
  .optional(),
21
21
  });
22
22
  export const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
23
- const PackageManagerSchema = z.enum(PACKAGE_MANAGERS);
24
23
  // Schema for workshop configuration (stored settings only)
25
24
  const ConfigSchema = z.object({
26
25
  reposDirectory: z.string().optional(),
27
- packageManager: PackageManagerSchema.optional(),
28
26
  });
29
27
  function getDefaultReposDirectory() {
30
28
  return path.join(os.homedir(), 'epic-workshops');
@@ -89,24 +87,6 @@ export async function setReposDirectory(directory) {
89
87
  config.reposDirectory = path.resolve(directory);
90
88
  await saveConfig(config);
91
89
  }
92
- export async function getPackageManager() {
93
- const config = await loadConfig();
94
- return config.packageManager ?? 'npm';
95
- }
96
- export async function isPackageManagerConfigured() {
97
- const config = await loadConfig();
98
- return Boolean(config.packageManager);
99
- }
100
- export async function setPackageManager(packageManager) {
101
- const config = await loadConfig();
102
- config.packageManager = packageManager;
103
- await saveConfig(config);
104
- }
105
- export async function clearPackageManager() {
106
- const config = await loadConfig();
107
- delete config.packageManager;
108
- await saveConfig(config);
109
- }
110
90
  /**
111
91
  * Verify that the configured repos directory exists and is accessible.
112
92
  * If the directory doesn't exist, attempts to create it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epic-web/workshop-utils",
3
- "version": "6.68.0",
3
+ "version": "6.69.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -221,6 +221,7 @@
221
221
  "mdx-bundler": "^10.1.1",
222
222
  "p-queue": "^9.1.0",
223
223
  "parse-git-diff": "^0.0.19",
224
+ "pkgmgr": "^1.0.0",
224
225
  "react": "^19.2.3",
225
226
  "react-dom": "^19.2.3",
226
227
  "react-router": "^7.12.0",