Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
01b7370
Chore: Sync fuzzer: Add action that syncs a new temporary client
personalizedrefrigerator Jul 7, 2025
4da67d2
Work around sync failure during first sync of secondary client
personalizedrefrigerator Jul 7, 2025
a67f69a
Refactoring
personalizedrefrigerator Jul 7, 2025
7abddaf
Refactoring
personalizedrefrigerator Jul 8, 2025
0d38d70
Sync after accepting shares -- trying to fix E2EE key not loaded error
personalizedrefrigerator Jul 8, 2025
92aeff1
Merge remote-tracking branch 'upstream/dev' into pr/server/fuzzer/syn…
personalizedrefrigerator Aug 4, 2025
f987fbb
WIP: Keep the CLI app open in the background, rather than re-opening for
personalizedrefrigerator Aug 4, 2025
3b7db5f
Merge remote-tracking branch 'upstream/dev' into pr/server/fuzzer/syn…
personalizedrefrigerator Aug 5, 2025
94f94d7
Trying to fix "master key not loaded" sync error
personalizedrefrigerator Aug 5, 2025
c6fb978
Add additional debug logic
personalizedrefrigerator Aug 6, 2025
c03770b
Remove console.log
personalizedrefrigerator Aug 6, 2025
1bf4dc0
Work around certain timing-related sync issues by retrying sync with a
personalizedrefrigerator Aug 6, 2025
6585bbd
Work around E2EE sync error by adjusting retry delays
personalizedrefrigerator Aug 6, 2025
f783ee5
Resync secondary clients on the same account if the initial checkStat…
personalizedrefrigerator Aug 6, 2025
f658cbd
Debugging: Add command to dump the content of a specific item
personalizedrefrigerator Aug 6, 2025
aca24f8
Merge remote-tracking branch 'upstream/dev' into pr/server/fuzzer/syn…
personalizedrefrigerator Aug 6, 2025
1a9b539
Merge remote-tracking branch 'upstream/dev' into pr/server/fuzzer/jus…
personalizedrefrigerator Aug 8, 2025
32a664f
Revert changes related to adding new clients on the same account
personalizedrefrigerator Aug 8, 2025
6e03e13
Refactoring
personalizedrefrigerator Aug 8, 2025
253d567
Don't start a background decryption task when syncInfoCache changes and
personalizedrefrigerator Aug 8, 2025
56f48e8
Update server command help text
personalizedrefrigerator Aug 8, 2025
74f7d29
Refactoring
personalizedrefrigerator Aug 8, 2025
d576a10
Refactoring
personalizedrefrigerator Aug 8, 2025
78ef350
Batch command: Don't allow reading commands from stdin in GUI mode
personalizedrefrigerator Aug 8, 2025
d7a0207
Exclude more information from the client transcript
personalizedrefrigerator Aug 8, 2025
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
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
Expand Down Expand Up @@ -135,6 +136,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
Expand Down Expand Up @@ -1747,6 +1749,7 @@ packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ packages/app-cli/app/app.js
packages/app-cli/app/base-command.js
packages/app-cli/app/command-apidoc.js
packages/app-cli/app/command-attach.js
packages/app-cli/app/command-batch.js
packages/app-cli/app/command-cat.js
packages/app-cli/app/command-config.js
packages/app-cli/app/command-cp.js
Expand Down Expand Up @@ -108,6 +109,7 @@ packages/app-cli/app/gui/StatusBarWidget.js
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/setupCommand.js
packages/app-cli/app/utils/initializeCommandService.js
packages/app-cli/app/utils/iterateStdin.js
packages/app-cli/app/utils/shimInitCli.js
packages/app-cli/app/utils/testUtils.js
packages/app-cli/tests/HtmlToMd.js
Expand Down Expand Up @@ -1720,6 +1722,7 @@ packages/tools/fuzzer/utils/SeededRandom.js
packages/tools/fuzzer/utils/getNumberProperty.js
packages/tools/fuzzer/utils/getProperty.js
packages/tools/fuzzer/utils/getStringProperty.js
packages/tools/fuzzer/utils/openDebugSession.js
packages/tools/fuzzer/utils/retryWithCount.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
Expand Down
27 changes: 2 additions & 25 deletions packages/app-cli/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Tag from '@joplin/lib/models/Tag';
import Setting, { Env } from '@joplin/lib/models/Setting';
import { reg } from '@joplin/lib/registry.js';
import { dirname, fileExtension } from '@joplin/lib/path-utils';
import { splitCommandString } from '@joplin/utils';
import { _ } from '@joplin/lib/locale';
import { pathExists, readFile, readdirSync } from 'fs-extra';
import RevisionService from '@joplin/lib/services/RevisionService';
Expand All @@ -19,7 +18,6 @@ import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
import initializeCommandService from './utils/initializeCommandService';
const { cliUtils } = require('./cli-utils.js');
const Cache = require('@joplin/lib/Cache');
const { splitCommandBatch } = require('@joplin/lib/string-utils');

