Skip to content
Merged
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
35 changes: 25 additions & 10 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ See [docs/usage.md](../docs/usage.md) for full usage and resolution order (flags

- [`powersync autocomplete [SHELL]`](#powersync-autocomplete-shell)
- [`powersync commands`](#powersync-commands)
- [`powersync configure ide`](#powersync-configure-ide)
- [`powersync deploy`](#powersync-deploy)
- [`powersync deploy service-config`](#powersync-deploy-service-config)
- [`powersync deploy sync-config`](#powersync-deploy-sync-config)
Expand Down Expand Up @@ -353,6 +354,26 @@ DESCRIPTION

_See code: [@oclif/plugin-commands](https://github.com/oclif/plugin-commands/blob/v4.1.40/src/commands/commands.ts)_

## `powersync configure ide`

Configure your IDE for the best PowerSync CLI developer experience.

```
USAGE
$ powersync configure ide

DESCRIPTION
Configure your IDE for the best PowerSync CLI developer experience.

Configure or guide your IDE setup for the best PowerSync CLI developer experience. Enables YAML schema validation and
autocompletion, sets up !env custom tag support, and patches existing config files with language server directives.

EXAMPLES
$ powersync configure ide
```

_See code: [src/commands/configure/ide.ts](https://github.com/powersync-ja/powersync-js/blob/v0.0.0/src/commands/configure/ide.ts)_

## `powersync deploy`

[Cloud only] Deploy local config to the linked Cloud instance (connections + auth + sync config).
Expand Down Expand Up @@ -901,10 +922,7 @@ Scaffold a PowerSync Cloud config directory from a template.

```
USAGE
$ powersync init cloud [--directory <value>] [--vscode]

FLAGS
--vscode Configure the workspace with .vscode settings for YAML custom tags (!env).
$ powersync init cloud [--directory <value>]

PROJECT FLAGS
--directory=<value> [default: powersync] Directory containing PowerSync config. Defaults to "powersync". This is
Expand All @@ -919,7 +937,7 @@ DESCRIPTION
EXAMPLES
$ powersync init cloud

$ powersync init cloud --directory=powersync --vscode
$ powersync init cloud --directory=powersync
```

_See code: [src/commands/init/cloud.ts](https://github.com/powersync-ja/powersync-js/blob/v0.0.0/src/commands/init/cloud.ts)_
Expand All @@ -930,10 +948,7 @@ Scaffold a PowerSync self-hosted config directory from a template.

```
USAGE
$ powersync init self-hosted [--directory <value>] [--vscode]

FLAGS
--vscode Configure the workspace with .vscode settings for YAML custom tags (!env).
$ powersync init self-hosted [--directory <value>]

PROJECT FLAGS
--directory=<value> [default: powersync] Directory containing PowerSync config. Defaults to "powersync". This is
Expand All @@ -949,7 +964,7 @@ DESCRIPTION
EXAMPLES
$ powersync init self-hosted

$ powersync init self-hosted --directory=powersync --vscode
$ powersync init self-hosted --directory=powersync
```

_See code: [src/commands/init/self-hosted.ts](https://github.com/powersync-ja/powersync-js/blob/v0.0.0/src/commands/init/self-hosted.ts)_
Expand Down
104 changes: 104 additions & 0 deletions cli/src/api/ide/configure-vscode-ide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { ux } from '@oclif/core';
import {
CLI_FILENAME,
SERVICE_FILENAME,
SYNC_FILENAME,
YAML_CLI_SCHEMA,
YAML_SERVICE_SCHEMA,
YAML_SYNC_RULES_SCHEMA
} from '@powersync/cli-core';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

const VSCODE_YAML_TAGS = ['!env scalar'];

/** Maps each known config filename to its yaml-language-server schema comment. */
const YAML_SCHEMA_COMMENTS: Record<string, string> = {
[CLI_FILENAME]: YAML_CLI_SCHEMA,
[SERVICE_FILENAME]: YAML_SERVICE_SCHEMA,
[SYNC_FILENAME]: YAML_SYNC_RULES_SCHEMA
};

/**
* A pluggable function signature for configuring a specific IDE.
* Receives the workspace root (for IDE settings), the list of discovered project
* directories to scan, and a log callback for producing output.
*/
export type IdeConfigurator = (workspaceRoot: string, projectDirs: string[], log: (message: string) => void) => void;

/**
* Configures the VSCode workspace for PowerSync YAML editing and prints guidance:
* - Writes/merges .vscode/settings.json with yaml.customTags so the !env tag is recognised.
* - Scans each projectDir for known config files and prepends a yaml-language-server schema
* comment to any file that does not already have one.
* - Prints a summary of changes, extension recommendation, and schema comment reference.
*/
export function configureVscodeIde(workspaceRoot: string, projectDirs: string[], log: (message: string) => void): void {
const vscodeDir = join(workspaceRoot, '.vscode');
const settingsPath = join(vscodeDir, 'settings.json');

let settings: Record<string, unknown> = {};
if (existsSync(settingsPath)) {
try {
const raw = readFileSync(settingsPath, 'utf8');
settings = JSON.parse(raw) as Record<string, unknown>;
} catch {
// If invalid JSON, overwrite with our settings.
}
}

const currentTags = (settings['yaml.customTags'] ?? []) as string[];
settings['yaml.customTags'] = [...new Set([...currentTags, ...VSCODE_YAML_TAGS])];
mkdirSync(vscodeDir, { recursive: true });
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');

const filesUpdated: string[] = [];

for (const projectDir of projectDirs) {
for (const [filename, schemaComment] of Object.entries(YAML_SCHEMA_COMMENTS)) {
const filePath = join(projectDir, filename);
if (!existsSync(filePath)) continue;

const content = readFileSync(filePath, 'utf8');
if (!content.includes('yaml-language-server:')) {
writeFileSync(filePath, `${schemaComment}\n\n${content}`);
filesUpdated.push(filePath);
}
}
}

const lines: string[] = [
ux.colorize('green', 'VSCode configured for PowerSync YAML editing!'),
'',
`✔ Updated .vscode/settings.json with yaml.customTags: ${JSON.stringify(VSCODE_YAML_TAGS)}`
];

if (filesUpdated.length > 0) {
lines.push('', 'Added yaml-language-server schema comments to:');
for (const f of filesUpdated) {
lines.push(` ✔ ${f}`);
}
}

lines.push(
'',
ux.colorize('cyan', 'Recommended: Install the YAML extension'),
'Install the Red Hat YAML extension for VSCode to get schema validation and autocompletion:',
ux.colorize('blue', ' https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml'),
'',
ux.colorize('cyan', 'Language server schema comments'),
'The following comments at the top of each YAML config file activate schema support.',
'They are added automatically when you run powersync init, but you can also add them manually:',
'',
ux.colorize('dim', '# service.yaml'),
ux.colorize('gray', YAML_SERVICE_SCHEMA),
'',
ux.colorize('dim', '# sync-config.yaml'),
ux.colorize('gray', YAML_SYNC_RULES_SCHEMA),
'',
ux.colorize('dim', '# cli.yaml'),
ux.colorize('gray', YAML_CLI_SCHEMA)
);

log(lines.join('\n'));
}
29 changes: 0 additions & 29 deletions cli/src/api/write-vscode-settings-for-yaml-env.ts

This file was deleted.

68 changes: 68 additions & 0 deletions cli/src/commands/configure/ide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { select } from '@inquirer/prompts';
import { ux } from '@oclif/core';
import { CLI_FILENAME, parseYamlFile, PowerSyncCommand } from '@powersync/cli-core';
import { CLIConfig } from '@powersync/cli-schemas';
import { readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';

import { configureVscodeIde, type IdeConfigurator } from '../../api/ide/configure-vscode-ide.js';

const IDE_CONFIGURATORS: Record<string, IdeConfigurator> = {
vscode: configureVscodeIde
};

/**
* Scans the current working directory for subdirectories that contain a valid
* PowerSync cli.yaml. Returns their absolute paths.
*/
function findLinkedProjectDirs(cwd: string): string[] {
const projectDirs: string[] = [];

for (const entry of readdirSync(cwd)) {
const entryPath = join(cwd, entry);
try {
if (!statSync(entryPath).isDirectory()) continue;
} catch {
continue;
}

try {
const doc = parseYamlFile(join(entryPath, CLI_FILENAME));
CLIConfig.decode(doc.contents?.toJSON());
projectDirs.push(entryPath);
} catch {
// Not a valid PowerSync project — skip.
}
}

return projectDirs;
}

export default class ConfigureIde extends PowerSyncCommand {
static description =
'Configure or guide your IDE setup for the best PowerSync CLI developer experience. Enables YAML schema validation and autocompletion, sets up !env custom tag support, and patches existing config files with language server directives.';
static examples = ['<%= config.bin %> <%= command.id %>'];
static summary = 'Configure your IDE for the best PowerSync CLI developer experience.';

async run(): Promise<void> {
await this.parse(ConfigureIde);

this.log(
`Tip: use ${ux.colorize('blue', 'powersync edit config')} for a complete in-browser editing experience.\n`
);

const ide = await select({
choices: [
{ name: 'VSCode', value: 'vscode' },
{ name: 'Exit', value: 'exit' }
],
message: 'Select your IDE to configure (only VSCode is supported for now):'
});

if (ide === 'exit') return;

const projectDirs = findLinkedProjectDirs(process.cwd());
const configurator = IDE_CONFIGURATORS[ide];
configurator(process.cwd(), projectDirs, (msg) => this.log(msg));
}
}
13 changes: 13 additions & 0 deletions cli/src/commands/configure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Command } from '@oclif/core';

export default class Configure extends Command {
static description = 'Configure your workspace or IDE for PowerSync development.';
static examples = ['<%= config.bin %> <%= command.id %>'];
static hidden = true;
static summary = 'Configure your workspace or IDE for PowerSync development.';

async run(): Promise<void> {
await this.parse(Configure);
this.log('Use a subcommand: configure ide');
}
}
23 changes: 5 additions & 18 deletions cli/src/commands/init/cloud.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flags, ux } from '@oclif/core';
import { ux } from '@oclif/core';
import {
CLI_FILENAME,
InstanceCommand,
Expand All @@ -12,8 +12,6 @@ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { writeVscodeSettingsForYamlEnv } from '../../api/write-vscode-settings-for-yaml-env.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const TEMPLATES_DIR = join(__dirname, '..', '..', '..', 'templates');

Expand All @@ -22,20 +20,16 @@ export default class InitCloud extends InstanceCommand {
'Copy a Cloud template into a config directory (default powersync/). Edit service.yaml then run link cloud and deploy.';
static examples = [
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> --directory=powersync --vscode'
'<%= config.bin %> <%= command.id %> --directory=powersync'
];
static flags = {
...InstanceCommand.flags,
vscode: Flags.boolean({
default: false,
description: 'Configure the workspace with .vscode settings for YAML custom tags (!env).'
})
...InstanceCommand.flags
};
static summary = 'Scaffold a PowerSync Cloud config directory from a template.';

async run(): Promise<void> {
const { flags } = await this.parse(InitCloud);
const { directory, vscode } = flags;
const { directory } = flags;
const targetDir = this.resolveProjectDir(flags);

if (existsSync(targetDir)) {
Expand All @@ -62,10 +56,6 @@ export default class InitCloud extends InstanceCommand {
writeFileSync(syncPath, `${YAML_SYNC_RULES_SCHEMA}\n\n${readFileSync(syncPath, 'utf8')}`);
writeFileSync(cliPath, `${YAML_CLI_SCHEMA}\n\n${readFileSync(cliPath, 'utf8')}`);

if (vscode) {
writeVscodeSettingsForYamlEnv(process.cwd());
}

const instructions = [
'Create a new instance with ',
ux.colorize('blue', '\tpowersync link cloud --create --org-id=<org-id> --project-id=<project-id>'),
Expand All @@ -86,13 +76,10 @@ export default class InitCloud extends InstanceCommand {
'Configuration files are located in:',
`\t${targetDir}`,
`Check the ${SERVICE_FILENAME} and ${SYNC_FILENAME} file(s) and configure them by uncommenting the options you would like to use.`,
`Tip: Run ${ux.colorize('blue', 'powersync configure ide')} to configure your IDE for YAML schema support.`,
'',
instructions
];
if (vscode) {
lines.splice(6, 0, 'Added .vscode/settings.json for YAML !env tag support.');
lines.splice(7, 0, '');
}

this.log(lines.join('\n'));
}
Expand Down
Loading