epicshop 6.74.2 โ 6.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +3 -2
- package/dist/commands/workshops.d.ts +1 -0
- package/dist/commands/workshops.js +69 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,8 +41,8 @@ epicshop start <workshop>
|
|
|
41
41
|
|
|
42
42
|
### Helpful commands
|
|
43
43
|
|
|
44
|
-
- `epicshop add <repo-name> [destination]`: clone a workshop from the
|
|
45
|
-
`epicweb-dev` GitHub org
|
|
44
|
+
- `epicshop add <repo-name[#ref]> [destination]`: clone a workshop from the
|
|
45
|
+
`epicweb-dev` GitHub org (use `#` to pin a tag, branch, or commit)
|
|
46
46
|
- `epicshop list`: list your workshops
|
|
47
47
|
- `epicshop open`: open a workshop in your editor
|
|
48
48
|
- `epicshop update`: pull the latest workshop changes
|
package/dist/cli.js
CHANGED
|
@@ -158,7 +158,7 @@ const cli = yargs(args)
|
|
|
158
158
|
.command('add [repo-name] [destination]', 'Add a workshop by cloning from epicweb-dev GitHub org', (yargs) => {
|
|
159
159
|
return yargs
|
|
160
160
|
.positional('repo-name', {
|
|
161
|
-
describe: 'Repository name from epicweb-dev org (optional, shows list if omitted)',
|
|
161
|
+
describe: 'Repository name from epicweb-dev org (optional, shows list if omitted). Use <repo>#<tag|branch|commit> to pin a ref.',
|
|
162
162
|
type: 'string',
|
|
163
163
|
})
|
|
164
164
|
.positional('destination', {
|
|
@@ -179,7 +179,8 @@ const cli = yargs(args)
|
|
|
179
179
|
.example('$0 add', 'Show available workshops to add')
|
|
180
180
|
.example('$0 add full-stack-foundations', 'Clone and set up the full-stack-foundations workshop')
|
|
181
181
|
.example('$0 add web-forms --directory ~/my-workshops', 'Clone workshop to a custom directory')
|
|
182
|
-
.example('$0 add react-fundamentals ~/Desktop/react-fundamentals', 'Clone workshop to a specific destination directory')
|
|
182
|
+
.example('$0 add react-fundamentals ~/Desktop/react-fundamentals', 'Clone workshop to a specific destination directory')
|
|
183
|
+
.example('$0 add react-fundamentals#v1.2.0', 'Clone a workshop at a specific tag, branch, or commit');
|
|
183
184
|
}, async (argv) => {
|
|
184
185
|
const { add } = await import("./commands/workshops.js");
|
|
185
186
|
const result = await add({
|
|
@@ -99,6 +99,19 @@ function resolvePathWithTilde(inputPath) {
|
|
|
99
99
|
}
|
|
100
100
|
return trimmed;
|
|
101
101
|
}
|
|
102
|
+
function parseRepoSpecifier(value) {
|
|
103
|
+
const trimmed = value.trim();
|
|
104
|
+
const hashIndex = trimmed.indexOf('#');
|
|
105
|
+
if (hashIndex === -1) {
|
|
106
|
+
return { repoName: trimmed };
|
|
107
|
+
}
|
|
108
|
+
const repoName = trimmed.slice(0, hashIndex).trim();
|
|
109
|
+
const repoRef = trimmed.slice(hashIndex + 1).trim();
|
|
110
|
+
if (!repoName || !repoRef) {
|
|
111
|
+
throw new Error('Invalid repo specifier. Use the format: <repo-name>#<tag|branch|commit>.');
|
|
112
|
+
}
|
|
113
|
+
return { repoName, repoRef };
|
|
114
|
+
}
|
|
102
115
|
function getGitHubHeaders() {
|
|
103
116
|
const headers = {
|
|
104
117
|
Accept: 'application/vnd.github.v3+json',
|
|
@@ -258,7 +271,7 @@ async function checkWorkshopAccess(workshops) {
|
|
|
258
271
|
* This handles the actual cloning and setup logic
|
|
259
272
|
*/
|
|
260
273
|
async function addSingleWorkshop(repoName, options) {
|
|
261
|
-
const { silent = false } = options;
|
|
274
|
+
const { silent = false, repoRef } = options;
|
|
262
275
|
const hasExplicitCloneDestination = Boolean(options.destination?.trim() || options.directory?.trim());
|
|
263
276
|
// Ensure config is set up first (only when using the managed repos directory)
|
|
264
277
|
if (!hasExplicitCloneDestination) {
|
|
@@ -344,7 +357,8 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
344
357
|
}
|
|
345
358
|
const repoUrl = `https://github.com/${GITHUB_ORG}/${repoName}.git`;
|
|
346
359
|
if (!silent) {
|
|
347
|
-
|
|
360
|
+
const refHint = repoRef ? ` (${repoRef})` : '';
|
|
361
|
+
console.log(chalk.cyan(`๐ฆ Cloning ${repoUrl}${refHint}...`));
|
|
348
362
|
}
|
|
349
363
|
// Clone the repository
|
|
350
364
|
const cloneArgs = cloneIntoExistingDir
|
|
@@ -362,6 +376,31 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
362
376
|
error: cloneResult.error,
|
|
363
377
|
};
|
|
364
378
|
}
|
|
379
|
+
if (repoRef) {
|
|
380
|
+
if (!silent) {
|
|
381
|
+
console.log(chalk.cyan(`๐ Checking out ${repoRef}...`));
|
|
382
|
+
}
|
|
383
|
+
const checkoutResult = await runCommand('git', ['checkout', repoRef], {
|
|
384
|
+
cwd: workshopPath,
|
|
385
|
+
silent,
|
|
386
|
+
});
|
|
387
|
+
if (!checkoutResult.success) {
|
|
388
|
+
if (!silent) {
|
|
389
|
+
console.log(chalk.yellow(`๐งน Cleaning up cloned directory...`));
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
await fs.promises.rm(workshopPath, { recursive: true, force: true });
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Ignore cleanup errors
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
message: `Failed to check out ${repoRef}: ${checkoutResult.message}`,
|
|
400
|
+
error: checkoutResult.error,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
365
404
|
const setupResult = await setup({ cwd: workshopPath, silent });
|
|
366
405
|
if (!setupResult.success) {
|
|
367
406
|
// Clean up the cloned directory on setup failure
|
|
@@ -380,7 +419,8 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
380
419
|
error: setupResult.error,
|
|
381
420
|
};
|
|
382
421
|
}
|
|
383
|
-
const
|
|
422
|
+
const refSuffix = repoRef ? ` (${repoRef})` : '';
|
|
423
|
+
const message = `Workshop "${repoName}"${refSuffix} cloned successfully to ${workshopPath}`;
|
|
384
424
|
if (!silent) {
|
|
385
425
|
console.log(chalk.green(`โ
${message}`));
|
|
386
426
|
}
|
|
@@ -392,6 +432,25 @@ async function addSingleWorkshop(repoName, options) {
|
|
|
392
432
|
export async function add(options) {
|
|
393
433
|
const { silent = false } = options;
|
|
394
434
|
let { repoName } = options;
|
|
435
|
+
let repoRef = options.repoRef;
|
|
436
|
+
if (repoName) {
|
|
437
|
+
try {
|
|
438
|
+
const parsed = parseRepoSpecifier(repoName);
|
|
439
|
+
repoName = parsed.repoName;
|
|
440
|
+
repoRef = parsed.repoRef ?? repoRef;
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
444
|
+
if (!silent) {
|
|
445
|
+
console.error(chalk.red(`โ ${message}`));
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
success: false,
|
|
449
|
+
message,
|
|
450
|
+
error: error instanceof Error ? error : new Error(message),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
395
454
|
try {
|
|
396
455
|
// If no repo name provided, fetch available workshops and let user select
|
|
397
456
|
if (!repoName) {
|
|
@@ -406,6 +465,7 @@ export async function add(options) {
|
|
|
406
465
|
hints: [
|
|
407
466
|
'Provide the repo name: npx epicshop add <repo-name>',
|
|
408
467
|
'Example: npx epicshop add react-fundamentals',
|
|
468
|
+
'Pin to a tag/branch/commit: npx epicshop add react-fundamentals#v1.2.0',
|
|
409
469
|
],
|
|
410
470
|
});
|
|
411
471
|
const spinner = ora('Fetching available workshops...').start();
|
|
@@ -667,7 +727,10 @@ export async function add(options) {
|
|
|
667
727
|
for (const selectedRepo of selectedRepoNames) {
|
|
668
728
|
const displayName = getDisplayName(selectedRepo);
|
|
669
729
|
console.log(chalk.cyan(`๐๏ธ Setting up ${chalk.bold(displayName)}...\n`));
|
|
670
|
-
const result = await addSingleWorkshop(selectedRepo,
|
|
730
|
+
const result = await addSingleWorkshop(selectedRepo, {
|
|
731
|
+
...options,
|
|
732
|
+
repoRef,
|
|
733
|
+
});
|
|
671
734
|
if (result.success) {
|
|
672
735
|
successCount++;
|
|
673
736
|
console.log(chalk.green(`๐ Finished setting up ${chalk.bold(displayName)}\n`));
|
|
@@ -706,7 +769,7 @@ export async function add(options) {
|
|
|
706
769
|
}
|
|
707
770
|
const displayName = getDisplayName(repoName);
|
|
708
771
|
console.log(chalk.cyan(`๐๏ธ Setting up ${chalk.bold(displayName)}...\n`));
|
|
709
|
-
const result = await addSingleWorkshop(repoName, options);
|
|
772
|
+
const result = await addSingleWorkshop(repoName, { ...options, repoRef });
|
|
710
773
|
if (result.success) {
|
|
711
774
|
console.log(chalk.green(`๐ Finished setting up ${chalk.bold(displayName)}\n`));
|
|
712
775
|
console.log(chalk.white('Run:'));
|
|
@@ -724,7 +787,7 @@ export async function add(options) {
|
|
|
724
787
|
if (!silent) {
|
|
725
788
|
console.log(chalk.cyan(`๐๏ธ Setting up ${chalk.bold(repoName)}...\n`));
|
|
726
789
|
}
|
|
727
|
-
const result = await addSingleWorkshop(repoName, options);
|
|
790
|
+
const result = await addSingleWorkshop(repoName, { ...options, repoRef });
|
|
728
791
|
if (result.success && !silent) {
|
|
729
792
|
console.log(chalk.green(`๐ Finished setting up ${chalk.bold(repoName)}\n`));
|
|
730
793
|
console.log(chalk.white('Run:'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epicshop",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.75.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"build:watch": "nx watch --projects=epicshop -- nx run \\$NX_PROJECT_NAME:build"
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
|
-
"@epic-web/workshop-utils": "6.
|
|
102
|
+
"@epic-web/workshop-utils": "6.75.0",
|
|
103
103
|
"@inquirer/prompts": "^8.2.0",
|
|
104
104
|
"@sentry/node": "^10.36.0",
|
|
105
105
|
"chalk": "^5.6.2",
|