class Application extends BaseApplication {

Expand Down Expand Up @@ -381,22 +379,6 @@ class Application extends BaseApplication {
return output;
}

public async commandList(argv: string[]) {
if (argv.length && argv[0] === 'batch') {
const commands = [];
const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8'));

for (const commandLine of commandLines) {
if (!commandLine.trim()) continue;
const splitted = splitCommandString(commandLine.trim());
commands.push(splitted);
}
return commands;
} else {
return [argv];
}
}

// We need this special case here because by the time the `version` command
// runs, the keychain has already been setup.
public checkIfKeychainEnabled(argv: string[]) {
Expand Down Expand Up @@ -433,15 +415,10 @@ class Application extends BaseApplication {
if (argv.length) {
this.gui_ = this.dummyGui();

this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));

await this.applySettingsSideEffects();

await this.refreshCurrentFolder();
try {
const commands = await this.commandList(argv);
for (const command of commands) {
await this.execCommand(command);
}
await this.execCommand(argv);
} catch (error) {
if (this.showStackTraces_) {
console.error(error);
Expand Down
19 changes: 0 additions & 19 deletions packages/app-cli/app/command-batch.js

This file was deleted.

79 changes: 79 additions & 0 deletions packages/app-cli/app/command-batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { splitCommandBatch } from '@joplin/lib/string-utils';
import BaseCommand from './base-command';
import { _ } from '@joplin/lib/locale';
import { splitCommandString } from '@joplin/utils';
import iterateStdin from './utils/iterateStdin';
import { readFile } from 'fs-extra';
import app from './app';

interface Options {
'file-path': string;
options: {
'continue-on-failure': boolean;
};
}

class Command extends BaseCommand {
public usage() {
return 'batch <file-path>';
}

public options() {
return [
// These are present mostly for testing purposes
['--continue-on-failure', 'Continue running commands when one command in the batch fails.'],
];
}

public description() {
return _('Runs the commands contained in the text file. There should be one command per line.');
}

private streamCommands_ = async function*(filePath: string) {
const processLines = function*(lines: string) {
const commandLines = splitCommandBatch(lines);

for (const command of commandLines) {
if (!command.trim()) continue;
yield splitCommandString(command.trim());
}
};

if (filePath === '-') { // stdin
// Iterating over standard input conflicts with the CLI app's GUI.
if (app().hasGui()) {
throw new Error(_('Reading commands from standard input is only available in CLI mode.'));
}

for await (const lines of iterateStdin('command> ')) {
yield* processLines(lines);
}
} else {
const data = await readFile(filePath, 'utf-8');
yield* processLines(data);
}
};

public async action(options: Options) {
let lastError;
for await (const command of this.streamCommands_(options['file-path'])) {
try {
await app().refreshCurrentFolder();
await app().execCommand(command);
} catch (error) {
if (options.options['continue-on-failure']) {
app().stdout(error.message);
lastError = error;
} else {
throw error;
}
}
}

if (lastError) {
throw lastError;
}
}
}

module.exports = Command;
18 changes: 15 additions & 3 deletions packages/app-cli/app/command-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,25 @@ class Command extends BaseCommand {
return `${_('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|'))} This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.`;
}

options() {
return [
['--exit-early', 'Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.'],
['--quiet', 'Log less information to the console. More verbose logs will still be available through log-clipper.txt.'],
Comment on lines +19 to +20
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
['--exit-early', 'Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.'],
['--quiet', 'Log less information to the console. More verbose logs will still be available through log-clipper.txt.'],
['--exit-early', _('Allow the command to exit while the server is still running. The server will still stop when the app exits. Valid only for the `start` subcommand.')],
['--quiet', _('Log less information to the console. More verbose logs will still be available through log-clipper.txt.')],

These commands are mostly intended for debugging. However, since they have help text, it may make sense to allow the help text to be localized.

];
}

async action(args) {
const command = args.command;

const ClipperServer = require('@joplin/lib/ClipperServer').default;
ClipperServer.instance().initialize();
const stdoutFn = (...s) => this.stdout(s.join(' '));
const ignoreOutputFn = ()=>{};
const clipperLogger = new Logger();
clipperLogger.addTarget('file', { path: `${Setting.value('profileDir')}/log-clipper.txt` });
clipperLogger.addTarget('console', { console: {
info: stdoutFn,
warn: stdoutFn,
info: args.options.quiet ? ignoreOutputFn : stdoutFn,
warn: args.options.quiet ? ignoreOutputFn : stdoutFn,
error: stdoutFn,
} });
ClipperServer.instance().setDispatch(() => {});
Expand All @@ -38,7 +46,11 @@ class Command extends BaseCommand {
this.stdout(_('Server is already running on port %d', runningOnPort));
} else {
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
await ClipperServer.instance().start(); // Never exit
const promise = ClipperServer.instance().start();

if (!args.options['exit-early']) {
await promise; // Never exit
}
}
} else if (command === 'status') {
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));
Expand Down
54 changes: 54 additions & 0 deletions packages/app-cli/app/utils/iterateStdin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { createInterface } from 'readline/promises';

const iterateStdin = async function*(prompt: string) {
let nextLineListeners: (()=> void)[] = [];
const dispatchAllListeners = () => {
const listeners = nextLineListeners;
nextLineListeners = [];
for (const listener of listeners) {
listener();
}
};

const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
rl.setPrompt(prompt);

let buffer: string[] = [];
rl.on('line', (line) => {
buffer.push(line);
dispatchAllListeners();
});

let done = false;
rl.on('close', () => {
done = true;
dispatchAllListeners();
});

const readNextLines = () => {
return new Promise<string|null>(resolve => {
if (done) {
resolve(null);
} else if (buffer.length > 0) {
resolve(buffer.join('\n'));
buffer = [];
} else {
nextLineListeners.push(() => {
resolve(buffer.join('\n'));
buffer = [];
});
}
});
};

while (!done) {
rl.prompt();
const lines = await readNextLines();
yield lines;
}
};

export default iterateStdin;
19 changes: 10 additions & 9 deletions packages/lib/BaseApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,17 +395,18 @@ export default class BaseApplication {
// - All the calls below are cheap or do nothing if there's nothing
// to do.
'syncInfoCache': async () => {
if (this.hasGui()) {
appLogger.info('"syncInfoCache" was changed - setting up encryption related code');
appLogger.info('"syncInfoCache" was changed - setting up encryption related code');

await loadMasterKeysFromSettings(EncryptionService.instance());
void DecryptionWorker.instance().scheduleStart();
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();
await loadMasterKeysFromSettings(EncryptionService.instance());
const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();

this.dispatch({
type: 'MASTERKEY_REMOVE_NOT_LOADED',
ids: loadedMasterKeyIds,
});

this.dispatch({
type: 'MASTERKEY_REMOVE_NOT_LOADED',
ids: loadedMasterKeyIds,
});
if (this.hasGui()) {
void DecryptionWorker.instance().scheduleStart();

// Schedule a sync operation so that items that need to be encrypted
// are sent to sync target.
Expand Down
Loading
Loading