epicshop 6.67.0 → 6.68.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/cli.js +73 -1
- package/dist/commands/setup.d.ts +10 -0
- package/dist/commands/setup.js +136 -0
- package/dist/commands/workshops.d.ts +2 -0
- package/dist/commands/workshops.js +127 -63
- package/dist/utils/command-runner.d.ts +13 -0
- package/dist/utils/command-runner.js +41 -0
- package/dist/utils/package-manager.d.ts +5 -0
- package/dist/utils/package-manager.js +27 -0
- package/dist/utils/sentry-cli.js +1 -1
- package/package.json +8 -2
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import '@epic-web/workshop-utils/init-env';
|
|
3
|
+
import { PACKAGE_MANAGERS, getPackageManager, isPackageManagerConfigured, setPackageManager, } from '@epic-web/workshop-utils/workshops.server';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
5
|
import { matchSorter } from 'match-sorter';
|
|
5
6
|
import yargs from 'yargs';
|
|
6
7
|
import { hideBin } from 'yargs/helpers';
|
|
7
|
-
import { assertCanPrompt } from "./utils/cli-runtime.js";
|
|
8
|
+
import { assertCanPrompt, hasTty, isCiEnvironment, } from "./utils/cli-runtime.js";
|
|
9
|
+
import { detectRuntimePackageManager } from "./utils/package-manager.js";
|
|
8
10
|
import { initCliSentry } from "./utils/sentry-cli.js";
|
|
9
11
|
// Check for --help on start command before yargs parses
|
|
10
12
|
// (yargs exits before command handler when help is requested)
|
|
@@ -28,6 +30,43 @@ function formatHelp(helpText) {
|
|
|
28
30
|
.replace(/--[\w-]+/g, (match) => chalk.yellow(match))
|
|
29
31
|
.replace(/-\w(?=\s|,)/g, (match) => chalk.yellow(match));
|
|
30
32
|
}
|
|
33
|
+
async function maybePromptToUpdatePackageManager(parsedArgs) {
|
|
34
|
+
if (parsedArgs.includes('--help') || parsedArgs.includes('-h'))
|
|
35
|
+
return;
|
|
36
|
+
if (parsedArgs.includes('--silent') || parsedArgs.includes('-s'))
|
|
37
|
+
return;
|
|
38
|
+
if (parsedArgs[0] === 'config')
|
|
39
|
+
return;
|
|
40
|
+
if (isCiEnvironment() || !hasTty())
|
|
41
|
+
return;
|
|
42
|
+
const runtimeManager = detectRuntimePackageManager();
|
|
43
|
+
if (!runtimeManager)
|
|
44
|
+
return;
|
|
45
|
+
const isConfigured = await isPackageManagerConfigured();
|
|
46
|
+
if (!isConfigured)
|
|
47
|
+
return;
|
|
48
|
+
const configuredManager = await getPackageManager();
|
|
49
|
+
if (configuredManager === runtimeManager)
|
|
50
|
+
return;
|
|
51
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
52
|
+
const shouldUpdate = await confirm({
|
|
53
|
+
message: `You ran epicshop with ${runtimeManager}, but your default package manager is ${configuredManager}. Update the default?`,
|
|
54
|
+
default: true,
|
|
55
|
+
});
|
|
56
|
+
if (shouldUpdate) {
|
|
57
|
+
await setPackageManager(runtimeManager);
|
|
58
|
+
console.log(chalk.green(`✅ Default package manager updated to ${runtimeManager}.`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await maybePromptToUpdatePackageManager(args);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error.message === 'USER_QUIT') {
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
// Silently ignore other errors during package manager prompt to avoid disrupting CLI startup
|
|
69
|
+
}
|
|
31
70
|
// Set up yargs CLI
|
|
32
71
|
const cli = yargs(args)
|
|
33
72
|
.scriptName('epicshop')
|
|
@@ -138,6 +177,22 @@ const cli = yargs(args)
|
|
|
138
177
|
if (!result.success) {
|
|
139
178
|
process.exit(1);
|
|
140
179
|
}
|
|
180
|
+
})
|
|
181
|
+
.command('setup', 'Install workshop dependencies (uses configured package manager)', (yargs) => {
|
|
182
|
+
return yargs
|
|
183
|
+
.option('silent', {
|
|
184
|
+
alias: 's',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
description: 'Run without output logs',
|
|
187
|
+
default: false,
|
|
188
|
+
})
|
|
189
|
+
.example('$0 setup', 'Install workshop dependencies');
|
|
190
|
+
}, async (argv) => {
|
|
191
|
+
const { setup } = await import("./commands/setup.js");
|
|
192
|
+
const result = await setup({ silent: argv.silent });
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
141
196
|
})
|
|
142
197
|
.command('add [repo-name] [destination]', 'Add a workshop by cloning from epicweb-dev GitHub org', (yargs) => {
|
|
143
198
|
return yargs
|
|
@@ -268,6 +323,11 @@ const cli = yargs(args)
|
|
|
268
323
|
.option('repos-dir', {
|
|
269
324
|
type: 'string',
|
|
270
325
|
description: 'Set the default directory for workshop repos',
|
|
326
|
+
})
|
|
327
|
+
.option('package-manager', {
|
|
328
|
+
type: 'string',
|
|
329
|
+
choices: PACKAGE_MANAGERS,
|
|
330
|
+
description: 'Set the default package manager',
|
|
271
331
|
})
|
|
272
332
|
.option('silent', {
|
|
273
333
|
alias: 's',
|
|
@@ -283,6 +343,7 @@ const cli = yargs(args)
|
|
|
283
343
|
const result = await config({
|
|
284
344
|
subcommand: argv.subcommand === 'reset' ? 'reset' : undefined,
|
|
285
345
|
reposDir: argv.reposDir,
|
|
346
|
+
packageManager: argv.packageManager,
|
|
286
347
|
silent: argv.silent,
|
|
287
348
|
});
|
|
288
349
|
if (!result.success) {
|
|
@@ -1131,6 +1192,10 @@ try {
|
|
|
1131
1192
|
name: `${chalk.green('add')} - Add a workshop`,
|
|
1132
1193
|
value: 'add',
|
|
1133
1194
|
description: 'Clone a workshop from epicweb-dev GitHub org',
|
|
1195
|
+
}, {
|
|
1196
|
+
name: `${chalk.green('setup')} - Install dependencies`,
|
|
1197
|
+
value: 'setup',
|
|
1198
|
+
description: 'Install workshop dependencies (uses configured manager)',
|
|
1134
1199
|
}, {
|
|
1135
1200
|
name: `${chalk.green('remove')} - Remove a workshop`,
|
|
1136
1201
|
value: 'remove',
|
|
@@ -1239,6 +1304,13 @@ try {
|
|
|
1239
1304
|
process.exit(1);
|
|
1240
1305
|
break;
|
|
1241
1306
|
}
|
|
1307
|
+
case 'setup': {
|
|
1308
|
+
const { setup } = await import("./commands/setup.js");
|
|
1309
|
+
const result = await setup({});
|
|
1310
|
+
if (!result.success)
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1242
1314
|
case 'remove': {
|
|
1243
1315
|
const { findWorkshopRoot, remove } = await import("./commands/workshops.js");
|
|
1244
1316
|
const workshopRoot = await findWorkshopRoot();
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getErrorMessage } from '@epic-web/workshop-utils/utils';
|
|
4
|
+
import { getPackageManager, isPackageManagerConfigured, } from '@epic-web/workshop-utils/workshops.server';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { execa } from 'execa';
|
|
7
|
+
import { resolveCliCommand, runCommand, } from "../utils/command-runner.js";
|
|
8
|
+
import { formatPackageManagerCommand, getPackageManagerInstallArgs, getPackageManagerRunArgs, } from "../utils/package-manager.js";
|
|
9
|
+
function isPackageJson(value) {
|
|
10
|
+
if (!value || typeof value !== 'object')
|
|
11
|
+
return false;
|
|
12
|
+
if (!('scripts' in value))
|
|
13
|
+
return true;
|
|
14
|
+
const scriptsValue = value.scripts;
|
|
15
|
+
if (scriptsValue === undefined)
|
|
16
|
+
return true;
|
|
17
|
+
if (!scriptsValue || typeof scriptsValue !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
return Object.values(scriptsValue).every((script) => typeof script === 'string');
|
|
20
|
+
}
|
|
21
|
+
async function getPackageManagerVersion(packageManager) {
|
|
22
|
+
try {
|
|
23
|
+
const result = await execa(resolveCliCommand(packageManager), ['--version'], {
|
|
24
|
+
stdio: 'pipe',
|
|
25
|
+
});
|
|
26
|
+
return { success: true, version: result.stdout.trim() };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const message = getErrorMessage(error, 'Failed to check package manager');
|
|
30
|
+
const err = error instanceof Error ? error : new Error(message);
|
|
31
|
+
return { success: false, error: err };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isNpmVersionSupported(version) {
|
|
35
|
+
const [majorString, minorString] = version.split('.');
|
|
36
|
+
const major = Number(majorString);
|
|
37
|
+
const minor = Number(minorString);
|
|
38
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor))
|
|
39
|
+
return false;
|
|
40
|
+
return major > 8 || (major === 8 && minor >= 16);
|
|
41
|
+
}
|
|
42
|
+
function formatCommandResultError(result, fallbackMessage) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
message: result.message ?? fallbackMessage,
|
|
46
|
+
error: result.error,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function setup(options = {}) {
|
|
50
|
+
const { silent = false } = options;
|
|
51
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
52
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
53
|
+
try {
|
|
54
|
+
await fs.promises.access(packageJsonPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
const message = `package.json not found at ${packageJsonPath}`;
|
|
58
|
+
if (!silent) {
|
|
59
|
+
console.error(chalk.red(`❌ ${message}`));
|
|
60
|
+
}
|
|
61
|
+
return { success: false, message, error: new Error(message) };
|
|
62
|
+
}
|
|
63
|
+
let scripts;
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(await fs.promises.readFile(packageJsonPath, 'utf8'));
|
|
66
|
+
if (isPackageJson(parsed)) {
|
|
67
|
+
scripts = parsed.scripts;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const message = getErrorMessage(error, 'Failed to read package.json');
|
|
72
|
+
if (!silent) {
|
|
73
|
+
console.error(chalk.red(`❌ ${message}`));
|
|
74
|
+
}
|
|
75
|
+
return { success: false, message, error: error };
|
|
76
|
+
}
|
|
77
|
+
const packageManager = await getPackageManager();
|
|
78
|
+
const isConfigured = await isPackageManagerConfigured();
|
|
79
|
+
const managerLabel = isConfigured
|
|
80
|
+
? packageManager
|
|
81
|
+
: `${packageManager} (default)`;
|
|
82
|
+
const versionResult = await getPackageManagerVersion(packageManager);
|
|
83
|
+
if (!versionResult.success || !versionResult.version) {
|
|
84
|
+
const message = `Failed to run ${packageManager} --version. Please ensure ${packageManager} is installed.`;
|
|
85
|
+
if (!silent) {
|
|
86
|
+
console.error(chalk.red(`❌ ${message}`));
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
message,
|
|
91
|
+
error: versionResult.error ?? new Error(message),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (packageManager === 'npm' &&
|
|
95
|
+
!isNpmVersionSupported(versionResult.version)) {
|
|
96
|
+
const message = `npm version is ${versionResult.version} which is out of date. Please install npm@8.16.0 or greater.`;
|
|
97
|
+
if (!silent) {
|
|
98
|
+
console.error(chalk.red(`❌ ${message}`));
|
|
99
|
+
}
|
|
100
|
+
return { success: false, message, error: new Error(message) };
|
|
101
|
+
}
|
|
102
|
+
const installArgs = getPackageManagerInstallArgs(packageManager);
|
|
103
|
+
const installCommand = formatPackageManagerCommand(packageManager, installArgs);
|
|
104
|
+
if (!silent) {
|
|
105
|
+
console.log(chalk.cyan(`📦 Installing dependencies using ${chalk.bold(managerLabel)}...`));
|
|
106
|
+
console.log(chalk.gray(` To change this, run: npx epicshop config --package-manager <npm|pnpm|yarn|bun>`));
|
|
107
|
+
console.log(chalk.gray(` Running: ${installCommand}`));
|
|
108
|
+
}
|
|
109
|
+
const installResult = await runCommand(packageManager, installArgs, {
|
|
110
|
+
cwd,
|
|
111
|
+
silent,
|
|
112
|
+
});
|
|
113
|
+
if (!installResult.success) {
|
|
114
|
+
return formatCommandResultError(installResult, 'Failed to install dependencies');
|
|
115
|
+
}
|
|
116
|
+
const hasCustomSetup = Boolean(scripts?.['setup:custom']);
|
|
117
|
+
if (hasCustomSetup) {
|
|
118
|
+
const customArgs = getPackageManagerRunArgs(packageManager, 'setup:custom');
|
|
119
|
+
const customCommand = formatPackageManagerCommand(packageManager, customArgs);
|
|
120
|
+
if (!silent) {
|
|
121
|
+
console.log(chalk.cyan(`🔧 Running ${customCommand}...`));
|
|
122
|
+
}
|
|
123
|
+
const customResult = await runCommand(packageManager, customArgs, {
|
|
124
|
+
cwd,
|
|
125
|
+
silent,
|
|
126
|
+
});
|
|
127
|
+
if (!customResult.success) {
|
|
128
|
+
return formatCommandResultError(customResult, 'Failed to run setup:custom');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const message = 'Workshop setup complete';
|
|
132
|
+
if (!silent) {
|
|
133
|
+
console.log(chalk.green(`✅ ${message}`));
|
|
134
|
+
}
|
|
135
|
+
return { success: true, message };
|
|
136
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@epic-web/workshop-utils/init-env';
|
|
2
|
+
import { type PackageManager } from '@epic-web/workshop-utils/workshops.server';
|
|
2
3
|
/**
|
|
3
4
|
* Find the workshop root directory by walking up from the current directory
|
|
4
5
|
* looking for a package.json with an epicshop field.
|
|
@@ -27,6 +28,7 @@ export type StartOptions = {
|
|
|
27
28
|
};
|
|
28
29
|
export type ConfigOptions = {
|
|
29
30
|
reposDir?: string;
|
|
31
|
+
packageManager?: PackageManager;
|
|
30
32
|
silent?: boolean;
|
|
31
33
|
subcommand?: 'reset' | 'delete';
|
|
32
34
|
};
|
|
@@ -6,12 +6,14 @@ import { cachified, githubCache } from '@epic-web/workshop-utils/cache.server';
|
|
|
6
6
|
import { parseEpicshopConfig } from '@epic-web/workshop-utils/config.server';
|
|
7
7
|
import { getAuthInfo } from '@epic-web/workshop-utils/db.server';
|
|
8
8
|
import { userHasAccessToWorkshop } from '@epic-web/workshop-utils/epic-api.server';
|
|
9
|
-
import {
|
|
9
|
+
import { PACKAGE_MANAGERS, clearPackageManager, getPackageManager, isPackageManagerConfigured, setPackageManager, } from '@epic-web/workshop-utils/workshops.server';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
-
import { execa } from 'execa';
|
|
12
11
|
import { matchSorter, rankings } from 'match-sorter';
|
|
13
12
|
import ora from 'ora';
|
|
14
13
|
import { assertCanPrompt, isCiEnvironment } from "../utils/cli-runtime.js";
|
|
14
|
+
import { runCommand, runCommandInteractive } from "../utils/command-runner.js";
|
|
15
|
+
import { formatPackageManagerCommand, getPackageManagerRunArgs, } from "../utils/package-manager.js";
|
|
16
|
+
import { setup } from "./setup.js";
|
|
15
17
|
const GITHUB_ORG = 'epicweb-dev';
|
|
16
18
|
const TUTORIAL_REPO = 'epicshop-tutorial';
|
|
17
19
|
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
|
@@ -343,14 +345,7 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
343
345
|
error: cloneResult.error,
|
|
344
346
|
};
|
|
345
347
|
}
|
|
346
|
-
|
|
347
|
-
console.log(chalk.cyan(`🔧 Running npm run setup...`));
|
|
348
|
-
}
|
|
349
|
-
// Run npm run setup
|
|
350
|
-
const setupResult = await runCommand('npm', ['run', 'setup'], {
|
|
351
|
-
cwd: workshopPath,
|
|
352
|
-
silent,
|
|
353
|
-
});
|
|
348
|
+
const setupResult = await setup({ cwd: workshopPath, silent });
|
|
354
349
|
if (!setupResult.success) {
|
|
355
350
|
// Clean up the cloned directory on setup failure
|
|
356
351
|
if (!silent) {
|
|
@@ -364,7 +359,7 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
364
359
|
}
|
|
365
360
|
return {
|
|
366
361
|
success: false,
|
|
367
|
-
message: `Failed to
|
|
362
|
+
message: `Failed to set up workshop: ${setupResult.message}`,
|
|
368
363
|
error: setupResult.error,
|
|
369
364
|
};
|
|
370
365
|
}
|
|
@@ -794,12 +789,14 @@ export async function list({ silent = false, } = {}) {
|
|
|
794
789
|
});
|
|
795
790
|
},
|
|
796
791
|
});
|
|
792
|
+
const packageManager = await getPackageManager();
|
|
793
|
+
const startCommand = formatPackageManagerCommand(packageManager, getPackageManagerRunArgs(packageManager, 'start'));
|
|
797
794
|
// Show actions for selected workshop
|
|
798
795
|
const actionChoices = [
|
|
799
796
|
{
|
|
800
797
|
name: 'Start workshop',
|
|
801
798
|
value: 'start',
|
|
802
|
-
description:
|
|
799
|
+
description: `Run ${startCommand} in the workshop directory`,
|
|
803
800
|
},
|
|
804
801
|
{
|
|
805
802
|
name: 'Open in editor',
|
|
@@ -1084,8 +1081,10 @@ export async function startWorkshop(options = {}) {
|
|
|
1084
1081
|
console.log(chalk.cyan(`🚀 Starting ${chalk.bold(workshopToStart.title)}...`));
|
|
1085
1082
|
console.log(chalk.gray(` Path: ${workshopToStart.path}\n`));
|
|
1086
1083
|
}
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1084
|
+
const packageManager = await getPackageManager();
|
|
1085
|
+
const startArgs = getPackageManagerRunArgs(packageManager, 'start');
|
|
1086
|
+
// Run start script in the workshop directory
|
|
1087
|
+
const startResult = await runCommandInteractive(packageManager, startArgs, {
|
|
1089
1088
|
cwd: workshopToStart.path,
|
|
1090
1089
|
});
|
|
1091
1090
|
if (!startResult.success) {
|
|
@@ -1282,14 +1281,27 @@ export async function config(options = {}) {
|
|
|
1282
1281
|
return { success: true, message: 'Cancelled' };
|
|
1283
1282
|
}
|
|
1284
1283
|
}
|
|
1284
|
+
// Handle CLI flags for setting config values
|
|
1285
|
+
const messages = [];
|
|
1285
1286
|
if (options.reposDir) {
|
|
1286
1287
|
// Set the repos directory directly via CLI flag
|
|
1287
1288
|
const resolvedPath = path.resolve(options.reposDir);
|
|
1288
1289
|
await setReposDirectory(resolvedPath);
|
|
1289
1290
|
const message = `Repos directory set to: ${resolvedPath}`;
|
|
1291
|
+
messages.push(message);
|
|
1290
1292
|
if (!silent)
|
|
1291
1293
|
console.log(chalk.green(`✅ ${message}`));
|
|
1292
|
-
|
|
1294
|
+
}
|
|
1295
|
+
if (options.packageManager) {
|
|
1296
|
+
await setPackageManager(options.packageManager);
|
|
1297
|
+
const message = `Package manager set to: ${options.packageManager}`;
|
|
1298
|
+
messages.push(message);
|
|
1299
|
+
if (!silent)
|
|
1300
|
+
console.log(chalk.green(`✅ ${message}`));
|
|
1301
|
+
}
|
|
1302
|
+
// If either option was set, return now
|
|
1303
|
+
if (messages.length > 0) {
|
|
1304
|
+
return { success: true, message: messages.join('; ') };
|
|
1293
1305
|
}
|
|
1294
1306
|
if (silent) {
|
|
1295
1307
|
// In silent mode, just return current config
|
|
@@ -1301,13 +1313,16 @@ export async function config(options = {}) {
|
|
|
1301
1313
|
reason: 'select a configuration option',
|
|
1302
1314
|
hints: [
|
|
1303
1315
|
'Set repos dir directly: npx epicshop config --repos-dir <path>',
|
|
1316
|
+
'Set package manager directly: npx epicshop config --package-manager <npm|pnpm|yarn|bun>',
|
|
1304
1317
|
'Delete config non-interactively: npx epicshop config reset --silent',
|
|
1305
1318
|
],
|
|
1306
1319
|
});
|
|
1307
|
-
const { search, confirm } = await import('@inquirer/prompts');
|
|
1320
|
+
const { search, confirm, select } = await import('@inquirer/prompts');
|
|
1308
1321
|
const reposDir = await getReposDirectory();
|
|
1309
1322
|
const isConfigured = await isReposDirectoryConfigured();
|
|
1310
1323
|
const defaultDir = getDefaultReposDir();
|
|
1324
|
+
const packageManager = await getPackageManager();
|
|
1325
|
+
const isPackageManagerSet = await isPackageManagerConfigured();
|
|
1311
1326
|
// Build config options
|
|
1312
1327
|
const configOptions = [
|
|
1313
1328
|
{
|
|
@@ -1315,6 +1330,13 @@ export async function config(options = {}) {
|
|
|
1315
1330
|
value: 'repos-dir',
|
|
1316
1331
|
description: isConfigured ? reposDir : `${reposDir} (default)`,
|
|
1317
1332
|
},
|
|
1333
|
+
{
|
|
1334
|
+
name: `Package manager`,
|
|
1335
|
+
value: 'package-manager',
|
|
1336
|
+
description: isPackageManagerSet
|
|
1337
|
+
? packageManager
|
|
1338
|
+
: `${packageManager} (default)`,
|
|
1339
|
+
},
|
|
1318
1340
|
{
|
|
1319
1341
|
name: `Reset config file`,
|
|
1320
1342
|
value: 'reset',
|
|
@@ -1433,6 +1455,87 @@ export async function config(options = {}) {
|
|
|
1433
1455
|
return { success: true, message: 'Cancelled' };
|
|
1434
1456
|
}
|
|
1435
1457
|
}
|
|
1458
|
+
if (selectedConfig === 'package-manager') {
|
|
1459
|
+
console.log();
|
|
1460
|
+
console.log(chalk.bold(' Current value:'));
|
|
1461
|
+
if (isPackageManagerSet) {
|
|
1462
|
+
console.log(chalk.white(` ${packageManager}`));
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
console.log(chalk.gray(` ${packageManager} (default, not explicitly set)`));
|
|
1466
|
+
}
|
|
1467
|
+
console.log();
|
|
1468
|
+
const actionChoices = [
|
|
1469
|
+
{
|
|
1470
|
+
name: 'Edit',
|
|
1471
|
+
value: 'edit',
|
|
1472
|
+
description: 'Change the default package manager',
|
|
1473
|
+
},
|
|
1474
|
+
...(isPackageManagerSet
|
|
1475
|
+
? [
|
|
1476
|
+
{
|
|
1477
|
+
name: 'Remove',
|
|
1478
|
+
value: 'remove',
|
|
1479
|
+
description: 'Reset to default (npm)',
|
|
1480
|
+
},
|
|
1481
|
+
]
|
|
1482
|
+
: []),
|
|
1483
|
+
{
|
|
1484
|
+
name: 'Cancel',
|
|
1485
|
+
value: 'cancel',
|
|
1486
|
+
description: 'Go back without changes',
|
|
1487
|
+
},
|
|
1488
|
+
];
|
|
1489
|
+
const action = await search({
|
|
1490
|
+
message: 'What would you like to do?',
|
|
1491
|
+
source: async (input) => {
|
|
1492
|
+
if (!input)
|
|
1493
|
+
return actionChoices;
|
|
1494
|
+
return matchSorter(actionChoices, input, {
|
|
1495
|
+
keys: ['name', 'value', 'description'],
|
|
1496
|
+
});
|
|
1497
|
+
},
|
|
1498
|
+
});
|
|
1499
|
+
if (action === 'edit') {
|
|
1500
|
+
const selectedManager = await select({
|
|
1501
|
+
message: 'Select a package manager:',
|
|
1502
|
+
choices: PACKAGE_MANAGERS.map((manager) => ({
|
|
1503
|
+
name: manager,
|
|
1504
|
+
value: manager,
|
|
1505
|
+
})),
|
|
1506
|
+
});
|
|
1507
|
+
await setPackageManager(selectedManager);
|
|
1508
|
+
console.log();
|
|
1509
|
+
console.log(chalk.green(`✅ Package manager set to: ${chalk.bold(selectedManager)}`));
|
|
1510
|
+
return {
|
|
1511
|
+
success: true,
|
|
1512
|
+
message: `Package manager set to: ${selectedManager}`,
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
else if (action === 'remove') {
|
|
1516
|
+
const shouldRemove = await confirm({
|
|
1517
|
+
message: 'Reset package manager to default (npm)?',
|
|
1518
|
+
default: false,
|
|
1519
|
+
});
|
|
1520
|
+
if (shouldRemove) {
|
|
1521
|
+
await clearPackageManager();
|
|
1522
|
+
console.log();
|
|
1523
|
+
console.log(chalk.green(`✅ Package manager reset to default (npm).`));
|
|
1524
|
+
return {
|
|
1525
|
+
success: true,
|
|
1526
|
+
message: 'Package manager reset to default (npm).',
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
console.log(chalk.gray('\nNo changes made.'));
|
|
1531
|
+
return { success: true, message: 'Cancelled' };
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
console.log(chalk.gray('\nNo changes made.'));
|
|
1536
|
+
return { success: true, message: 'Cancelled' };
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1436
1539
|
return { success: true, message: 'Config viewed' };
|
|
1437
1540
|
}
|
|
1438
1541
|
catch (error) {
|
|
@@ -1719,7 +1822,8 @@ async function promptAndSetupAccessibleWorkshops() {
|
|
|
1719
1822
|
const { workshopExists } = await import('@epic-web/workshop-utils/workshops.server');
|
|
1720
1823
|
console.log(chalk.bold.cyan('\n📚 Workshop Setup\n'));
|
|
1721
1824
|
console.log(chalk.cyan('🐨 Next, you can select any workshops you’d like me to set up for you.'));
|
|
1722
|
-
|
|
1825
|
+
const packageManager = await getPackageManager();
|
|
1826
|
+
console.log(chalk.gray(` This will clone each workshop repo into your workshops directory and run setup using ${packageManager}.\n` +
|
|
1723
1827
|
' (If something fails, we’ll keep going and you can retry later with `npx epicshop add`.)\n'));
|
|
1724
1828
|
assertCanPrompt({
|
|
1725
1829
|
reason: 'select workshops to set up',
|
|
@@ -2000,12 +2104,7 @@ async function ensureTutorialAndStart() {
|
|
|
2000
2104
|
error: cloneResult.error,
|
|
2001
2105
|
};
|
|
2002
2106
|
}
|
|
2003
|
-
|
|
2004
|
-
// Run npm run setup
|
|
2005
|
-
const setupResult = await runCommand('npm', ['run', 'setup'], {
|
|
2006
|
-
cwd: workshopPath,
|
|
2007
|
-
silent: false,
|
|
2008
|
-
});
|
|
2107
|
+
const setupResult = await setup({ cwd: workshopPath, silent: false });
|
|
2009
2108
|
if (!setupResult.success) {
|
|
2010
2109
|
// Clean up on failure
|
|
2011
2110
|
console.log(chalk.yellow(`🧹 Cleaning up cloned directory...`));
|
|
@@ -2015,10 +2114,10 @@ async function ensureTutorialAndStart() {
|
|
|
2015
2114
|
catch {
|
|
2016
2115
|
// Ignore cleanup errors
|
|
2017
2116
|
}
|
|
2018
|
-
console.error(chalk.red(`❌ Failed to
|
|
2117
|
+
console.error(chalk.red(`❌ Failed to set up workshop: ${setupResult.message}`));
|
|
2019
2118
|
return {
|
|
2020
2119
|
success: false,
|
|
2021
|
-
message: `Failed to
|
|
2120
|
+
message: `Failed to set up workshop: ${setupResult.message}`,
|
|
2022
2121
|
error: setupResult.error,
|
|
2023
2122
|
};
|
|
2024
2123
|
}
|
|
@@ -2033,8 +2132,10 @@ async function ensureTutorialAndStart() {
|
|
|
2033
2132
|
}
|
|
2034
2133
|
await promptToStartTutorial(workshopTitle);
|
|
2035
2134
|
console.log(chalk.cyan(`\n🚀 Starting ${chalk.bold(workshopTitle)}...\n`));
|
|
2135
|
+
const packageManager = await getPackageManager();
|
|
2136
|
+
const startArgs = getPackageManagerRunArgs(packageManager, 'start');
|
|
2036
2137
|
// Start the workshop
|
|
2037
|
-
const startResult = await runCommandInteractive(
|
|
2138
|
+
const startResult = await runCommandInteractive(packageManager, startArgs, {
|
|
2038
2139
|
cwd: workshopPath,
|
|
2039
2140
|
});
|
|
2040
2141
|
if (!startResult.success) {
|
|
@@ -2200,40 +2301,3 @@ async function directoryExists(dirPath) {
|
|
|
2200
2301
|
return false;
|
|
2201
2302
|
}
|
|
2202
2303
|
}
|
|
2203
|
-
function resolveCliCommand(command) {
|
|
2204
|
-
// On Windows, package manager binaries are typically shimmed as *.cmd files.
|
|
2205
|
-
// Spawning "npm" directly can fail with ENOENT even though "npm.cmd" exists.
|
|
2206
|
-
if (process.platform === 'win32' &&
|
|
2207
|
-
(command === 'npm' || command === 'npx')) {
|
|
2208
|
-
return `${command}.cmd`;
|
|
2209
|
-
}
|
|
2210
|
-
return command;
|
|
2211
|
-
}
|
|
2212
|
-
function runCommand(command, args, options) {
|
|
2213
|
-
return execa(resolveCliCommand(command), args, {
|
|
2214
|
-
cwd: options.cwd,
|
|
2215
|
-
stdio: options.silent ? 'pipe' : 'inherit',
|
|
2216
|
-
}).then(() => ({ success: true }), (error) => {
|
|
2217
|
-
const message = getErrorMessage(error, 'Command failed');
|
|
2218
|
-
const err = error instanceof Error ? error : new Error(message);
|
|
2219
|
-
return { success: false, message, error: err };
|
|
2220
|
-
});
|
|
2221
|
-
}
|
|
2222
|
-
function runCommandInteractive(command, args, options) {
|
|
2223
|
-
return execa(resolveCliCommand(command), args, {
|
|
2224
|
-
cwd: options.cwd,
|
|
2225
|
-
stdio: 'inherit',
|
|
2226
|
-
}).then(() => ({ success: true }), (error) => {
|
|
2227
|
-
// If the process was terminated by a signal (e.g. user presses Ctrl+C),
|
|
2228
|
-
// treat it as success so we don't show a confusing error message.
|
|
2229
|
-
if (error &&
|
|
2230
|
-
typeof error === 'object' &&
|
|
2231
|
-
'signal' in error &&
|
|
2232
|
-
typeof error.signal === 'string') {
|
|
2233
|
-
return { success: true };
|
|
2234
|
-
}
|
|
2235
|
-
const message = getErrorMessage(error, 'Command failed');
|
|
2236
|
-
const err = error instanceof Error ? error : new Error(message);
|
|
2237
|
-
return { success: false, message, error: err };
|
|
2238
|
-
});
|
|
2239
|
-
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type CommandResult = {
|
|
2
|
+
success: boolean;
|
|
3
|
+
message?: string;
|
|
4
|
+
error?: Error;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveCliCommand(command: string): string;
|
|
7
|
+
export declare function runCommand(command: string, args: string[], options: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
silent?: boolean;
|
|
10
|
+
}): Promise<CommandResult>;
|
|
11
|
+
export declare function runCommandInteractive(command: string, args: string[], options: {
|
|
12
|
+
cwd: string;
|
|
13
|
+
}): Promise<CommandResult>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getErrorMessage } from '@epic-web/workshop-utils/utils';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
export function resolveCliCommand(command) {
|
|
4
|
+
// On Windows, package manager binaries are typically shimmed as *.cmd files.
|
|
5
|
+
if (process.platform === 'win32' &&
|
|
6
|
+
(command === 'npm' ||
|
|
7
|
+
command === 'npx' ||
|
|
8
|
+
command === 'pnpm' ||
|
|
9
|
+
command === 'yarn')) {
|
|
10
|
+
return `${command}.cmd`;
|
|
11
|
+
}
|
|
12
|
+
return command;
|
|
13
|
+
}
|
|
14
|
+
export function runCommand(command, args, options) {
|
|
15
|
+
return execa(resolveCliCommand(command), args, {
|
|
16
|
+
cwd: options.cwd,
|
|
17
|
+
stdio: options.silent ? 'pipe' : 'inherit',
|
|
18
|
+
}).then(() => ({ success: true }), (error) => {
|
|
19
|
+
const message = getErrorMessage(error, 'Command failed');
|
|
20
|
+
const err = error instanceof Error ? error : new Error(message);
|
|
21
|
+
return { success: false, message, error: err };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function runCommandInteractive(command, args, options) {
|
|
25
|
+
return execa(resolveCliCommand(command), args, {
|
|
26
|
+
cwd: options.cwd,
|
|
27
|
+
stdio: 'inherit',
|
|
28
|
+
}).then(() => ({ success: true }), (error) => {
|
|
29
|
+
// If the process was terminated by a signal (e.g. user presses Ctrl+C),
|
|
30
|
+
// treat it as success so we don't show a confusing error message.
|
|
31
|
+
if (error &&
|
|
32
|
+
typeof error === 'object' &&
|
|
33
|
+
'signal' in error &&
|
|
34
|
+
typeof error.signal === 'string') {
|
|
35
|
+
return { success: true };
|
|
36
|
+
}
|
|
37
|
+
const message = getErrorMessage(error, 'Command failed');
|
|
38
|
+
const err = error instanceof Error ? error : new Error(message);
|
|
39
|
+
return { success: false, message, error: err };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type PackageManager } from '@epic-web/workshop-utils/workshops.server';
|
|
2
|
+
export declare function detectRuntimePackageManager(): PackageManager | null;
|
|
3
|
+
export declare function getPackageManagerInstallArgs(_packageManager: PackageManager): string[];
|
|
4
|
+
export declare function getPackageManagerRunArgs(_packageManager: PackageManager, script: string): string[];
|
|
5
|
+
export declare function formatPackageManagerCommand(packageManager: PackageManager, args: string[]): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function detectPackageManager(value) {
|
|
2
|
+
if (!value)
|
|
3
|
+
return null;
|
|
4
|
+
if (value.includes('pnpm'))
|
|
5
|
+
return 'pnpm';
|
|
6
|
+
if (value.includes('yarn'))
|
|
7
|
+
return 'yarn';
|
|
8
|
+
if (value.includes('bun'))
|
|
9
|
+
return 'bun';
|
|
10
|
+
if (value.includes('npm'))
|
|
11
|
+
return 'npm';
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
export function detectRuntimePackageManager() {
|
|
15
|
+
const userAgent = (process.env.npm_config_user_agent ?? '').toLowerCase();
|
|
16
|
+
const execPath = (process.env.npm_execpath ?? '').toLowerCase();
|
|
17
|
+
return detectPackageManager(userAgent) ?? detectPackageManager(execPath);
|
|
18
|
+
}
|
|
19
|
+
export function getPackageManagerInstallArgs(_packageManager) {
|
|
20
|
+
return ['install'];
|
|
21
|
+
}
|
|
22
|
+
export function getPackageManagerRunArgs(_packageManager, script) {
|
|
23
|
+
return ['run', script];
|
|
24
|
+
}
|
|
25
|
+
export function formatPackageManagerCommand(packageManager, args) {
|
|
26
|
+
return `${packageManager} ${args.join(' ')}`.trim();
|
|
27
|
+
}
|
package/dist/utils/sentry-cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epicshop",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.68.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
"./package.json": "./package.json",
|
|
16
16
|
"./cleanup": "./src/commands/cleanup.ts",
|
|
17
|
+
"./setup": "./src/commands/setup.ts",
|
|
17
18
|
"./warm": "./src/commands/warm.ts",
|
|
18
19
|
"./start": "./src/commands/start.ts",
|
|
19
20
|
"./update": "./src/commands/update.ts",
|
|
@@ -33,6 +34,11 @@
|
|
|
33
34
|
"types": "./dist/commands/cleanup.d.ts",
|
|
34
35
|
"default": "./dist/commands/cleanup.js"
|
|
35
36
|
},
|
|
37
|
+
"./setup": {
|
|
38
|
+
"import": "./dist/commands/setup.js",
|
|
39
|
+
"types": "./dist/commands/setup.d.ts",
|
|
40
|
+
"default": "./dist/commands/setup.js"
|
|
41
|
+
},
|
|
36
42
|
"./warm": {
|
|
37
43
|
"import": "./dist/commands/warm.js",
|
|
38
44
|
"types": "./dist/commands/warm.d.ts",
|
|
@@ -93,7 +99,7 @@
|
|
|
93
99
|
"build:watch": "nx watch --projects=epicshop -- nx run \\$NX_PROJECT_NAME:build"
|
|
94
100
|
},
|
|
95
101
|
"dependencies": {
|
|
96
|
-
"@epic-web/workshop-utils": "6.
|
|
102
|
+
"@epic-web/workshop-utils": "6.68.0",
|
|
97
103
|
"@inquirer/prompts": "^8.2.0",
|
|
98
104
|
"@sentry/node": "^10.35.0",
|
|
99
105
|
"chalk": "^5.6.2",
|