Skip to content

feat: Introduce releaseFull command for automated releases #13

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
38 changes: 38 additions & 0 deletions cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,50 @@
"metaInfo": {
"hasValue": true,
"description": "Meta information for publishing"
},
"packageVersion": {
"hasValue": true
}
}
},
"release": {
"description": "Push builded file to server."
},
"releaseFull": {
"options": {
"origin": {
"hasValue": true
},
"next": {
"hasValue": true
},
"output": {
"hasValue": true,
"default": "${tempDir}/output/diff-${time}.ppk-patch"
},
"platform": {
"hasValue": true
},
"name": {
"hasValue": true
},
"description": {
"hasValue": true
},
"packageVersion": {
"hasValue": true
},
"metaInfo": {
"hasValue": true
},
"rollout": {
"hasValue": true
},
"dryRun": {
"default": false
}
}
},
"diff": {
"description": "Create diff patch",
"options": {
Expand Down
91 changes: 65 additions & 26 deletions src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,19 @@ function basename(fn: string) {
return m?.[1];
}

async function diffFromPPK(origin: string, next: string, output: string) {
async function diffFromPPKInternal(origin: string, next: string, output: string, diffAlgorithm?: Diff) {
fs.ensureDirSync(path.dirname(output));

const selectedDiff = diffAlgorithm || diff; // Use provided algorithm or global 'diff'

if (!selectedDiff && !bsdiff) {
throw new Error(
`Diff algorithm not specified and bsdiff is not available. Please install "node-bsdiff".`,
);
}
const currentDiffTool = selectedDiff || bsdiff; // Default to bsdiff if global 'diff' is not set


const originEntries = {};
const originMap = {};

Expand Down Expand Up @@ -620,7 +630,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff');
zipfile.addBuffer(
diff(originSource, newSource),
currentDiffTool(originSource, newSource),
'index.bundlejs.patch',
);
//console.log('End diff');
Expand All @@ -630,7 +640,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff');
zipfile.addBuffer(
diff(originSource, newSource),
currentDiffTool(originSource, newSource),
'bundle.harmony.js.patch',
);
//console.log('End diff');
Expand Down Expand Up @@ -900,10 +910,30 @@ function diffArgsCheck(args: string[], options: any, diffFn: string) {
};
}

export async function diffFromPPK(origin: string, next: string, output: string, diffAlgorithm?: 'bsdiff' | 'hdiff') {
let selectedDiffTool: Diff;
if (diffAlgorithm === 'hdiff') {
if (!hdiff) throw new Error('hdiff is not available. Please install node-hdiffpatch.');
selectedDiffTool = hdiff;
} else { // Default to bsdiff
if (!bsdiff) throw new Error('bsdiff is not available. Please install node-bsdiff.');
selectedDiffTool = bsdiff;
}
// The global 'diff' variable is not used here to avoid side effects from other commands.
return diffFromPPKInternal(origin, next, output, selectedDiffTool);
}

export const commands = {
bundle: async ({ options }) => {
const platform = await getPlatform(options.platform);

// Ensure packageVersion is also translated or retrieved if available in options
const translatedOpts = translateOptions({
...options, // Original options which might include packageVersion
tempDir,
platform,
});

const {
bundleName,
entryFile,
Expand All @@ -915,14 +945,14 @@ export const commands = {
expo,
rncli,
disableHermes,
name,
description,
metaInfo,
} = translateOptions({
...options,
tempDir,
platform,
});
name, // For bundle naming and publish
description, // For publish
metaInfo, // For publish
} = translatedOpts;

// packageVersion for publish should be taken from original options if translateOptions doesn't handle it for bundle
const packageVersion = options.packageVersion;


checkLockFiles();
addGitIgnore();
Expand Down Expand Up @@ -958,15 +988,20 @@ export const commands = {

await pack(path.resolve(intermediaDir), realOutput);

if (name) {
if (name) { // If name is provided, publish automatically
const publishOptions: any = { // Type according to what versions.publish expects
platform,
name,
description,
metaInfo,
};
if (packageVersion) {
publishOptions.packageVersion = packageVersion;
}

const versionName = await versionCommands.publish({
args: [realOutput],
options: {
platform,
name,
description,
metaInfo,
},
options: publishOptions,
});

if (isSentry) {
Expand All @@ -978,14 +1013,17 @@ export const commands = {
versionName,
);
}
} else if (!options['no-interactive']) {
} else if (!options['no-interactive']) { // If name is not provided, retain old prompt behavior
const v = await question(t('uploadBundlePrompt'));
if (v.toLowerCase() === 'y') {
const publishOptions: any = { platform };
if (packageVersion) { // Also consider packageVersion if prompting
publishOptions.packageVersion = packageVersion;
}
// name, description, metaInfo will be prompted by versionCommands.publish if not provided
const versionName = await versionCommands.publish({
args: [realOutput],
options: {
platform,
},
options: publishOptions,
});
if (isSentry) {
await copyDebugidForSentry(
Expand All @@ -1006,15 +1044,16 @@ export const commands = {

async diff({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(args, options, 'diff');

await diffFromPPK(origin, next, realOutput);
// diffArgsCheck sets the global 'diff' variable to bsdiff
// diffFromPPKInternal will use the global 'diff' if no algorithm is passed.
await diffFromPPKInternal(origin, next, realOutput);
console.log(`${realOutput} generated.`);
},

async hdiff({ args, options }) {
const { origin, next, realOutput } = diffArgsCheck(args, options, 'hdiff');

await diffFromPPK(origin, next, realOutput);
// diffArgsCheck sets the global 'diff' variable to hdiff
await diffFromPPKInternal(origin, next, realOutput);
console.log(`${realOutput} generated.`);
},

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const commands = {
...require('./app').commands,
...require('./package').commands,
...require('./versions').commands,
...require('./release').commands,
help: printUsage,
};

Expand Down
138 changes: 138 additions & 0 deletions src/release.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// src/release.ts
import { checkPlatform, translateOptions, question } from './utils';
import { diffFromPPK } from './bundle'; // Import the actual diffFromPPK
import {
executePublish,
getPackagesForUpdate,
bindVersionToPackages
} from './versions'; // Import actual functions
import { tempDir, time } from './utils/constants'; // Adjust path if necessary
import { t } from './utils/i18n';
import { getSelectedApp } from './app'; // Added for appId

// _internal export can be removed or kept empty if tests are updated
// to mock imported functions directly (e.g. jest.mock('./versions')).
// For now, keeping it but its contents are no longer used by releaseFull itself.
const mockPublishForTest = async () => ({ id: 'test-id', versionName: 'test-name', hash: 'test-hash' });
const mockUpdateForTest = async () => {};
export const _internal = {
performPublish: mockPublishForTest, // Placeholder for tests if they spy on _internal.performPublish
performUpdate: mockUpdateForTest, // Placeholder for tests if they spy on _internal.performUpdate
};

export const commands = {
releaseFull: async function({ args, options }) {
console.log(t('RELEASE_FULL_START')); // Assumes i18n key exists

try {
// Step 0: Option Processing & App ID
const platform = await checkPlatform(options.platform);
const { appId } = await getSelectedApp(platform); // Get appId

const translatedOpts = await translateOptions(options, 'releaseFull');

const {
origin, // Path to original ppk/zip
next, // Path to next ppk/zip
output, // User-specified output path for the diff package (optional)
name, // Name for the published bundle (bundle's version name)
description,
packageVersion, // Target NATIVE version for the update/binding
metaInfo,
rollout,
dryRun
} = translatedOpts;

if (!origin || !next) {
const errorMsg = t('RELEASE_FULL_ERROR_ORIGIN_NEXT_REQUIRED'); // Assumes i18n key
console.error(errorMsg);
// In a real CLI, you might throw new Error(errorMsg) or process.exit(1)
return;
}
if (!packageVersion) {
const errorMsg = t('RELEASE_FULL_ERROR_PACKAGE_VERSION_REQUIRED'); // Assumes i18n key for native package version
console.error(errorMsg);
return;
}
if (!name) {
const errorMsg = t('RELEASE_FULL_ERROR_NAME_REQUIRED'); // Assumes i18n key for bundle name
console.error(errorMsg);
return;
}


// Step 1: Perform Diff
console.log(t('RELEASE_FULL_DIFF_GENERATING')); // Assumes i18n key
let diffPath;
try {
// Default output path for diff if not provided by user
// The 'output' from cli.json for releaseFull has a default: "${tempDir}/output/diff-${time}.ppk-patch"
// translateOptions should have resolved this.
const diffOutputPath = output;
// Call the actual diffFromPPK, defaulting to 'bsdiff'.
// 'bsdiff' is chosen as a default because releaseFull doesn't have a diff type option.
await diffFromPPK(origin, next, diffOutputPath, 'bsdiff');
diffPath = diffOutputPath; // The file is created at diffOutputPath
console.log(t('RELEASE_FULL_DIFF_SUCCESS', { path: diffPath }));
} catch (error) {
console.error(t('RELEASE_FULL_ERROR_DIFF'), error);
return;
}

// Step 2: Publish Diff Bundle
console.log(t('RELEASE_FULL_PUBLISH_START'));
let versionId;
let publishedVersionName;
try {
// Call actual executePublish
const publishResult = await executePublish({
filePath: diffPath,
platform,
appId,
name, // Name for the bundle version
description,
metaInfo,
// packageVersion from releaseFull options is for native targeting, not bundle's own version here.
// deps and commit are handled by executePublish if not provided.
});
versionId = publishResult.id;
publishedVersionName = publishResult.versionName;
console.log(t('RELEASE_FULL_PUBLISH_SUCCESS', { id: versionId, name: publishedVersionName }));
} catch (error) {
console.error(t('RELEASE_FULL_ERROR_PUBLISH'), error);
return;
}

// Step 3: Update/Bind Version
console.log(t('RELEASE_FULL_UPDATE_START'));
try {
const pkgsToBind = await getPackagesForUpdate(appId, {
packageVersion: packageVersion // Target native package version
});

if (!pkgsToBind || pkgsToBind.length === 0) {
console.error(t('RELEASE_FULL_ERROR_NO_PACKAGES_FOUND', { packageVersion: packageVersion }));
return;
}

await bindVersionToPackages({
appId,
versionId, // ID from the publish step
pkgs: pkgsToBind, // Packages obtained from getPackagesForUpdate
rollout: rollout ? Number(rollout) : undefined, // Ensure rollout is a number
dryRun,
});
console.log(t('RELEASE_FULL_UPDATE_SUCCESS'));
} catch (error) {
console.error(t('RELEASE_FULL_ERROR_UPDATE'), error);
return;
}

console.log(t('RELEASE_FULL_SUCCESS')); // Assumes i18n key

} catch (error) {
// Catch errors from checkPlatform, getSelectedApp, or translateOptions
console.error(t('RELEASE_FULL_ERROR_UNEXPECTED'), error); // Assumes i18n key
}
}
};
Loading