Compare commits

...
Author SHA1 Message Date
Laurent Cozic cca6d46ad3 Doc: Remove obsolete CLI dependency version step from deploy doc
setupNewRelease already sets the @joplin dependency ranges of the CLI app.
2026-11-12 09:11:15 +00:00
Laurent Cozic db12dd7f2b Tools: Publish the CLI app from CI using npm trusted publishing
`yarn releaseCli` now bumps the app and libraries and generates the changelog. Pushing the cli-v* tag triggers the publish workflow, which publishes the libraries, then the app, so they no longer need to be released separately.
2026-11-12 08:48:52 +00:00
5 changed files with 52 additions and 46 deletions

No files matched your search

+5 -3
View File
@@ -1,12 +1,14 @@
name: Publish sub-packages
name: Publish npm packages
# The tag is created and pushed by `yarn publishAll`. Publishing uses npm trusted publishing, so no
# token is needed, but each package must have this workflow registered as a trusted publisher on
# The sub-packages tag is pushed by `yarn publishAll`, and the cli tag by `yarn releaseCli`. Either way,
# any library or CLI app version not yet on npm is published. Publishing uses npm trusted publishing, so
# no token is needed, but each package must have this workflow registered as a trusted publisher on
# npmjs.com.
on:
push:
tags:
- 'sub-packages-v*'
- 'cli-v*'
permissions:
contents: read
+2 -1
View File
@@ -19,7 +19,8 @@
},
"repository": {
"type": "git",
"url": "https://github.com/laurent22/joplin"
"url": "git+https://github.com/laurent22/joplin.git",
"directory": "packages/app-cli"
},
"copyright": {
"title": "Joplin CLI",
+10 -9
View File
@@ -1,28 +1,29 @@
import { execCommand } from '@joplin/utils';
import { rootDir, completeReleaseWithChangelog } from './tool-utils';
import { versionPatch } from '@joplin/utils/version';
import { versionAll } from './releaseSubPackages';
const appDir = `${rootDir}/packages/app-cli`;
const changelogPath = `${rootDir}/readme/about/changelog/cli.md`;
// Start with node Tools/release-cli.js --changelog-from cli-v1.0.126
// to specify from where the changelog should be created
//
// Pushing the tag triggers the publish-sub-packages workflow, which publishes the libraries, then the app.
async function main() {
process.chdir(appDir);
process.chdir(rootDir);
await execCommand('git pull');
await versionAll();
process.chdir(appDir);
const newVersion = await versionPatch();
console.info(`Building ${newVersion}...`);
console.info(`New version: ${newVersion}`);
const newTag = `cli-${newVersion}`;
await execCommand('touch app/main.js');
await execCommand('yarn build');
await execCommand('cp ../../README.md build/');
process.chdir(`${appDir}/build`);
await execCommand('npm publish');
process.chdir(rootDir);
await execCommand('yarn install --mode=update-lockfile');
await completeReleaseWithChangelog(changelogPath, newVersion, newTag, 'CLI', false);
}
+27 -11
View File
@@ -118,7 +118,25 @@ const publishPackage = async (dir: string, rootLicensePath: string, dryRun: bool
}
};
const versionAll = async () => {
// The CLI app is a private package because it is published from its build directory
const publishCliApp = async (rootDir: string, dryRun: boolean) => {
const buildDir = join(rootDir, 'packages', 'app-cli', 'build');
await execCommand(['yarn', 'workspace', 'joplin', 'build']);
await copy(join(rootDir, 'README.md'), join(buildDir, 'README.md'));
await publishPackage(buildDir, join(rootDir, 'LICENSE'), dryRun);
};
const publishIfNeeded = async (manifest: PackageJson, publish: ()=> Promise<void>) => {
const id = `${manifest.name}@${manifest.version}`;
if (await isPublished(manifest)) {
console.info(`Skipping ${id}: already published`);
return;
}
console.info(`Publishing ${id}...`);
await publish();
};
export const versionAll = async () => {
const packages = await loadPublicPackages(await getRootDir());
const bumped = bumpVersions(packages.map(p => p.manifest));
for (const [index, { dir, manifest }] of packages.entries()) {
@@ -127,19 +145,17 @@ const versionAll = async () => {
}
};
// Packages are published one at a time, dependencies first, so that a failure leaves the registry in a
// consistent state. Already published versions are skipped, so the command can be re-run after a failure.
// Packages are published one at a time, dependencies first and the CLI app last, so that a failure leaves
// the registry in a consistent state. Already published versions are skipped, so the command can be
// re-run after a failure, and the CLI app is only published when its version has been bumped.
const publishAll = async (dryRun: boolean) => {
const rootDir = await getRootDir();
for (const { dir, manifest } of sortByDependencies(await loadPublicPackages(rootDir))) {
const id = `${manifest.name}@${manifest.version}`;
if (await isPublished(manifest)) {
console.info(`Skipping ${id}: already published`);
continue;
}
console.info(`Publishing ${id}...`);
await publishPackage(dir, join(rootDir, 'LICENSE'), dryRun);
await publishIfNeeded(manifest, () => publishPackage(dir, join(rootDir, 'LICENSE'), dryRun));
}
const cliManifest: PackageJson = JSON.parse(await readFile(join(rootDir, 'packages', 'app-cli', 'package.json'), 'utf8'));
await publishIfNeeded(cliManifest, () => publishCliApp(rootDir, dryRun));
};
if (require.main === module) {
@@ -147,7 +163,7 @@ if (require.main === module) {
try {
await yargs(hideBin(process.argv))
.command('version', 'Bumps the patch version of all public packages', {}, () => versionAll())
.command('publish', 'Publishes all public packages whose version is not on the registry yet', yargs => {
.command('publish', 'Publishes all public packages and the CLI app whose version is not on the registry yet', yargs => {
return yargs.option('dry-run', {
type: 'boolean',
default: false,
+8 -22
View File
@@ -30,32 +30,18 @@ It must be built and released manually using XCode.
## CLI application
Unlike the mobile or desktop application, the CLI app doesn't bundle its dependencies and is always installed from source. For that reason, all its `@joplin` dependencies must be deployed publicly first. This is done using:
Unlike the mobile or desktop application, the CLI app doesn't bundle its dependencies and is always installed from source. For that reason, all its `@joplin` dependencies, such as `@joplin/lib`, `@joplin/tools`, etc., must be published to npm with it.
yarn publishAll
This is going to bump the version of all the Joplin libraries, such as `@joplin/lib`, `@joplin/tools`, etc., then commit and push a `sub-packages-v*` tag. The tag triggers the `publish-sub-packages.yml` workflow, which builds and publishes the libraries to npm using [trusted publishing](https://docs.npmjs.com/trusted-publishers). Wait for that workflow to complete before releasing the CLI app.
Each library must have `laurent22/joplin` and `publish-sub-packages.yml` registered as a trusted publisher on npmjs.com, with "Allow npm publish" enabled. When adding a new library, it must be published once manually before this can be configured.
Then in `app-cli/package.json`, all `@joplin` dependencies and devdependencies must be set to the last major/minor version. For example:
```json
"dependencies": {
"@joplin/lib": "1.8",
"@joplin/renderer": "1.8",
"...": "..."
},
"devDependencies": {
"@joplin/tools": "1.8",
"...": "..."
}
```
Finally, to release the actual app, run:
To release the app, run:
yarn releaseCli
This is going to bump the version of the app and of all the Joplin libraries, then generate the changelog. Once the changelog has been checked, run the printed commands to commit and push a `cli-v*` tag. The tag triggers the `publish-sub-packages.yml` workflow, which builds and publishes the libraries, then the app, to npm using [trusted publishing](https://docs.npmjs.com/trusted-publishers).
To publish only the libraries, run `yarn publishAll` instead. It bumps their versions, then commits and pushes a `sub-packages-v*` tag, which triggers the same workflow.
The `joplin` package and each library must have `laurent22/joplin` and `publish-sub-packages.yml` registered as a trusted publisher on npmjs.com, with "Allow npm publish" enabled. When adding a new library, it must be published once manually before this can be configured.
## Joplin Server
Run: