mopub 0.0.3 → 0.1.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/publish.js +156 -22
- package/package.json +6 -3
package/dist/publish.js
CHANGED
|
@@ -19,7 +19,7 @@ export const PublishInput = z
|
|
|
19
19
|
.optional(),
|
|
20
20
|
otp: z
|
|
21
21
|
.string()
|
|
22
|
-
.describe('npm OTP to pass through to `npm publish --otp`.
|
|
22
|
+
.describe('npm OTP to pass through to `npm publish --otp`. If not provided you will be prompted for it (press enter at the prompt to try publishing without MFA, e.g. for registries that use browser-based auth).')
|
|
23
23
|
.optional(),
|
|
24
24
|
tag: z
|
|
25
25
|
.string()
|
|
@@ -122,6 +122,21 @@ async function getRegistryVersions(pkg) {
|
|
|
122
122
|
const registryVersionsStdout = StdoutShape.parse(JSON.parse(registryVersionsResult.stdout));
|
|
123
123
|
return Array.isArray(registryVersionsStdout) ? registryVersionsStdout : [];
|
|
124
124
|
}
|
|
125
|
+
async function getRegistryDistTags(pkgName) {
|
|
126
|
+
const result = await execa('npm', ['view', pkgName, 'dist-tags', '--json'], { reject: false });
|
|
127
|
+
if (!result.stdout)
|
|
128
|
+
return {};
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(result.stdout);
|
|
131
|
+
if (parsed && typeof parsed === 'object' && !('error' in parsed)) {
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// ignore - fall through to empty
|
|
137
|
+
}
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
125
140
|
const _breakpoint = (why = 'hmm') => ({
|
|
126
141
|
title: `wait a bit because ${why}`,
|
|
127
142
|
task: async (ctx, task) => {
|
|
@@ -444,7 +459,18 @@ export const publish = async (input) => {
|
|
|
444
459
|
rendererOptions: { persistentOutput: true },
|
|
445
460
|
task: async (ctx, task) => {
|
|
446
461
|
const shouldActuallyPublish = input.publish;
|
|
447
|
-
|
|
462
|
+
let otp = input.otp;
|
|
463
|
+
if (shouldActuallyPublish) {
|
|
464
|
+
otp ||= await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
465
|
+
message: 'Enter npm OTP (press enter to try publishing without MFA)',
|
|
466
|
+
type: 'Input',
|
|
467
|
+
validate: v => v === '' || (typeof v === 'string' && /^\d{6}$/.test(v)),
|
|
468
|
+
});
|
|
469
|
+
if (otp.length === 0) {
|
|
470
|
+
task.output = 'No OTP provided - publish will likely error unless you have disabled MFA.';
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const publishTasks = createPublishTasks(ctx, { otp, tag: input.tag });
|
|
448
474
|
if (!shouldActuallyPublish)
|
|
449
475
|
publishTasks.forEach(t => (t.skip = true));
|
|
450
476
|
return task.newListr(publishTasks, { rendererOptions: { collapseSubtasks: false } });
|
|
@@ -481,7 +507,7 @@ export const PrebuiltInput = z.tuple([
|
|
|
481
507
|
z.object({
|
|
482
508
|
otp: z
|
|
483
509
|
.string()
|
|
484
|
-
.describe('npm OTP to pass through to `npm publish --otp`.
|
|
510
|
+
.describe('npm OTP to pass through to `npm publish --otp`. If not provided you will be prompted for it (press enter at the prompt to try publishing without MFA, e.g. for registries that use browser-based auth).')
|
|
485
511
|
.optional(),
|
|
486
512
|
tag: z
|
|
487
513
|
.string()
|
|
@@ -495,7 +521,29 @@ export async function publishPrebuilt([folder, options]) {
|
|
|
495
521
|
throw new Error(`Failed to get npm username: ${me.stderr}`);
|
|
496
522
|
console.log(me.stdout, '<<<<<<< npm whoami');
|
|
497
523
|
const ctx = loadContext(folder);
|
|
498
|
-
const tasks = new Listr(
|
|
524
|
+
const tasks = new Listr([
|
|
525
|
+
{
|
|
526
|
+
title: 'Get OTP',
|
|
527
|
+
enabled: () => !options.otp,
|
|
528
|
+
task: async (_ctx, task) => {
|
|
529
|
+
options.otp = await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
530
|
+
message: 'Enter npm OTP (press enter to try publishing without MFA)',
|
|
531
|
+
type: 'Input',
|
|
532
|
+
validate: v => v === '' || (typeof v === 'string' && /^\d{6}$/.test(v)),
|
|
533
|
+
});
|
|
534
|
+
if (options.otp === '') {
|
|
535
|
+
const confirmed = await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
536
|
+
message: 'This will fail unless you have disabled MFA, which is not recommended.',
|
|
537
|
+
type: 'confirm',
|
|
538
|
+
});
|
|
539
|
+
if (!confirmed) {
|
|
540
|
+
throw new Error('OTP not provided');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
...createPublishTasks(ctx, options),
|
|
546
|
+
], { ctx });
|
|
499
547
|
await tasks.run();
|
|
500
548
|
}
|
|
501
549
|
function createPublishTasks(ctx, options) {
|
|
@@ -510,11 +558,30 @@ function createPublishTasks(ctx, options) {
|
|
|
510
558
|
const publishArgs = ['--access', 'public'];
|
|
511
559
|
if (options.otp)
|
|
512
560
|
publishArgs.push('--otp', options.otp);
|
|
513
|
-
|
|
561
|
+
let resolvedTag = options.tag;
|
|
562
|
+
// default to whichever dist-tag the previous comparable release was published under,
|
|
563
|
+
// when both versions are the same prerelease/non-prerelease type. e.g. if 0.0.1-1 was
|
|
564
|
+
// published as "latest", a follow-up 0.0.1-2 should also go to "latest" (not "next");
|
|
565
|
+
// if 4.0.0 was published as "next", 4.0.1 should also be "next" (not "latest").
|
|
566
|
+
if (!resolvedTag && lhsPackageJson?.version && rhsPackageJson?.version) {
|
|
567
|
+
const sameType = Boolean(semver.prerelease(lhsPackageJson.version)) ===
|
|
568
|
+
Boolean(semver.prerelease(rhsPackageJson.version));
|
|
569
|
+
if (sameType) {
|
|
570
|
+
const distTags = await getRegistryDistTags(pkg.name);
|
|
571
|
+
const matchingTags = Object.entries(distTags)
|
|
572
|
+
.filter(([, v]) => v === lhsPackageJson.version)
|
|
573
|
+
.map(([t]) => t);
|
|
574
|
+
if (matchingTags.length > 0) {
|
|
575
|
+
resolvedTag = matchingTags.find(t => t === 'latest') || matchingTags[0];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (resolvedTag) {
|
|
580
|
+
publishArgs.push('--tag', resolvedTag);
|
|
581
|
+
}
|
|
582
|
+
else if (rhsPackageJson && semver.prerelease(rhsPackageJson.version)) {
|
|
514
583
|
const first = semver.prerelease(rhsPackageJson.version)?.[0];
|
|
515
|
-
|
|
516
|
-
tag ||= typeof first === 'string' ? first : 'next';
|
|
517
|
-
publishArgs.push('--tag', tag);
|
|
584
|
+
publishArgs.push('--tag', typeof first === 'string' ? first : 'next');
|
|
518
585
|
}
|
|
519
586
|
await pipeExeca(subtask, 'pnpm', ['publish', ...publishArgs], {
|
|
520
587
|
cwd: path.dirname(packageJsonFilepath(pkg, RHS_FOLDER)),
|
|
@@ -567,10 +634,18 @@ export const ReleaseNotesInput = z.object({
|
|
|
567
634
|
});
|
|
568
635
|
async function pullRegistryPackage(subtask, pkg, { version, folder }) {
|
|
569
636
|
// note: `npm pack foobar` will actually pull foobar.1-2-3.tgz from the registry. It's not actually doing a "pack" at all. `pnpm pack` does not do the same thing - it packs the local directory
|
|
570
|
-
await pipeExeca(subtask, 'npm', ['pack', `${pkg.name}@${version}`], { cwd: folder });
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
637
|
+
const packResult = await pipeExeca(subtask, 'npm', ['pack', `${pkg.name}@${version}`], { cwd: folder });
|
|
638
|
+
const packStdout = typeof packResult.stdout === 'string' ? packResult.stdout : '';
|
|
639
|
+
const tgzFileName = packStdout
|
|
640
|
+
.split('\n')
|
|
641
|
+
.map(line => line.trim())
|
|
642
|
+
.find(line => line.endsWith('.tgz'));
|
|
643
|
+
const folderEntries = fs.readdirSync(folder);
|
|
644
|
+
if (!tgzFileName || !folderEntries.includes(tgzFileName)) {
|
|
645
|
+
throw new Error([
|
|
646
|
+
`Expected npm pack ${pkg.name}@${version} to write ${tgzFileName || 'a .tgz file'} in ${folder}.`,
|
|
647
|
+
`Found: ${folderEntries.length ? folderEntries.join(', ') : '(nothing)'}`,
|
|
648
|
+
].join('\n'));
|
|
574
649
|
}
|
|
575
650
|
await pipeExeca(subtask, 'tar', ['-xvzf', tgzFileName], { cwd: folder });
|
|
576
651
|
const filepath = path.join(folder, 'package', 'package.json');
|
|
@@ -643,7 +718,7 @@ function getBumpedVersionValidation(lowerBoundVersion, v) {
|
|
|
643
718
|
function getWorkspaceRoot() {
|
|
644
719
|
return path.dirname(findUpSync('pnpm-workspace.yaml') || findUpSync('pnpm-lock.yaml') || findUpOrThrow('.git', { type: 'directory' }));
|
|
645
720
|
}
|
|
646
|
-
/** "Pessimistic" comparison ref. Tries to use the registry package.json's `git.sha` property, falls back to a matching version tag, and if neither exists prompts the user
|
|
721
|
+
/** "Pessimistic" comparison ref. Tries to use the registry package.json's `git.sha` property, falls back to a matching version tag, and if neither exists prompts the user to choose between the last publish with a findable sha, the last commit before the previous publish, the first commit to the package folder, or a manually-entered sha. */
|
|
647
722
|
async function getPackageLastPublishRef(pkg, task) {
|
|
648
723
|
const packageJson = loadLHSPackageJson(pkg);
|
|
649
724
|
return first7(await getPackageJsonGitSha(pkg, packageJson, task));
|
|
@@ -669,18 +744,74 @@ async function getPackageJsonGitSha(pkg, packageJson, task) {
|
|
|
669
744
|
const repoUrl = getPackageJsonRepository(loadRHSPackageJson(pkg) || loadLHSPackageJson(pkg) || loadPackageJson(path.join(getWorkspaceRoot(), 'package.json')));
|
|
670
745
|
const olderUrl = repoUrl && oldestShownSha ? `\nOlder commits: ${repoUrl}/commits/${oldestShownSha}` : '';
|
|
671
746
|
const recentList = commitLines.length ? `\nRecent commits in ${pkg.path}:\n${commitLines.join('\n')}${olderUrl}\n` : '';
|
|
747
|
+
const choices = [];
|
|
748
|
+
const lastPublishWithSha = await findLastPublishWithFindableSha(pkg, commitLines);
|
|
749
|
+
if (lastPublishWithSha) {
|
|
750
|
+
choices.push({
|
|
751
|
+
message: `${first7(lastPublishWithSha.sha)} - last publish with a findable sha (${lastPublishWithSha.version})`,
|
|
752
|
+
value: lastPublishWithSha.sha,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
const commitBeforePreviousPublish = await getLastCommitBeforePreviousPublish(pkg, packageJson);
|
|
756
|
+
if (commitBeforePreviousPublish) {
|
|
757
|
+
choices.push({
|
|
758
|
+
message: `${first7(commitBeforePreviousPublish.sha)} - last commit before previous publish (${commitBeforePreviousPublish.version} @ ${commitBeforePreviousPublish.time})`,
|
|
759
|
+
value: commitBeforePreviousPublish.sha,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
choices.push({ message: `${first7(firstRef)} - first commit in ${pkg.path}`, value: firstRef }, { message: 'Other (enter a sha manually)', value: 'other' });
|
|
763
|
+
const selected = await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
764
|
+
type: 'Select',
|
|
765
|
+
message: `Couldn't find a git sha for the last published version of ${pkg.name}.${recentList}\nSelect the sha to diff from:`,
|
|
766
|
+
choices,
|
|
767
|
+
});
|
|
768
|
+
if (selected !== 'other')
|
|
769
|
+
return selected;
|
|
672
770
|
const promptAnswer = await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
673
771
|
type: 'Input',
|
|
674
|
-
message: `
|
|
772
|
+
message: `Enter the sha to diff from:`,
|
|
675
773
|
validate: (v) => {
|
|
676
|
-
if (typeof v !== 'string')
|
|
677
|
-
return 'Enter a
|
|
678
|
-
|
|
679
|
-
return true;
|
|
680
|
-
return /^[0-9a-f]{4,40}$/i.test(v.trim()) ? true : 'Enter a valid git sha (4-40 hex chars) or leave empty.';
|
|
774
|
+
if (typeof v !== 'string' || !/^[0-9a-f]{4,40}$/i.test(v.trim()))
|
|
775
|
+
return 'Enter a valid git sha (4-40 hex chars).';
|
|
776
|
+
return true;
|
|
681
777
|
},
|
|
682
778
|
});
|
|
683
|
-
return promptAnswer.trim()
|
|
779
|
+
return promptAnswer.trim();
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Walks the registry's published versions newest-first looking for one whose git sha can be determined - via the
|
|
783
|
+
* registry package.json's `git.sha`, a version tag, or a `version x.y.z` commit message - even if its
|
|
784
|
+
* prerelease-ness doesn't match the version being compared against. Capped at the 20 newest versions to bound
|
|
785
|
+
* `npm view` calls.
|
|
786
|
+
*/
|
|
787
|
+
async function findLastPublishWithFindableSha(pkg, recentCommitLines) {
|
|
788
|
+
const newestFirst = pkg.publishedVersions.slice().reverse().slice(0, 20);
|
|
789
|
+
for (const version of newestFirst) {
|
|
790
|
+
const { stdout: registrySha } = await execa('npm', ['view', `${pkg.name}@${version}`, 'git.sha'], { reject: false });
|
|
791
|
+
if (registrySha.trim())
|
|
792
|
+
return { version, sha: registrySha.trim() };
|
|
793
|
+
const tagSha = await getShaFromVersionTag(pkg, version);
|
|
794
|
+
if (tagSha)
|
|
795
|
+
return { version, sha: tagSha };
|
|
796
|
+
const versionCommitMessage = `version ${version}`;
|
|
797
|
+
const match = recentCommitLines.find(line => line.slice(line.indexOf(' ') + 1) === versionCommitMessage);
|
|
798
|
+
if (match)
|
|
799
|
+
return { version, sha: match.split(' ', 1)[0] };
|
|
800
|
+
}
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
/** The last commit in the package folder made before the previous version was published, based on the registry's publish timestamp. */
|
|
804
|
+
async function getLastCommitBeforePreviousPublish(pkg, packageJson) {
|
|
805
|
+
const previousVersion = packageJson?.version || pkg.publishedVersions.at(-1);
|
|
806
|
+
if (!previousVersion)
|
|
807
|
+
return null;
|
|
808
|
+
const timesResult = await execa('npm', ['view', pkg.name, 'time', '--json'], { reject: false });
|
|
809
|
+
const times = z.record(z.string(), z.string()).safeParse(JSON.parse(timesResult.stdout || '{}'));
|
|
810
|
+
const publishTime = times.success ? times.data[previousVersion] : undefined;
|
|
811
|
+
if (!publishTime)
|
|
812
|
+
return null;
|
|
813
|
+
const { stdout: sha } = await execa('git', ['log', '-n', '1', `--before=${publishTime}`, '--pretty=format:%h', '--', '.'], { cwd: pkg.path, reject: false });
|
|
814
|
+
return sha ? { version: previousVersion, time: publishTime, sha } : null;
|
|
684
815
|
}
|
|
685
816
|
async function getPackageJsonShaFromVersionTag(pkg, packageJson) {
|
|
686
817
|
// throw new Error(
|
|
@@ -688,13 +819,16 @@ async function getPackageJsonShaFromVersionTag(pkg, packageJson) {
|
|
|
688
819
|
// )
|
|
689
820
|
if (!packageJson?.version)
|
|
690
821
|
return null;
|
|
691
|
-
|
|
822
|
+
return getShaFromVersionTag(pkg, packageJson.version);
|
|
823
|
+
}
|
|
824
|
+
async function getShaFromVersionTag(pkg, version) {
|
|
825
|
+
const { stdout: vTagSha } = await execa('git', ['rev-list', '-n', '1', `v${version}`], {
|
|
692
826
|
cwd: pkg.path,
|
|
693
827
|
reject: false,
|
|
694
828
|
});
|
|
695
829
|
if (vTagSha)
|
|
696
830
|
return vTagSha;
|
|
697
|
-
const { stdout: tagSha } = await execa('git', ['rev-list', '-n', '1',
|
|
831
|
+
const { stdout: tagSha } = await execa('git', ['rev-list', '-n', '1', version], {
|
|
698
832
|
cwd: pkg.path,
|
|
699
833
|
reject: false,
|
|
700
834
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mopub",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Publish packages in a pnpm monorepo",
|
|
5
|
+
"license": "ISC",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"mopub": "dist/cli.js"
|
|
@@ -9,7 +10,6 @@
|
|
|
9
10
|
"files": [
|
|
10
11
|
"dist"
|
|
11
12
|
],
|
|
12
|
-
"license": "ISC",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@listr2/prompt-adapter-enquirer": "^4.2.1",
|
|
15
15
|
"@trpc/server": "^11.16.0",
|
|
@@ -31,9 +31,12 @@
|
|
|
31
31
|
"tsx": "^4.21.0",
|
|
32
32
|
"typescript": "^6.0.3"
|
|
33
33
|
},
|
|
34
|
+
"git": {
|
|
35
|
+
"sha": "43d2162"
|
|
36
|
+
},
|
|
34
37
|
"scripts": {
|
|
35
|
-
"clean": "rm -rf dist",
|
|
36
38
|
"build": "tsc --noEmit false --outDir dist",
|
|
39
|
+
"clean": "rm -rf dist",
|
|
37
40
|
"lint": "eslint .",
|
|
38
41
|
"test": "echo maybe later"
|
|
39
42
|
}
|