Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite publish logic #132

Merged
merged 17 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spec/publish-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ describe('apm publish', () => {
apm.run(['publish', 'patch'], callback);
waitsFor('waiting for publish to complete', 600000, () => callback.callCount === 1);
runs(() => {
expect(requests.length).toBe(2);
expect(requests.length).toBe(1);
expect(callback.mostRecentCall.args[0]).toBeUndefined();
});
});
Expand Down
96 changes: 62 additions & 34 deletions src/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ have published it.\
this.spawn(gitCommand, ['commit', '-m', message], (code, stderr, stdout) => {
stderr ??= '';
stdout ??= '';
if (code === 0) {
if (code !== 0) {
Copy link
Member

@DeeDeeG DeeDeeG May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just commentary, no action needed:

Kind of odd to see that this check was coded wrong from before this PR, I believe it was inadvertently changed as part of the utterly massive promisification PRs, from looking at the blame history.

This change seems legit. All other calls to logFailure() are when code !== 0, as in error code rather than clean exit code. So yeah, seems legit IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was surprised to see this one broken for so long tbh

this.logFailure();
const commitOutput = `${stdout}\n${stderr}`.trim();
reject(`Failed to commit package.json: ${commitOutput}`);
Expand Down Expand Up @@ -417,11 +417,24 @@ have published it.\

// Run the publish command with the given options
async run(options) {
let pack;
let pack, originalName;
options = this.parseOptions(options.commandArgs);
let {tag, rename} = options.argv;
let [version] = options.argv._;

// Normalize variables to ensure they are strings with zero length
// rather than their default `undefined`
// This ensures later length checks always behave as expected
if (typeof tag !== "string") {
tag = "";
}
if (typeof version !== "string") {
version = "";
}
if (typeof rename !== "string") {
rename = "";
}

try {
pack = this.loadMetadata();
} catch (error) {
Expand All @@ -440,56 +453,71 @@ have published it.\
return error;
}

if ((version?.length > 0) || (rename?.length > 0)) {
let originalName;
if (version?.length <= 0) { version = 'patch'; }
if (rename?.length > 0) { originalName = pack.name; }
if (version?.length === 0 && tag?.length === 0 && rename?.length === 0) {
Copy link
Member

@DeeDeeG DeeDeeG May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a benefit to know this is somehow still ending up undefined (despite the normalization we're doing) sooner, due to undefined.length giving an error Uncaught TypeError: Cannot read properties of undefined (reading 'length').

Rather than end up silently doing some tricky comparisons of undefined vs 0 (undefined === 0 specifically, which would be false, seeming to imply that the length is greater than 1 as it's "not 0", which I think puts us on the wrong code flow path in the if, right?)

Suggested change
if (version?.length === 0 && tag?.length === 0 && rename?.length === 0) {
if (version.length === 0 && tag.length === 0 && rename.length === 0) {

Sort of theoretical, since it shouldn't be possible.

But also, I like to avoid more complicated syntax if it's not needed, for ease of reading.

Also: Given the head-scratcher this issue was to solve, correctness and surfacing error messages in as straight-forward a way as possible is kind of my top priority, rather than avoiding errors in this case. Unless we want to try/catch and print the error and exit gracefully, but... I don't think that helps anything anyway in this case.

tl;dr: Hitting a simple error message would be better than not hitting it, in this case, I think? So, optional chaining not a benefit here?

Not a blocker, since it should all be hypothetical with all the normalizing done in this PR. But the goose-chase tracking down strange behavior makes me a tad paranoid, not gonna lie.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for all the nitpicking, just inclined to make this code very tidy if possible after what had to be thought through to get here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: If moving away from optional chaining for the varName.length checks, there are three more in the diff for this PR, I just didn't individually comment them all.

Copy link
Member

@DeeDeeG DeeDeeG May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. I suppose these instances of optional chaining mirror what was there before this PR, but while we're addressing tiny subtleties of behavior that were unintuitive, I think it's worth deciding whether to move away from the optional chaining or not.

return "A version, tag, or new package name is required";
}

if (rename?.length > 0) {
// A version isn't required when renaming a package (apparently)
if (version?.length === 0) { version = "patch"; }
originalName = pack.name;

let firstTimePublishing;
let tag;
try {
firstTimePublishing = await this.registerPackage(pack);
await this.renamePackage(pack, rename);
} catch(error) {
return error;
}
}

// Now we know a version has been specified, and that we have settled any
// rename concerns. Lets get to publication

if (tag?.length === 0) {
// only create and assign a tag if the user didn't specify one.
// if they did we assume that tag already exists and only work to publish
try {
tag = await this.versionPackage(version);
await this.pushVersion(tag, pack);
} catch (error) {
} catch(error) {
return error;
}

await this.waitForTagToBeAvailable(pack, tag);
if (originalName != null) {
// If we're renaming a package, we have to hit the API with the
// current name, not the new one, or it will 404.
rename = pack.name;
pack.name = originalName;
}
}

let doesPackageExist;
try {
doesPackageExist = await this.packageExists(pack.name);
} catch(error) {
return error;
}

if (doesPackageExist) {
// This is an existing package we just want to publish a new version of
try {
await this.publishPackage(pack, tag, {rename});
} catch (error) {
if (firstTimePublishing) {
this.logFirstTimePublishMessage(pack);
if (originalName != null) {
// If we're renaming a package, we have to hit the API with the
// current name, not the new one, or it will 404.
rename = pack.name;
pack.name = originalName;
await this.publishPackage(pack, tag, {rename});
} else {
await this.publishPackage(pack, tag);
}
return error;
}
} else if (tag?.length > 0) {
let firstTimePublishing;
try {
firstTimePublishing = await this.registerPackage(pack);
} catch (error) {
} catch(error) {
return error;
}

} else {
// This is a brand new package we want to publish for the first time
try {
await this.publishPackage(pack, tag);
} catch (error) {
if (firstTimePublishing) {
this.logFirstTimePublishMessage(pack);
}
await this.registerPackage(pack);
} catch(error) {
return error;
}
} else {
return 'A version, tag, or new package name is required';

this.logFirstTimePublishMessage(pack);
}

}
}
Loading