From d62de133ba2006e7f85dbb1383bfa5c5b4528992 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 14 Apr 2026 16:31:32 +0200 Subject: [PATCH 1/5] feat: resolve project-id and org-id using instance-id --- .gitignore | 3 +- .../api/cloud/validate-cloud-link-config.ts | 60 ++++++------ cli/src/commands/deploy/index.ts | 5 +- cli/src/commands/deploy/service-config.ts | 5 +- cli/src/commands/deploy/sync-config.ts | 5 +- cli/src/commands/fetch/status.ts | 2 +- cli/src/commands/generate/schema.ts | 2 +- cli/src/commands/init/cloud.ts | 5 +- cli/src/commands/link/cloud.ts | 51 +++++++--- cli/src/commands/pull/index.ts | 2 +- cli/src/commands/pull/instance.ts | 93 ++++++++++++++----- cli/test/commands/migrate.test.ts | 34 ++++--- .../src/command-types/CloudInstanceCommand.ts | 45 ++++----- .../command-types/SharedInstanceCommand.ts | 33 +++---- packages/cli-core/src/index.ts | 1 + .../src/utils/resolve-cloud-instance-link.ts | 77 +++++++++++++++ 16 files changed, 275 insertions(+), 148 deletions(-) create mode 100644 packages/cli-core/src/utils/resolve-cloud-instance-link.ts diff --git a/.gitignore b/.gitignore index 2504c3d..b6d6e28 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ playground/ # PNPM .pnpm-store/ +.pnpmfile.cjs # MacOS -.DS_Store \ No newline at end of file +.DS_Store diff --git a/cli/src/api/cloud/validate-cloud-link-config.ts b/cli/src/api/cloud/validate-cloud-link-config.ts index 365ad1f..dcbb149 100644 --- a/cli/src/api/cloud/validate-cloud-link-config.ts +++ b/cli/src/api/cloud/validate-cloud-link-config.ts @@ -1,12 +1,14 @@ -import { createAccountsHubClient, OBJECT_ID_REGEX } from '@powersync/cli-core'; +import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; + +import { createAccountsHubClient, OBJECT_ID_REGEX, resolveCloudInstanceLink } from '@powersync/cli-core'; import { PowerSyncManagementClient } from '@powersync/management-client'; type InstanceConfigResponse = Awaited>; export type CloudLinkValidationInput = { instanceId?: string; - orgId: string; - projectId: string; + orgId?: string; + projectId?: string; }; export type ValidateCloudLinkConfigOptions = { @@ -17,9 +19,14 @@ export type ValidateCloudLinkConfigOptions = { export type ValidateCloudLinkConfigResult = { instanceConfig?: InstanceConfigResponse; + linked?: ResolvedCloudCLIConfig; }; -function ensureObjectId(value: string, flagName: '--instance-id' | '--org-id' | '--project-id') { +function ensureObjectId(value: string | undefined, flagName: '--instance-id' | '--org-id' | '--project-id') { + if (value == null) { + return; + } + if (!OBJECT_ID_REGEX.test(value)) { throw new Error(`Invalid ${flagName} "${value}". Expected a BSON ObjectID (24 hex characters).`); } @@ -31,6 +38,28 @@ export async function validateCloudLinkConfig( const { cloudClient, input, validateInstance = false } = options; const { instanceId, orgId, projectId } = input; + if (validateInstance) { + const linked = await resolveCloudInstanceLink({ client: cloudClient, instanceId, orgId, projectId }); + let instanceConfig: InstanceConfigResponse; + try { + instanceConfig = await cloudClient.getInstanceConfig({ + app_id: linked.project_id, + id: linked.instance_id, + org_id: linked.org_id + }); + } catch { + throw new Error( + `Instance ${linked.instance_id} was not found in project ${linked.project_id} in organization ${linked.org_id}, or is not accessible with the current token.` + ); + } + + return { instanceConfig, linked }; + } + + if (!orgId || !projectId) { + throw new Error('Project validation requires both an organization ID and a project ID.'); + } + ensureObjectId(orgId, '--org-id'); ensureObjectId(projectId, '--project-id'); @@ -57,26 +86,5 @@ export async function validateCloudLinkConfig( ); } - if (!validateInstance) { - return {}; - } - - if (!instanceId) { - throw new Error('Instance validation requested but no instance ID was provided.'); - } - - ensureObjectId(instanceId, '--instance-id'); - - try { - const instanceConfig = await cloudClient.getInstanceConfig({ - app_id: projectId, - id: instanceId, - org_id: orgId - }); - return { instanceConfig }; - } catch { - throw new Error( - `Instance ${instanceId} was not found in project ${projectId} in organization ${orgId}, or is not accessible with the current token.` - ); - } + return {}; } diff --git a/cli/src/commands/deploy/index.ts b/cli/src/commands/deploy/index.ts index e53899e..95feb9e 100644 --- a/cli/src/commands/deploy/index.ts +++ b/cli/src/commands/deploy/index.ts @@ -16,10 +16,7 @@ export default class DeployAll extends WithSyncConfigFilePath(BaseDeployCommand) `See also ${ux.colorize('blue', 'powersync deploy sync-config')} to deploy only sync config changes.`, `See also ${ux.colorize('blue', 'powersync deploy service-config')} to deploy only service config changes.` ].join('\n'); - static examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id=' - ]; + static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --instance-id=']; static flags = { ...GENERAL_VALIDATION_FLAG_HELPERS.flags }; diff --git a/cli/src/commands/deploy/service-config.ts b/cli/src/commands/deploy/service-config.ts index c9c7122..e095206 100644 --- a/cli/src/commands/deploy/service-config.ts +++ b/cli/src/commands/deploy/service-config.ts @@ -12,10 +12,7 @@ const SERVICE_CONFIG_VALIDATION_FLAGS = generateValidationTestFlags({ export default class DeployServiceConfig extends BaseDeployCommand { static description = 'Deploy only service config changes (without sync config updates).'; - static examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id=' - ]; + static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --instance-id=']; static flags = { ...SERVICE_CONFIG_VALIDATION_FLAGS.flags }; diff --git a/cli/src/commands/deploy/sync-config.ts b/cli/src/commands/deploy/sync-config.ts index 74c2319..de1cbb1 100644 --- a/cli/src/commands/deploy/sync-config.ts +++ b/cli/src/commands/deploy/sync-config.ts @@ -17,10 +17,7 @@ const SYNC_CONFIG_VALIDATION_FLAGS = generateValidationTestFlags({ export default class DeploySyncConfig extends WithSyncConfigFilePath(BaseDeployCommand) { static description = 'Deploy only sync config changes.'; - static examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id=' - ]; + static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --instance-id=']; static flags = { ...SYNC_CONFIG_VALIDATION_FLAGS.flags }; diff --git a/cli/src/commands/fetch/status.ts b/cli/src/commands/fetch/status.ts index 02b6596..573702a 100644 --- a/cli/src/commands/fetch/status.ts +++ b/cli/src/commands/fetch/status.ts @@ -10,7 +10,7 @@ export default class FetchStatus extends SharedInstanceCommand { static examples = [ '<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --output=json', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id=' + '<%= config.bin %> <%= command.id %> --instance-id=' ]; static flags = { output: Flags.string({ diff --git a/cli/src/commands/generate/schema.ts b/cli/src/commands/generate/schema.ts index e101b3e..6b05566 100644 --- a/cli/src/commands/generate/schema.ts +++ b/cli/src/commands/generate/schema.ts @@ -19,7 +19,7 @@ export default class GenerateSchema extends WithSyncConfigFilePath(SharedInstanc 'Generate a client-side schema file from the instance database schema and sync config. Supports multiple output types (e.g. type, dart). Requires a linked instance. Cloud and self-hosted.'; static examples = [ '<%= config.bin %> <%= command.id %> --output=ts --output-path=schema.ts', - '<%= config.bin %> <%= command.id %> --output=dart --output-path=lib/schema.dart --instance-id= --project-id=' + '<%= config.bin %> <%= command.id %> --output=dart --output-path=lib/schema.dart --instance-id=' ]; static flags = { output: Flags.string({ diff --git a/cli/src/commands/init/cloud.ts b/cli/src/commands/init/cloud.ts index 2f1d408..ba65f49 100644 --- a/cli/src/commands/init/cloud.ts +++ b/cli/src/commands/init/cloud.ts @@ -70,10 +70,7 @@ export default class InitCloud extends InstanceCommand { 'Create a new instance with ', ux.colorize('blue', '\tpowersync link cloud --create --org-id= --project-id='), 'or pull an existing instance with ', - ux.colorize( - 'blue', - '\tpowersync pull instance --org-id= --project-id= --instance-id=' - ), + ux.colorize('blue', '\tpowersync pull instance --instance-id='), `Tip: use ${ux.colorize('blue', 'powersync fetch instances')} to see available organizations and projects for your token.`, 'Then run', ux.colorize('blue', '\tpowersync deploy'), diff --git a/cli/src/commands/link/cloud.ts b/cli/src/commands/link/cloud.ts index 212c200..3c76f7a 100644 --- a/cli/src/commands/link/cloud.ts +++ b/cli/src/commands/link/cloud.ts @@ -1,3 +1,5 @@ +import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; + import { Flags, ux } from '@oclif/core'; import { CLI_FILENAME, @@ -16,9 +18,9 @@ import { writeCloudLink } from '../../api/cloud/write-cloud-link.js'; export default class LinkCloud extends CloudInstanceCommand { static commandHelpGroup = CommandHelpGroup.PROJECT_SETUP; static description = - 'Write or update cli.yaml with a Cloud instance (instance-id, org-id, project-id). Use --create to create a new instance from service.yaml name/region and link it; omit --instance-id when using --create. Org ID is optional when the token has a single organization.'; + 'Write or update cli.yaml with a Cloud instance. Use --create to create a new instance from service.yaml name/region and link it; omit --instance-id when using --create.'; static examples = [ - '<%= config.bin %> <%= command.id %> --project-id=', + '<%= config.bin %> <%= command.id %> --instance-id=', '<%= config.bin %> <%= command.id %> --create --project-id=', '<%= config.bin %> <%= command.id %> --instance-id= --project-id= --org-id=' ]; @@ -36,13 +38,13 @@ export default class LinkCloud extends CloudInstanceCommand { 'org-id': Flags.string({ default: env.ORG_ID, description: - 'Organization ID. Optional when the token has a single org; required when the token has multiple orgs. Resolved: flag → ORG_ID → cli.yaml.', + 'Organization ID. Required with --create when the token has multiple orgs; optional when linking an existing instance.', required: false }), 'project-id': Flags.string({ default: env.PROJECT_ID, - description: 'Project ID. Resolved: flag → PROJECT_ID → cli.yaml.', - required: true + description: 'Project ID. Required with --create; optional assertion when linking an existing instance.', + required: false }) }; static summary = '[Cloud only] Link to a PowerSync Cloud instance (or create one with --create).'; @@ -51,10 +53,6 @@ export default class LinkCloud extends CloudInstanceCommand { const { flags } = await this.parse(LinkCloud); let { create, directory, 'instance-id': instanceId, 'org-id': orgId, 'project-id': projectId } = flags; - if (!orgId) { - orgId = await getDefaultOrgId(); - } - const projectDirectory = this.resolveProjectDir(flags); if (create) { if (instanceId) { @@ -63,10 +61,23 @@ export default class LinkCloud extends CloudInstanceCommand { }); } + if (!projectId) { + this.styledError({ + message: 'Creating a Cloud instance requires --project-id.' + }); + } + + if (!orgId) { + orgId = await getDefaultOrgId(); + } + + const createOrgId = orgId!; + const createProjectId = projectId!; + try { await validateCloudLinkConfig({ cloudClient: this.client, - input: { orgId, projectId }, + input: { orgId: createOrgId, projectId: createProjectId }, validateInstance: false }); } catch (error) { @@ -80,8 +91,8 @@ export default class LinkCloud extends CloudInstanceCommand { try { const result = await createCloudInstance(client, { name: config.name, - orgId, - projectId, + orgId: createOrgId, + projectId: createProjectId, region: config.region }); newInstanceId = result.instanceId; @@ -96,7 +107,7 @@ export default class LinkCloud extends CloudInstanceCommand { expectedType: ServiceType.CLOUD, projectDir: projectDirectory }); - writeCloudLink(projectDirectory, { instanceId: newInstanceId, orgId, projectId }); + writeCloudLink(projectDirectory, { instanceId: newInstanceId, orgId: createOrgId, projectId: createProjectId }); this.log( ux.colorize('green', `Created Cloud instance ${newInstanceId} and updated ${directory}/${CLI_FILENAME}.`) ); @@ -110,17 +121,27 @@ export default class LinkCloud extends CloudInstanceCommand { }); } + let linked: ResolvedCloudCLIConfig | undefined; try { - await validateCloudLinkConfig({ + const validationResult = await validateCloudLinkConfig({ cloudClient: this.client, input: { instanceId, orgId, projectId }, validateInstance: true }); + linked = validationResult.linked; } catch (error) { this.styledError({ message: error instanceof Error ? error.message : String(error) }); } - writeCloudLink(projectDirectory, { instanceId, orgId, projectId }); + if (!linked) { + this.styledError({ message: `Failed to resolve Cloud instance ${instanceId}.` }); + } + + writeCloudLink(projectDirectory, { + instanceId: linked.instance_id, + orgId: linked.org_id, + projectId: linked.project_id + }); ensureServiceTypeMatches({ command: this, configRequired: false, diff --git a/cli/src/commands/pull/index.ts b/cli/src/commands/pull/index.ts index d2ba6a8..2f57a37 100644 --- a/cli/src/commands/pull/index.ts +++ b/cli/src/commands/pull/index.ts @@ -2,7 +2,7 @@ import { Command } from '@oclif/core'; export default class Pull extends Command { static description = - 'Download current config from PowerSync Cloud into local YAML files. Use pull instance; pass --instance-id and --project-id when the directory is not yet linked (--org-id is optional when the token has a single organization).'; + 'Download current config from PowerSync Cloud into local YAML files. Use pull instance; pass --instance-id when the directory is not yet linked.'; static examples = ['<%= config.bin %> <%= command.id %>']; static hidden = true; static summary = '[Cloud only] Download Cloud config into local service.yaml and sync-config.yaml.'; diff --git a/cli/src/commands/pull/instance.ts b/cli/src/commands/pull/instance.ts index 51b1614..1ddaa56 100644 --- a/cli/src/commands/pull/instance.ts +++ b/cli/src/commands/pull/instance.ts @@ -1,10 +1,12 @@ +import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; + import { Flags, ux } from '@oclif/core'; import { CLI_FILENAME, CloudInstanceCommand, CommandHelpGroup, ensureServiceTypeMatches, - getDefaultOrgId, + env, SERVICE_FILENAME, ServiceType, SYNC_FILENAME, @@ -30,10 +32,10 @@ const PULL_CONFIG_HEADER = `# PowerSync Cloud config (fetched from cloud) export default class PullInstance extends CloudInstanceCommand { static commandHelpGroup = CommandHelpGroup.PROJECT_SETUP; static description = - 'Fetch an existing Cloud instance by ID: create the config directory if needed, write cli.yaml, and download service.yaml and sync-config.yaml. Pass --instance-id and --project-id when the directory is not yet linked; --org-id is optional when the token has a single organization. Cloud only.'; + 'Fetch an existing Cloud instance by ID: create the config directory if needed, write cli.yaml, and download service.yaml and sync-config.yaml. Cloud only.'; static examples = [ '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id=', + '<%= config.bin %> <%= command.id %> --instance-id=', '<%= config.bin %> <%= command.id %> --instance-id= --project-id= --org-id=' ]; static flags = { @@ -48,20 +50,36 @@ export default class PullInstance extends CloudInstanceCommand { async run(): Promise { const { flags } = await this.parse(PullInstance); const { directory, 'instance-id': instanceId, 'org-id': _orgId, 'project-id': projectId } = flags; + const inputInstanceId = instanceId ?? env.INSTANCE_ID; + const inputOrgId = _orgId ?? env.ORG_ID; + const inputProjectId = projectId ?? env.PROJECT_ID; - const resolvedOrgId = _orgId ?? (await getDefaultOrgId().catch(() => null)); + let resolvedLink: ResolvedCloudCLIConfig | undefined; + let instanceConfig; /** * The pull instance command can be used to create a new powersync project directory */ const projectDir = this.resolveProjectDir(flags); if (!existsSync(projectDir)) { - if (instanceId && resolvedOrgId && projectId) { - mkdirSync(projectDir, { recursive: true }); - } else { + if (!inputInstanceId) { this.styledError({ - message: `Directory "${directory}" not found. Pass --instance-id, and --project-id to create the config directory and link, or run this command from a directory that already contains a linked PowerSync config.` + message: `Directory "${directory}" not found. Pass --instance-id to create the config directory and link, or run this command from a directory that already contains a linked PowerSync config.` }); } + + try { + const validationResult = await validateCloudLinkConfig({ + cloudClient: this.client, + input: { instanceId: inputInstanceId, orgId: inputOrgId, projectId: inputProjectId }, + validateInstance: true + }); + resolvedLink = validationResult.linked; + instanceConfig = validationResult.instanceConfig; + } catch (error) { + this.styledError({ message: error instanceof Error ? error.message : String(error) }); + } + + mkdirSync(projectDir, { recursive: true }); } ensureServiceTypeMatches({ @@ -74,32 +92,57 @@ export default class PullInstance extends CloudInstanceCommand { const linkPath = join(projectDir, CLI_FILENAME); if (!existsSync(linkPath)) { - if (!instanceId || !resolvedOrgId || !projectId) { + if (!resolvedLink) { + if (!inputInstanceId) { + this.styledError({ + message: `Linking is required. Pass --instance-id to this command, or run ${ux.colorize('blue', 'powersync link cloud --instance-id=')} first.` + }); + } + + try { + const validationResult = await validateCloudLinkConfig({ + cloudClient: this.client, + input: { instanceId: inputInstanceId, orgId: inputOrgId, projectId: inputProjectId }, + validateInstance: true + }); + resolvedLink = validationResult.linked; + instanceConfig = validationResult.instanceConfig; + } catch (error) { + this.styledError({ message: error instanceof Error ? error.message : String(error) }); + } + } + + if (!resolvedLink) { this.styledError({ - message: `Linking is required. Pass --instance-id, --org-id, and --project-id to this command, or run ${ux.colorize('blue', 'powersync link cloud --instance-id= --org-id= --project-id=')} first.` + message: `Failed to resolve Cloud instance ${inputInstanceId}.` }); } - writeCloudLink(projectDir, { instanceId, orgId: resolvedOrgId, projectId }); + writeCloudLink(projectDir, { + instanceId: resolvedLink.instance_id, + orgId: resolvedLink.org_id, + projectId: resolvedLink.project_id + }); this.log(`Created ${ux.colorize('blue', `${directory}/${CLI_FILENAME}`)} with Cloud instance link.`); } const { linked } = await this.loadProject(flags); - let instanceConfig; - try { - const validationResult = await validateCloudLinkConfig({ - cloudClient: this.client, - input: { - instanceId: linked.instance_id, - orgId: linked.org_id, - projectId: linked.project_id - }, - validateInstance: true - }); - instanceConfig = validationResult.instanceConfig; - } catch (error) { - this.styledError({ message: error instanceof Error ? error.message : String(error) }); + if (!instanceConfig) { + try { + const validationResult = await validateCloudLinkConfig({ + cloudClient: this.client, + input: { + instanceId: linked.instance_id, + orgId: linked.org_id, + projectId: linked.project_id + }, + validateInstance: true + }); + instanceConfig = validationResult.instanceConfig; + } catch (error) { + this.styledError({ message: error instanceof Error ? error.message : String(error) }); + } } if (!instanceConfig) { diff --git a/cli/test/commands/migrate.test.ts b/cli/test/commands/migrate.test.ts index d6a34cd..dc22a5a 100644 --- a/cli/test/commands/migrate.test.ts +++ b/cli/test/commands/migrate.test.ts @@ -2,33 +2,36 @@ import { runCommand } from '@oclif/test'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { expect, onTestFinished, test } from 'vitest'; +import { describe, expect, onTestFinished, test } from 'vitest'; import { root } from '../helpers/root.js'; -test('migrates from sync rules to sync streams', async () => { - const testDirectory = mkdtempSync(join(tmpdir(), 'migrate-test-')); - onTestFinished(() => rmSync(testDirectory, { recursive: true })); +describe('migrate', () => { + test('migrates from sync rules to sync streams', async () => { + const testDirectory = mkdtempSync(join(tmpdir(), 'migrate-test-')); + onTestFinished(() => rmSync(testDirectory, { recursive: true })); - const inputFile = join(testDirectory, 'input.yaml'); - const outputFile = join(testDirectory, 'output.yaml'); - writeFileSync( - inputFile, - ` + const inputFile = join(testDirectory, 'input.yaml'); + const outputFile = join(testDirectory, 'output.yaml'); + writeFileSync( + inputFile, + ` bucket_definitions: user_lists: parameters: SELECT request.user_id() as user_id data: - SELECT * FROM lists WHERE owner_id = bucket.user_id ` - ); + ); - const result = await runCommand(`migrate sync-rules --input-file ${inputFile} --output-file ${outputFile}`, { root }); - expect(result.error).toBeUndefined(); + const result = await runCommand(`migrate sync-rules --input-file ${inputFile} --output-file ${outputFile}`, { + root + }); + expect(result.error).toBeUndefined(); - const transformed = readFileSync(outputFile).toString('utf-8'); - expect(transformed) - .toStrictEqual(`# Adds YAML Schema support for VSCode users with the YAML extension installed. This enables features like validation and autocompletion based on the provided schema. + const transformed = readFileSync(outputFile).toString('utf8'); + expect(transformed) + .toStrictEqual(`# Adds YAML Schema support for VSCode users with the YAML extension installed. This enables features like validation and autocompletion based on the provided schema. # yaml-language-server: $schema=https://unpkg.com/@powersync/service-sync-rules@latest/schema/sync_rules.json config: edition: 3 @@ -41,4 +44,5 @@ streams: queries: - SELECT * FROM lists WHERE owner_id = auth.user_id() `); + }); }); diff --git a/packages/cli-core/src/command-types/CloudInstanceCommand.ts b/packages/cli-core/src/command-types/CloudInstanceCommand.ts index ca728dd..4acf20c 100644 --- a/packages/cli-core/src/command-types/CloudInstanceCommand.ts +++ b/packages/cli-core/src/command-types/CloudInstanceCommand.ts @@ -9,12 +9,12 @@ import { PowerSyncManagementClient } from '@powersync/management-client'; import { existsSync } from 'node:fs'; import { join } from 'node:path'; -import { getDefaultOrgId } from '../clients/AccountsHubClientSDKClient.js'; import { createCloudClient } from '../clients/create-cloud-client.js'; import { ensureServiceTypeMatches, ServiceType } from '../utils/ensure-service-type.js'; import { env } from '../utils/env.js'; import { OBJECT_ID_REGEX } from '../utils/object-id.js'; import { CLI_FILENAME, SERVICE_FILENAME } from '../utils/project-config.js'; +import { resolveCloudInstanceLink } from '../utils/resolve-cloud-instance-link.js'; import { resolveSyncRulesContent } from '../utils/resolve-sync-rules-content.js'; import { parseYamlFile } from '../utils/yaml.js'; import { CommandHelpGroup, HelpGroup } from './HelpGroup.js'; @@ -41,36 +41,35 @@ export type CloudInstanceCommandFlags = Interfaces.InferredFlags< * 1. Command-line flags (--instance-id, --org-id, --project-id) * 2. Linked config from cli.yaml * 3. Environment variables (INSTANCE_ID, ORG_ID, PROJECT_ID) - * 4. If org_id is still missing: token's single org (via accounts API); error if multiple orgs. + * 4. If org_id or project_id is still missing: resolve it from the Cloud instance. * * @example * # Use linked project (cli.yaml) * pnpm exec powersync some-cloud-cmd * # Override with env - * INSTANCE_ID=... ORG_ID=... PROJECT_ID=... pnpm exec powersync some-cloud-cmd + * INSTANCE_ID=... pnpm exec powersync some-cloud-cmd * # Override with flags - * pnpm exec powersync some-cloud-cmd --instance-id=... --org-id=... --project-id=... + * pnpm exec powersync some-cloud-cmd --instance-id=... */ export abstract class CloudInstanceCommand extends InstanceCommand { static baseFlags = { /** * Instance ID, org ID, and project ID are resolved in order: flags → cli.yaml → env (INSTANCE_ID, ORG_ID, PROJECT_ID). + * Missing org/project IDs are resolved from the Cloud instance. */ ...InstanceCommand.baseFlags, 'instance-id': Flags.string({ - dependsOn: ['project-id'], description: 'PowerSync Cloud instance ID. Manually passed if the current context has not been linked.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'org-id': Flags.string({ - description: - 'Organization ID (optional). Defaults to the token’s single org when only one is available; pass explicitly if the token has multiple orgs.', + description: 'Organization ID (optional). Resolved from the Cloud instance when omitted.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'project-id': Flags.string({ - description: 'Project ID. Manually passed if the current context has not been linked.', + description: 'Project ID (optional). Resolved from the Cloud instance when omitted.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }) @@ -157,19 +156,7 @@ export abstract class CloudInstanceCommand extends InstanceCommand { const instance_id = flags['instance-id'] ?? (rawLink?.instance_id as string | undefined) ?? env.INSTANCE_ID; const project_id = flags['project-id'] ?? (rawLink?.project_id as string | undefined) ?? env.PROJECT_ID; - let org_id = flags['org-id'] ?? (rawLink?.org_id as string | undefined) ?? env.ORG_ID; - - try { - if (org_id == null && instance_id != null) { - org_id = await getDefaultOrgId(); - } - } catch (error) { - this.styledError({ - error, - message: - 'Linking is required before using this command. Provide flags, link the project (cli.yaml), or set environment variables.' - }); - } + const org_id = flags['org-id'] ?? (rawLink?.org_id as string | undefined) ?? env.ORG_ID; if (instance_id != null || project_id != null || org_id != null) { this.ensureObjectIdIfPresent(instance_id, '--instance-id'); @@ -177,17 +164,19 @@ export abstract class CloudInstanceCommand extends InstanceCommand { this.ensureObjectIdIfPresent(project_id, '--project-id'); try { - linked = ResolvedCloudCLIConfig.decode({ - instance_id: instance_id!, - org_id: org_id!, - project_id: project_id!, - type: 'cloud' - }); + linked = ResolvedCloudCLIConfig.decode( + await resolveCloudInstanceLink({ + client: this.client, + instanceId: instance_id, + orgId: org_id, + projectId: project_id + }) + ); } catch (error) { this.styledError({ error, message: - 'Linking is required before using this command. Provide flags, link the project (cli.yaml), or set environment variables.' + 'Linking is required before using this command. Provide --instance-id, link the project (cli.yaml), or set environment variables.' }); } } diff --git a/packages/cli-core/src/command-types/SharedInstanceCommand.ts b/packages/cli-core/src/command-types/SharedInstanceCommand.ts index 68b8e88..1973d6e 100644 --- a/packages/cli-core/src/command-types/SharedInstanceCommand.ts +++ b/packages/cli-core/src/command-types/SharedInstanceCommand.ts @@ -16,11 +16,11 @@ import { PowerSyncManagementClient } from '@powersync/management-client'; import { existsSync } from 'node:fs'; import { join } from 'node:path'; -import { getDefaultOrgId } from '../clients/AccountsHubClientSDKClient.js'; import { createCloudClient } from '../clients/create-cloud-client.js'; import { ensureServiceTypeMatches, ServiceType } from '../utils/ensure-service-type.js'; import { env } from '../utils/env.js'; import { CLI_FILENAME, SERVICE_FILENAME } from '../utils/project-config.js'; +import { resolveCloudInstanceLink } from '../utils/resolve-cloud-instance-link.js'; import { resolveSyncRulesContent } from '../utils/resolve-sync-rules-content.js'; import { parseYamlFile } from '../utils/yaml.js'; import { CloudProject } from './CloudInstanceCommand.js'; @@ -50,11 +50,11 @@ export type SharedInstanceCommandFlags = Interfaces.InferredFlags< * # Use linked project (cli.yaml determines cloud vs self-hosted) * pnpm exec powersync some-shared-cmd * # Force cloud with env - * INSTANCE_ID=... ORG_ID=... PROJECT_ID=... pnpm exec powersync some-shared-cmd + * INSTANCE_ID=... pnpm exec powersync some-shared-cmd * # Force self-hosted with flag * pnpm exec powersync some-shared-cmd --api-url=https://... * # Force cloud with flags - * pnpm exec powersync some-shared-cmd --instance-id=... --org-id=... --project-id=... + * pnpm exec powersync some-shared-cmd --instance-id=... */ export abstract class SharedInstanceCommand extends InstanceCommand { static baseFlags = { @@ -67,20 +67,18 @@ export abstract class SharedInstanceCommand extends InstanceCommand { required: false }), 'instance-id': Flags.string({ - dependsOn: ['project-id'], description: '[Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'org-id': Flags.string({ - description: - '[Cloud] Organization ID (optional). Defaults to the token’s single org when only one is available; pass explicitly if the token has multiple orgs. Resolved: flag → cli.yaml → ORG_ID.', + description: '[Cloud] Organization ID (optional). Resolved from the Cloud instance when omitted.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'project-id': Flags.string({ - description: '[Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID.', + description: '[Cloud] Project ID (optional). Resolved from the Cloud instance when omitted.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), @@ -166,7 +164,7 @@ export abstract class SharedInstanceCommand extends InstanceCommand { const linkMissingErrorMessage = [ 'Linking is required before using this command.', - 'Provide --api-url (self-hosted) or --instance-id with --org-id and --project-id (cloud), or link the project first.' + 'Provide --api-url (self-hosted) or --instance-id (cloud), or link the project first.' ].join('\n'); // If we don't have a project type by now, we need to error @@ -190,17 +188,14 @@ export abstract class SharedInstanceCommand extends InstanceCommand { } else { const _rawCloudCLIConfig = (rawCLIConfig as CloudCLIConfig) ?? { type: 'cloud' }; try { - let org_id = flags['org-id'] ?? _rawCloudCLIConfig.org_id ?? env.ORG_ID; - if (org_id == null && (flags['instance-id'] || env.INSTANCE_ID)) { - org_id = await getDefaultOrgId(); - } - - cliConfig = ResolvedCloudCLIConfig.decode({ - ..._rawCloudCLIConfig, - instance_id: flags['instance-id'] ?? _rawCloudCLIConfig.instance_id! ?? env.INSTANCE_ID, - org_id: org_id!, - project_id: flags['project-id'] ?? _rawCloudCLIConfig.project_id! ?? env.PROJECT_ID - }); + cliConfig = ResolvedCloudCLIConfig.decode( + await resolveCloudInstanceLink({ + client: this.cloudClient, + instanceId: flags['instance-id'] ?? _rawCloudCLIConfig.instance_id ?? env.INSTANCE_ID, + orgId: flags['org-id'] ?? _rawCloudCLIConfig.org_id ?? env.ORG_ID, + projectId: flags['project-id'] ?? _rawCloudCLIConfig.project_id ?? env.PROJECT_ID + }) + ); } catch (error) { this.styledError({ error, message: linkMissingErrorMessage }); } diff --git a/packages/cli-core/src/index.ts b/packages/cli-core/src/index.ts index e2ef8c1..0a9034d 100644 --- a/packages/cli-core/src/index.ts +++ b/packages/cli-core/src/index.ts @@ -23,6 +23,7 @@ export * from './utils/ensure-service-type.js'; export * from './utils/env.js'; export * from './utils/object-id.js'; export * from './utils/project-config.js'; +export * from './utils/resolve-cloud-instance-link.js'; export * from './utils/resolve-sync-rules-content.js'; export * from './utils/sync-config-file-path-flags.js'; export * from './utils/yaml.js'; diff --git a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts new file mode 100644 index 0000000..a43fc9f --- /dev/null +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -0,0 +1,77 @@ +import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; +import type { PowerSyncManagementClient } from '@powersync/management-client'; + +import { OBJECT_ID_REGEX } from './object-id.js'; + +type CloudInstanceMetadata = { + app_id: string; + id: string; + org_id: string; +}; + +type CloudInstanceResolverClient = PowerSyncManagementClient & { + getInstance(input: { id: string }): Promise; +}; + +export type ResolveCloudInstanceLinkInput = { + client: PowerSyncManagementClient; + instanceId?: string; + orgId?: string; + projectId?: string; +}; + +function ensureObjectId(value: string | undefined, label: '--instance-id' | '--org-id' | '--project-id'): void { + if (value == null) { + return; + } + + if (!OBJECT_ID_REGEX.test(value)) { + throw new Error(`Invalid ${label} "${value}". Expected a BSON ObjectID (24 hex characters).`); + } +} + +/** + * Resolves the full Cloud link from an instance ID. If org/project IDs are missing, fetches them from the instance. + */ +export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkInput): Promise { + const { client, instanceId, orgId, projectId } = input; + + ensureObjectId(instanceId, '--instance-id'); + ensureObjectId(orgId, '--org-id'); + ensureObjectId(projectId, '--project-id'); + + if (!instanceId) { + throw new Error('Cloud instance resolution requires an instance ID.'); + } + + if (orgId && projectId) { + return { + instance_id: instanceId, + org_id: orgId, + project_id: projectId, + type: 'cloud' + }; + } + + let instance: CloudInstanceMetadata; + try { + instance = await (client as CloudInstanceResolverClient).getInstance({ id: instanceId }); + } catch { + throw new Error(`Instance ${instanceId} was not found or is not accessible with the current token.`); + } + + if (orgId && orgId !== instance.org_id) { + throw new Error(`Instance ${instanceId} belongs to organization ${instance.org_id}, not ${orgId}.`); + } + + if (projectId && projectId !== instance.app_id) { + throw new Error(`Instance ${instanceId} belongs to project ${instance.app_id}, not ${projectId}.`); + } + + return { + instance_id: instance.id, + org_id: instance.org_id, + project_id: instance.app_id, + type: 'cloud' + }; +} From 002b85fc7b648f0a5e2c135abdafdccff46ea1ca Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 6 May 2026 15:10:27 +0200 Subject: [PATCH 2/5] Split validateCloudLinkConfig and merge ensureObjectId duplicates --- .../api/cloud/validate-cloud-link-config.ts | 85 ++++++++----------- cli/src/commands/link/cloud.ts | 24 ++---- cli/src/commands/pull/instance.ts | 31 +++---- packages/cli-core/src/utils/object-id.ts | 6 ++ .../src/utils/resolve-cloud-instance-link.ts | 48 ++++------- 5 files changed, 81 insertions(+), 113 deletions(-) diff --git a/cli/src/api/cloud/validate-cloud-link-config.ts b/cli/src/api/cloud/validate-cloud-link-config.ts index dcbb149..87dc7f9 100644 --- a/cli/src/api/cloud/validate-cloud-link-config.ts +++ b/cli/src/api/cloud/validate-cloud-link-config.ts @@ -1,64 +1,30 @@ import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; -import { createAccountsHubClient, OBJECT_ID_REGEX, resolveCloudInstanceLink } from '@powersync/cli-core'; +import { createAccountsHubClient, ensureObjectId, resolveCloudInstanceLink } from '@powersync/cli-core'; import { PowerSyncManagementClient } from '@powersync/management-client'; type InstanceConfigResponse = Awaited>; -export type CloudLinkValidationInput = { - instanceId?: string; - orgId?: string; - projectId?: string; +export type ValidateCloudProjectOptions = { + cloudClient: PowerSyncManagementClient; + orgId: string; + projectId: string; }; -export type ValidateCloudLinkConfigOptions = { +export type FetchCloudInstanceConfigOptions = { cloudClient: PowerSyncManagementClient; - input: CloudLinkValidationInput; - validateInstance?: boolean; + instanceId?: string; + orgId?: string; + projectId?: string; }; -export type ValidateCloudLinkConfigResult = { - instanceConfig?: InstanceConfigResponse; - linked?: ResolvedCloudCLIConfig; +export type FetchCloudInstanceConfigResult = { + instanceConfig: InstanceConfigResponse; + linked: ResolvedCloudCLIConfig; }; -function ensureObjectId(value: string | undefined, flagName: '--instance-id' | '--org-id' | '--project-id') { - if (value == null) { - return; - } - - if (!OBJECT_ID_REGEX.test(value)) { - throw new Error(`Invalid ${flagName} "${value}". Expected a BSON ObjectID (24 hex characters).`); - } -} - -export async function validateCloudLinkConfig( - options: ValidateCloudLinkConfigOptions -): Promise { - const { cloudClient, input, validateInstance = false } = options; - const { instanceId, orgId, projectId } = input; - - if (validateInstance) { - const linked = await resolveCloudInstanceLink({ client: cloudClient, instanceId, orgId, projectId }); - let instanceConfig: InstanceConfigResponse; - try { - instanceConfig = await cloudClient.getInstanceConfig({ - app_id: linked.project_id, - id: linked.instance_id, - org_id: linked.org_id - }); - } catch { - throw new Error( - `Instance ${linked.instance_id} was not found in project ${linked.project_id} in organization ${linked.org_id}, or is not accessible with the current token.` - ); - } - - return { instanceConfig, linked }; - } - - if (!orgId || !projectId) { - throw new Error('Project validation requires both an organization ID and a project ID.'); - } +export async function validateCloudProject(options: ValidateCloudProjectOptions): Promise { + const { orgId, projectId } = options; ensureObjectId(orgId, '--org-id'); ensureObjectId(projectId, '--project-id'); @@ -85,6 +51,27 @@ export async function validateCloudLinkConfig( `Project ${projectId} was not found in organization ${orgId}, or is not accessible with the current token.` ); } +} + +export async function fetchCloudInstanceConfig( + options: FetchCloudInstanceConfigOptions +): Promise { + const { cloudClient, instanceId, orgId, projectId } = options; + + const linked = await resolveCloudInstanceLink({ client: cloudClient, instanceId, orgId, projectId }); + + let instanceConfig: InstanceConfigResponse; + try { + instanceConfig = await cloudClient.getInstanceConfig({ + app_id: linked.project_id, + id: linked.instance_id, + org_id: linked.org_id + }); + } catch { + throw new Error( + `Instance ${linked.instance_id} was not found in project ${linked.project_id} in organization ${linked.org_id}, or is not accessible with the current token.` + ); + } - return {}; + return { instanceConfig, linked }; } diff --git a/cli/src/commands/link/cloud.ts b/cli/src/commands/link/cloud.ts index 3c76f7a..ba6103c 100644 --- a/cli/src/commands/link/cloud.ts +++ b/cli/src/commands/link/cloud.ts @@ -12,7 +12,7 @@ import { } from '@powersync/cli-core'; import { createCloudInstance } from '../../api/cloud/create-cloud-instance.js'; -import { validateCloudLinkConfig } from '../../api/cloud/validate-cloud-link-config.js'; +import { fetchCloudInstanceConfig, validateCloudProject } from '../../api/cloud/validate-cloud-link-config.js'; import { writeCloudLink } from '../../api/cloud/write-cloud-link.js'; export default class LinkCloud extends CloudInstanceCommand { @@ -71,15 +71,8 @@ export default class LinkCloud extends CloudInstanceCommand { orgId = await getDefaultOrgId(); } - const createOrgId = orgId!; - const createProjectId = projectId!; - try { - await validateCloudLinkConfig({ - cloudClient: this.client, - input: { orgId: createOrgId, projectId: createProjectId }, - validateInstance: false - }); + await validateCloudProject({ cloudClient: this.client, orgId: orgId!, projectId: projectId! }); } catch (error) { this.styledError({ message: error instanceof Error ? error.message : String(error) }); } @@ -91,8 +84,8 @@ export default class LinkCloud extends CloudInstanceCommand { try { const result = await createCloudInstance(client, { name: config.name, - orgId: createOrgId, - projectId: createProjectId, + orgId: orgId!, + projectId: projectId!, region: config.region }); newInstanceId = result.instanceId; @@ -107,7 +100,7 @@ export default class LinkCloud extends CloudInstanceCommand { expectedType: ServiceType.CLOUD, projectDir: projectDirectory }); - writeCloudLink(projectDirectory, { instanceId: newInstanceId, orgId: createOrgId, projectId: createProjectId }); + writeCloudLink(projectDirectory, { instanceId: newInstanceId, orgId: orgId!, projectId: projectId! }); this.log( ux.colorize('green', `Created Cloud instance ${newInstanceId} and updated ${directory}/${CLI_FILENAME}.`) ); @@ -123,10 +116,11 @@ export default class LinkCloud extends CloudInstanceCommand { let linked: ResolvedCloudCLIConfig | undefined; try { - const validationResult = await validateCloudLinkConfig({ + const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - input: { instanceId, orgId, projectId }, - validateInstance: true + instanceId, + orgId, + projectId }); linked = validationResult.linked; } catch (error) { diff --git a/cli/src/commands/pull/instance.ts b/cli/src/commands/pull/instance.ts index 1ddaa56..efe3d57 100644 --- a/cli/src/commands/pull/instance.ts +++ b/cli/src/commands/pull/instance.ts @@ -19,7 +19,7 @@ import { join } from 'node:path'; import { buildServiceYaml } from '../../api/build-service-yaml.js'; import { CLOUD_SERVICE_TEMPLATE_PATH, writeCloudSyncConfigFile } from '../../api/cloud/create-cloud-template.js'; import { decodeFetchedCloudConfig } from '../../api/cloud/fetch-cloud-config.js'; -import { validateCloudLinkConfig } from '../../api/cloud/validate-cloud-link-config.js'; +import { fetchCloudInstanceConfig } from '../../api/cloud/validate-cloud-link-config.js'; import { writeCloudLink } from '../../api/cloud/write-cloud-link.js'; const SERVICE_FETCHED_FILENAME = 'service-fetched.yaml'; @@ -56,9 +56,6 @@ export default class PullInstance extends CloudInstanceCommand { let resolvedLink: ResolvedCloudCLIConfig | undefined; let instanceConfig; - /** - * The pull instance command can be used to create a new powersync project directory - */ const projectDir = this.resolveProjectDir(flags); if (!existsSync(projectDir)) { if (!inputInstanceId) { @@ -68,10 +65,11 @@ export default class PullInstance extends CloudInstanceCommand { } try { - const validationResult = await validateCloudLinkConfig({ + const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - input: { instanceId: inputInstanceId, orgId: inputOrgId, projectId: inputProjectId }, - validateInstance: true + instanceId: inputInstanceId, + orgId: inputOrgId, + projectId: inputProjectId }); resolvedLink = validationResult.linked; instanceConfig = validationResult.instanceConfig; @@ -100,10 +98,11 @@ export default class PullInstance extends CloudInstanceCommand { } try { - const validationResult = await validateCloudLinkConfig({ + const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - input: { instanceId: inputInstanceId, orgId: inputOrgId, projectId: inputProjectId }, - validateInstance: true + instanceId: inputInstanceId, + orgId: inputOrgId, + projectId: inputProjectId }); resolvedLink = validationResult.linked; instanceConfig = validationResult.instanceConfig; @@ -130,14 +129,11 @@ export default class PullInstance extends CloudInstanceCommand { if (!instanceConfig) { try { - const validationResult = await validateCloudLinkConfig({ + const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - input: { - instanceId: linked.instance_id, - orgId: linked.org_id, - projectId: linked.project_id - }, - validateInstance: true + instanceId: linked.instance_id, + orgId: linked.org_id, + projectId: linked.project_id }); instanceConfig = validationResult.instanceConfig; } catch (error) { @@ -191,7 +187,6 @@ export default class PullInstance extends CloudInstanceCommand { writeFileSync(syncOutputPath, YAML_SYNC_RULES_SCHEMA + '\n' + fetched.syncRules, 'utf8'); this.log(`Wrote ${ux.colorize('blue', syncOutputName)} with sync config from the cloud.`); } else if (!fetched.syncRules && !syncExists) { - // If there is no sync config in the cloud and no existing sync config locally, we should still create an empty sync-config.yaml with the correct header and schema reference await writeCloudSyncConfigFile({ targetDir: projectDir }); this.log( `Wrote ${ux.colorize('blue', SYNC_FILENAME)} with template sync config (no sync config found in the cloud).` diff --git a/packages/cli-core/src/utils/object-id.ts b/packages/cli-core/src/utils/object-id.ts index 9a30632..c38b9cb 100644 --- a/packages/cli-core/src/utils/object-id.ts +++ b/packages/cli-core/src/utils/object-id.ts @@ -1 +1,7 @@ export const OBJECT_ID_REGEX = /^[0-9a-fA-F]{24}$/; + +export function ensureObjectId(value: string, label: string): void { + if (!OBJECT_ID_REGEX.test(value)) { + throw new Error(`Invalid ${label} "${value}". Expected a BSON ObjectID (24 hex characters).`); + } +} diff --git a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts index a43fc9f..f5a9a69 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -1,17 +1,7 @@ import type { ResolvedCloudCLIConfig } from '@powersync/cli-schemas'; import type { PowerSyncManagementClient } from '@powersync/management-client'; -import { OBJECT_ID_REGEX } from './object-id.js'; - -type CloudInstanceMetadata = { - app_id: string; - id: string; - org_id: string; -}; - -type CloudInstanceResolverClient = PowerSyncManagementClient & { - getInstance(input: { id: string }): Promise; -}; +import { ensureObjectId } from './object-id.js'; export type ResolveCloudInstanceLinkInput = { client: PowerSyncManagementClient; @@ -20,31 +10,21 @@ export type ResolveCloudInstanceLinkInput = { projectId?: string; }; -function ensureObjectId(value: string | undefined, label: '--instance-id' | '--org-id' | '--project-id'): void { - if (value == null) { - return; - } - - if (!OBJECT_ID_REGEX.test(value)) { - throw new Error(`Invalid ${label} "${value}". Expected a BSON ObjectID (24 hex characters).`); - } -} - /** * Resolves the full Cloud link from an instance ID. If org/project IDs are missing, fetches them from the instance. */ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkInput): Promise { const { client, instanceId, orgId, projectId } = input; - ensureObjectId(instanceId, '--instance-id'); - ensureObjectId(orgId, '--org-id'); - ensureObjectId(projectId, '--project-id'); - if (!instanceId) { throw new Error('Cloud instance resolution requires an instance ID.'); } + ensureObjectId(instanceId, '--instance-id'); + if (orgId && projectId) { + ensureObjectId(orgId, '--org-id'); + ensureObjectId(projectId, '--project-id'); return { instance_id: instanceId, org_id: orgId, @@ -53,19 +33,25 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn }; } - let instance: CloudInstanceMetadata; + let instance; try { - instance = await (client as CloudInstanceResolverClient).getInstance({ id: instanceId }); + instance = await client.getInstance({ id: instanceId }); } catch { throw new Error(`Instance ${instanceId} was not found or is not accessible with the current token.`); } - if (orgId && orgId !== instance.org_id) { - throw new Error(`Instance ${instanceId} belongs to organization ${instance.org_id}, not ${orgId}.`); + if (orgId) { + ensureObjectId(orgId, '--org-id'); + if (orgId !== instance.org_id) { + throw new Error(`Instance ${instanceId} belongs to organization ${instance.org_id}, not ${orgId}.`); + } } - if (projectId && projectId !== instance.app_id) { - throw new Error(`Instance ${instanceId} belongs to project ${instance.app_id}, not ${projectId}.`); + if (projectId) { + ensureObjectId(projectId, '--project-id'); + if (projectId !== instance.app_id) { + throw new Error(`Instance ${instanceId} belongs to project ${instance.app_id}, not ${projectId}.`); + } } return { From 12d009d368b0658bf3d8abc592cea23df1202c06 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 6 May 2026 16:00:22 +0200 Subject: [PATCH 3/5] Tests --- cli/src/commands/link/cloud.ts | 1 + cli/test/setup.ts | 6 + .../utils/resolve-cloud-instance-link.test.ts | 128 ++++++++++++++++++ .../src/utils/resolve-cloud-instance-link.ts | 18 ++- 4 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 cli/test/utils/resolve-cloud-instance-link.test.ts diff --git a/cli/src/commands/link/cloud.ts b/cli/src/commands/link/cloud.ts index ba6103c..a1def68 100644 --- a/cli/src/commands/link/cloud.ts +++ b/cli/src/commands/link/cloud.ts @@ -22,6 +22,7 @@ export default class LinkCloud extends CloudInstanceCommand { static examples = [ '<%= config.bin %> <%= command.id %> --instance-id=', '<%= config.bin %> <%= command.id %> --create --project-id=', + '<%= config.bin %> <%= command.id %> --create --project-id= --org-id=', '<%= config.bin %> <%= command.id %> --instance-id= --project-id= --org-id=' ]; static flags = { diff --git a/cli/test/setup.ts b/cli/test/setup.ts index b32d818..58cd101 100644 --- a/cli/test/setup.ts +++ b/cli/test/setup.ts @@ -38,6 +38,7 @@ export const managementClientMock = { deactivateInstance: vi.fn(), deployInstance: vi.fn(), destroyInstance: vi.fn(), + getInstance: vi.fn(), getInstanceConfig: vi.fn(), getInstanceStatus: vi.fn(), listRegions: vi.fn(), @@ -51,6 +52,11 @@ export function resetManagementClientMocks(): void { } managementClientMock.createInstance.mockResolvedValue({ id: MOCK_CLOUD_IDS.instanceId }); + managementClientMock.getInstance.mockResolvedValue({ + app_id: MOCK_CLOUD_IDS.projectId, + id: MOCK_CLOUD_IDS.instanceId, + org_id: MOCK_CLOUD_IDS.orgId + }); managementClientMock.destroyInstance.mockRejectedValue(new Error('mock destroy failure')); managementClientMock.deactivateInstance.mockRejectedValue(new Error('mock deactivate failure')); managementClientMock.deployInstance.mockRejectedValue(new Error('mock deploy failure')); diff --git a/cli/test/utils/resolve-cloud-instance-link.test.ts b/cli/test/utils/resolve-cloud-instance-link.test.ts new file mode 100644 index 0000000..8dc6d08 --- /dev/null +++ b/cli/test/utils/resolve-cloud-instance-link.test.ts @@ -0,0 +1,128 @@ +import type { PowerSyncManagementClient } from '@powersync/management-client'; + +import { resolveCloudInstanceLink } from '@powersync/cli-core'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { managementClientMock, MOCK_CLOUD_IDS, resetManagementClientMocks } from '../setup.js'; + +const { instanceId: INSTANCE_ID, orgId: ORG_ID, projectId: PROJECT_ID } = MOCK_CLOUD_IDS; +const OTHER_ORG_ID = '4ffabc821ea10f9b2a000002'; +const OTHER_PROJECT_ID = '699ef9c371c56d0007320544'; + +const mockClient = managementClientMock as unknown as PowerSyncManagementClient; + +describe('resolveCloudInstanceLink', () => { + beforeEach(() => { + resetManagementClientMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('throws when no instanceId is provided', async () => { + await expect(resolveCloudInstanceLink({ client: mockClient })).rejects.toThrow( + 'Cloud instance resolution requires an instance ID.' + ); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + it('throws when instanceId has an invalid format', async () => { + await expect(resolveCloudInstanceLink({ client: mockClient, instanceId: 'not-a-valid-id' })).rejects.toThrow( + 'Invalid --instance-id' + ); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + describe('when both orgId and projectId are provided', () => { + it('returns the resolved link without an API call', async () => { + const result = await resolveCloudInstanceLink({ + client: mockClient, + instanceId: INSTANCE_ID, + orgId: ORG_ID, + projectId: PROJECT_ID + }); + expect(result).toEqual({ instance_id: INSTANCE_ID, org_id: ORG_ID, project_id: PROJECT_ID, type: 'cloud' }); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + it('throws when orgId has an invalid format', async () => { + await expect( + resolveCloudInstanceLink({ + client: mockClient, + instanceId: INSTANCE_ID, + orgId: 'bad-org', + projectId: PROJECT_ID + }) + ).rejects.toThrow('Invalid --org-id'); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + it('throws when projectId has an invalid format', async () => { + await expect( + resolveCloudInstanceLink({ + client: mockClient, + instanceId: INSTANCE_ID, + orgId: ORG_ID, + projectId: 'bad-project' + }) + ).rejects.toThrow('Invalid --project-id'); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + }); + + describe('when org or project IDs are missing (API lookup path)', () => { + it('throws when orgId has an invalid format before calling the API', async () => { + await expect( + resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID, orgId: 'bad-org' }) + ).rejects.toThrow('Invalid --org-id'); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + it('throws when projectId has an invalid format before calling the API', async () => { + await expect( + resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID, projectId: 'bad-project' }) + ).rejects.toThrow('Invalid --project-id'); + expect(managementClientMock.getInstance).not.toHaveBeenCalled(); + }); + + it('throws when the instance is not found', async () => { + managementClientMock.getInstance.mockRejectedValueOnce(new Error('not found')); + await expect(resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID })).rejects.toThrow( + `Instance ${INSTANCE_ID} was not found or is not accessible with the current token.` + ); + }); + + it('resolves all fields from the instance when only instanceId is provided', async () => { + const result = await resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID }); + expect(result).toEqual({ instance_id: INSTANCE_ID, org_id: ORG_ID, project_id: PROJECT_ID, type: 'cloud' }); + expect(managementClientMock.getInstance).toHaveBeenCalledWith({ id: INSTANCE_ID }); + }); + + it('throws when the provided orgId does not match the instance', async () => { + await expect( + resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID, orgId: OTHER_ORG_ID }) + ).rejects.toThrow(`Instance ${INSTANCE_ID} belongs to organization ${ORG_ID}, not ${OTHER_ORG_ID}.`); + }); + + it('throws when the provided projectId does not match the instance', async () => { + await expect( + resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID, projectId: OTHER_PROJECT_ID }) + ).rejects.toThrow(`Instance ${INSTANCE_ID} belongs to project ${PROJECT_ID}, not ${OTHER_PROJECT_ID}.`); + }); + + it('resolves correctly when instanceId and a matching orgId are provided', async () => { + const result = await resolveCloudInstanceLink({ client: mockClient, instanceId: INSTANCE_ID, orgId: ORG_ID }); + expect(result).toEqual({ instance_id: INSTANCE_ID, org_id: ORG_ID, project_id: PROJECT_ID, type: 'cloud' }); + }); + + it('resolves correctly when instanceId and a matching projectId are provided', async () => { + const result = await resolveCloudInstanceLink({ + client: mockClient, + instanceId: INSTANCE_ID, + projectId: PROJECT_ID + }); + expect(result).toEqual({ instance_id: INSTANCE_ID, org_id: ORG_ID, project_id: PROJECT_ID, type: 'cloud' }); + }); + }); +}); diff --git a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts index f5a9a69..55e656a 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -33,6 +33,10 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn }; } + // Fail fast on bad IDs + if (orgId) ensureObjectId(orgId, '--org-id'); + if (projectId) ensureObjectId(projectId, '--project-id'); + let instance; try { instance = await client.getInstance({ id: instanceId }); @@ -40,18 +44,12 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn throw new Error(`Instance ${instanceId} was not found or is not accessible with the current token.`); } - if (orgId) { - ensureObjectId(orgId, '--org-id'); - if (orgId !== instance.org_id) { - throw new Error(`Instance ${instanceId} belongs to organization ${instance.org_id}, not ${orgId}.`); - } + if (orgId && orgId !== instance.org_id) { + throw new Error(`Instance ${instanceId} belongs to organization ${instance.org_id}, not ${orgId}.`); } - if (projectId) { - ensureObjectId(projectId, '--project-id'); - if (projectId !== instance.app_id) { - throw new Error(`Instance ${instanceId} belongs to project ${instance.app_id}, not ${projectId}.`); - } + if (projectId && projectId !== instance.app_id) { + throw new Error(`Instance ${instanceId} belongs to project ${instance.app_id}, not ${projectId}.`); } return { From a10067850cd9eb6f331457ec4920cdbc8de076ba Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Mon, 25 May 2026 13:20:13 +0200 Subject: [PATCH 4/5] Update @powersync package versions, update tests --- cli/test/commands/link.test.ts | 6 +- cli/test/commands/pull/instance.test.ts | 4 +- pnpm-lock.yaml | 556 +++--------------------- pnpm-workspace.yaml | 4 +- 4 files changed, 66 insertions(+), 504 deletions(-) diff --git a/cli/test/commands/link.test.ts b/cli/test/commands/link.test.ts index 4429c05..16b452d 100644 --- a/cli/test/commands/link.test.ts +++ b/cli/test/commands/link.test.ts @@ -224,6 +224,7 @@ type: cloud it('errors when project does not exist in the organization', async () => { accountsClientMock.listProjects.mockResolvedValueOnce({ objects: [], total: 0 }); + managementClientMock.getInstanceConfig.mockRejectedValueOnce(new Error('not found')); const { error } = await runLinkCloudDirect([ `--instance-id=${INSTANCE_ID}`, @@ -231,8 +232,9 @@ type: cloud `--project-id=${PROJECT_ID}` ]); - expect(error?.message).toContain(`Project ${PROJECT_ID} was not found in organization ${ORG_ID}`); - expect(error?.message).not.toContain(', ::'); + expect(error?.message).toContain( + `Instance ${INSTANCE_ID} was not found in project ${PROJECT_ID} in organization ${ORG_ID}` + ); }); it('errors when instance does not exist and --create is not used', async () => { diff --git a/cli/test/commands/pull/instance.test.ts b/cli/test/commands/pull/instance.test.ts index 26d8629..65b697b 100644 --- a/cli/test/commands/pull/instance.test.ts +++ b/cli/test/commands/pull/instance.test.ts @@ -200,13 +200,13 @@ describe('pull instance', () => { it('errors when organization does not exist', async () => { accountsClientMock.getOrganization.mockRejectedValueOnce(new Error('not found')); const result = await runPullInstanceDirect(); - expect(result.error?.message).toContain(`Organization ${ORG_ID} was not found or is not accessible`); + expect(result.error?.message).toMatch(/Instance .* was not found in project .* in organization .*/); }); it('errors when project does not exist in the organization', async () => { accountsClientMock.listProjects.mockResolvedValueOnce({ objects: [], total: 0 }); const result = await runPullInstanceDirect(); - expect(result.error?.message).toContain(`Project ${PROJECT_ID} was not found in organization ${ORG_ID}`); + expect(result.error?.message).toMatch(/Instance .* was not found in project .* in organization .*/); }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bd6b85..7b13277 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,11 +7,11 @@ settings: catalogs: default: '@powersync/management-client': - specifier: ^0.0.6 - version: 0.0.6 + specifier: ^0.1.0 + version: 0.1.0 '@powersync/management-types': - specifier: ^0.0.5 - version: 0.0.5 + specifier: ^0.1.0 + version: 0.1.0 '@powersync/service-client': specifier: ^0.0.3 version: 0.0.3 @@ -102,10 +102,10 @@ importers: version: link:../packages/schemas '@powersync/management-client': specifier: 'catalog:' - version: 0.0.6(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 0.1.0(@types/json-schema@7.0.15) '@powersync/management-types': specifier: 'catalog:' - version: 0.0.5 + version: 0.1.0 '@powersync/service-sync-rules': specifier: 'catalog:' version: 0.34.0 @@ -187,10 +187,10 @@ importers: version: link:../schemas '@powersync/management-client': specifier: 'catalog:' - version: 0.0.6(@types/debug@4.1.12)(@types/json-schema@7.0.15)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) + version: 0.1.0(@types/json-schema@7.0.15) '@powersync/management-types': specifier: 'catalog:' - version: 0.0.5 + version: 0.1.0 '@powersync/service-client': specifier: 'catalog:' version: 0.0.3(@types/debug@4.1.12)(@types/json-schema@7.0.15)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) @@ -339,7 +339,7 @@ importers: dependencies: '@powersync/management-types': specifier: 'catalog:' - version: 0.0.5 + version: 0.1.0 '@powersync/service-schema': specifier: 'catalog:' version: 1.20.2 @@ -1448,27 +1448,33 @@ packages: '@journeyapps-labs/common-sdk@1.0.2': resolution: {integrity: sha512-bPFrrSGdVnaVFe6ymc/desjZk02gQ8f0EKggrU1/BQIeFJvGeQ804iOJsIYmUVU1fv4qNmJD0mD0GUjxu93Z8g==} - '@journeyapps-labs/common-sdk@1.0.3': - resolution: {integrity: sha512-FjmLBhy6VjT+jrQc/uKKpHetFS+qW/s8rAZd4i+4T5Iv5kNs0kAkDBdK0ArmryMDvdrDmFZukkziGIv1Sgw3jA==} + '@journeyapps-labs/common-sdk@1.0.4': + resolution: {integrity: sha512-KNzIuYftc8+7qHGWkUnhJKfIBGx7Stbmk66vdhuCkZyIGKrmaqTQzbVDHUyBS/SO+2+K5T2W2QfSVT5zHij99A==} '@journeyapps-labs/common-utils@1.0.1': resolution: {integrity: sha512-9P1f++wHSveqMQIWulLdGnDgvMdEqXP+j8eIscDm2bAuiMCIbij856AQgdrG0mQPPhL5SlUNlSGrfdKfhul2QA==} - '@journeyapps-labs/micro-codecs@1.0.1': - resolution: {integrity: sha512-Iy2sElOvk6C9NpzLHi0M2CYmVHVSRYK0y+BP/wRzaJ45z/3OUuXdwp9TqCEfVlqDIdDafoia7ES6X7oBWEBODw==} - '@journeyapps-labs/micro-codecs@1.0.2': resolution: {integrity: sha512-RytuC7bFhSChlDYIxPQPEog9o6Qvb7+oMsjN72LI5A0w82HBshCy8/v+OHMEL8UiI8nsHk4KQQP9LBXbuDuBOg==} + '@journeyapps-labs/micro-codecs@1.0.3': + resolution: {integrity: sha512-n1Du6wzfrumzUpjSeAl54jHEJYt4Tb1w12L9zA6hiibvOJAhwEfetzmbFEdD8gnS1mQhcHXzSZFImlBRZe+prA==} + '@journeyapps-labs/micro-errors@1.0.1': resolution: {integrity: sha512-L7a6AC0mX+AWw1q7RfFYNRrfxWrMlXMnNnFPysHBuZFrtLh4bT/4cnGwXANlm5OBNQC2urhTmtpdOXZVlxC7yw==} '@journeyapps-labs/micro-schema@1.0.1': resolution: {integrity: sha512-laimOhtrByxRb52yi880nVkPAHfqPZ4AzefmG161fto7IPoyhnSEwdWkou4byWS8II1d4R62Jhz01+wBTMHzdQ==} + '@journeyapps-labs/micro-schema@1.0.3': + resolution: {integrity: sha512-3n8d64jgDc/XJwGD8QRarfZs+JqEmuIbT9hEyY8nZR0IKsBkZEe6G9QYKm0tur6tisKs0YIcDcWjfzYcxRfmag==} + '@journeyapps-labs/micro-streaming@1.0.1': resolution: {integrity: sha512-woWFNtPwKZwTz5NQ4RUecGOPXXO/W/KLkx8YOx9mvZ7XY3Lx1bDRpI1i6ZSBPeix+IBbl+bs394R0qlFliI1cg==} + '@journeyapps-labs/micro-streaming@1.0.2': + resolution: {integrity: sha512-CdgWC817TmbTof7IL6Oy0hCF1KCa6RXogh2uEbOYRNz+XwC1q3KQeChp38qQXvaZZtqdIsiMlZGiGGa638eTFw==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1605,11 +1611,11 @@ packages: resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} - '@powersync/management-client@0.0.6': - resolution: {integrity: sha512-s5tvk/3JUw9B1ry0X8YH30sMscqf5xkbGK0XNSZ4X6o0ATtJWxxe97Fgg1HoNTfN2W7lMSyTyZnBdf9jsVOluQ==} + '@powersync/management-client@0.1.0': + resolution: {integrity: sha512-YGJPNZHrSwUF3ofhz8VhbU1/QCF4KUKWGTyfzsDkmGJ9p8DESF5tZ1r0O7Nnmt1o2C0JOaQ0pV8z2pgnE1qb0g==} - '@powersync/management-types@0.0.5': - resolution: {integrity: sha512-sh1vRo1pq0D2oxijiyy0bHz1fIVkuiZkW+hTSyDSumNtbmT18ELe7+Mto29xlCzQ10oJ1YeyskUwpi+ZWzfuIQ==} + '@powersync/management-types@0.1.0': + resolution: {integrity: sha512-Ypv1UCu2rOlTKS8HjGl21aMd1NQnsG9isrs7OyIamjlMM1n2wLjbGuZNacu7Wzqc9CPeNZisUO4QXKyfuXa2kw==} '@powersync/service-client@0.0.3': resolution: {integrity: sha512-K83tOFdjRyFh76c4CKViRFyGJfBH9wSvtOvF2PxtbajAIPMzZEeunpnFj3vphx8CQyrDFWkR8/1Qa0BiCo30xQ==} @@ -1626,6 +1632,9 @@ packages: '@powersync/service-types@0.15.0': resolution: {integrity: sha512-wIdHCT+vhwoJAMZ414/dwG8fd51+fEWnPbwShkwNE5XTwA0UGOT8aGyVZqzT8PT0sVos5famYYqG203Mjb7q6g==} + '@powersync/service-types@0.15.2': + resolution: {integrity: sha512-GXQqCuTME+S1nV9CLbxtgYuAtMGFFLkDubm+DRTNGQqur8wBVzJp3haHCHFBVJ03Bhec+K7AIrB6CTii7b/HJg==} + '@powersync/sync-config-tools@0.1.1': resolution: {integrity: sha512-+lSmE6uGDUmUFc+XnYIatp746LMMLCw0DK+VyWnheuk4LRxENSqaZwLYySekWTDg9fBsXQJakAQ6sHHVMSTsbg==} @@ -6299,6 +6308,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -8434,123 +8444,29 @@ snapshots: - tsx - yaml - '@journeyapps-labs/common-sdk@1.0.3(@types/debug@4.1.12)(@types/json-schema@7.0.15)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': + '@journeyapps-labs/common-sdk@1.0.4(@types/json-schema@7.0.15)': dependencies: '@journeyapps-labs/micro-errors': 1.0.1 - '@journeyapps-labs/micro-streaming': 1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) + '@journeyapps-labs/micro-streaming': 1.0.2(@types/json-schema@7.0.15) '@types/node': 25.5.0 agentkeepalive: 4.6.0 bson: 7.2.0 lodash: 4.17.23 uuid: 13.0.0 transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - '@types/json-schema' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@journeyapps-labs/common-sdk@1.0.3(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)': - dependencies: - '@journeyapps-labs/micro-errors': 1.0.1 - '@journeyapps-labs/micro-streaming': 1.0.1(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@types/node': 25.5.0 - agentkeepalive: 4.6.0 - bson: 7.2.0 - lodash: 4.17.23 - uuid: 13.0.0 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@types/json-schema' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml '@journeyapps-labs/common-utils@1.0.1': dependencies: uuid: 13.0.0 - '@journeyapps-labs/micro-codecs@1.0.1(@types/debug@4.1.12)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': - dependencies: - '@types/node': 24.10.10 - bson: 6.10.4 - ts-codec: 1.3.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@journeyapps-labs/micro-codecs@1.0.1(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)': + '@journeyapps-labs/micro-codecs@1.0.2': dependencies: '@types/node': 24.10.10 bson: 6.10.4 ts-codec: 1.3.0 - vitest: 3.2.4(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@journeyapps-labs/micro-codecs@1.0.2': + '@journeyapps-labs/micro-codecs@1.0.3': dependencies: '@types/node': 24.10.10 bson: 6.10.4 @@ -8561,7 +8477,7 @@ snapshots: '@journeyapps-labs/micro-schema@1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': dependencies: '@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15) - '@journeyapps-labs/micro-codecs': 1.0.1(@types/debug@4.1.12)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) + '@journeyapps-labs/micro-codecs': 1.0.2 '@journeyapps-labs/micro-errors': 1.0.1 ajv: 8.17.1 better-ajv-errors: 2.0.3(ajv@8.17.1) @@ -8590,69 +8506,18 @@ snapshots: - tsx - yaml - '@journeyapps-labs/micro-schema@1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': - dependencies: - '@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15) - '@journeyapps-labs/micro-codecs': 1.0.1(@types/debug@4.1.12)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) - '@journeyapps-labs/micro-errors': 1.0.1 - ajv: 8.17.1 - better-ajv-errors: 2.0.3(ajv@8.17.1) - ts-codec: 1.3.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) - zod: 4.3.6 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@types/json-schema' - - '@types/node' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@journeyapps-labs/micro-schema@1.0.1(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)': + '@journeyapps-labs/micro-schema@1.0.3(@types/json-schema@7.0.15)': dependencies: '@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15) - '@journeyapps-labs/micro-codecs': 1.0.1(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + '@journeyapps-labs/micro-codecs': 1.0.3 '@journeyapps-labs/micro-errors': 1.0.1 ajv: 8.17.1 better-ajv-errors: 2.0.3(ajv@8.17.1) + bson: 6.10.4 ts-codec: 1.3.0 - vitest: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) zod: 4.3.6 transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - '@types/json-schema' - - '@types/node' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml '@journeyapps-labs/micro-streaming@1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': dependencies: @@ -8682,61 +8547,14 @@ snapshots: - tsx - yaml - '@journeyapps-labs/micro-streaming@1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': - dependencies: - '@journeyapps-labs/micro-errors': 1.0.1 - '@journeyapps-labs/micro-schema': 1.0.1(@types/debug@4.1.12)(@types/json-schema@7.0.15)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) - '@types/express': 5.0.6 - bson: 6.10.4 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@types/json-schema' - - '@types/node' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@journeyapps-labs/micro-streaming@1.0.1(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)': + '@journeyapps-labs/micro-streaming@1.0.2(@types/json-schema@7.0.15)': dependencies: '@journeyapps-labs/micro-errors': 1.0.1 - '@journeyapps-labs/micro-schema': 1.0.1(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + '@journeyapps-labs/micro-schema': 1.0.3(@types/json-schema@7.0.15) '@types/express': 5.0.6 bson: 6.10.4 transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - '@types/json-schema' - - '@types/node' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -8960,62 +8778,18 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@powersync/management-client@0.0.6(@types/debug@4.1.12)(@types/json-schema@7.0.15)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2)': + '@powersync/management-client@0.1.0(@types/json-schema@7.0.15)': dependencies: - '@journeyapps-labs/common-sdk': 1.0.3(@types/debug@4.1.12)(@types/json-schema@7.0.15)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2) - '@powersync/management-types': 0.0.5 + '@journeyapps-labs/common-sdk': 1.0.4(@types/json-schema@7.0.15) + '@powersync/management-types': 0.1.0 transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - '@types/json-schema' - - '@vitest/browser' - - '@vitest/ui' - babel-plugin-macros - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@powersync/management-client@0.0.6(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)': - dependencies: - '@journeyapps-labs/common-sdk': 1.0.3(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@powersync/management-types': 0.0.5 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@types/debug' - - '@types/json-schema' - - '@vitest/browser' - - '@vitest/ui' - - babel-plugin-macros - - happy-dom - - jiti - - jsdom - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@powersync/management-types@0.0.5': + '@powersync/management-types@0.1.0': dependencies: - '@journeyapps-labs/micro-codecs': 1.0.2 - '@powersync/service-types': 0.15.0 + '@journeyapps-labs/micro-codecs': 1.0.3 + '@powersync/service-types': 0.15.2 bson: 6.10.4 ts-codec: 1.3.0 transitivePeerDependencies: @@ -9070,6 +8844,14 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros + '@powersync/service-types@0.15.2': + dependencies: + dedent: 1.7.1 + ts-codec: 1.3.0 + uri-js: 4.4.1 + transitivePeerDependencies: + - babel-plugin-macros + '@powersync/sync-config-tools@0.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -10473,7 +10255,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/chai@5.2.3': dependencies: @@ -10482,7 +10264,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/debug@4.1.12': dependencies: @@ -10495,7 +10277,7 @@ snapshots: '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -10523,7 +10305,7 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/node@12.20.55': {} @@ -10564,12 +10346,12 @@ snapshots: '@types/send@1.2.1': dependencies: - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 24.10.10 + '@types/node': 22.19.7 '@types/wrap-ansi@3.0.0': {} @@ -10769,14 +10551,6 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.10.10)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 @@ -10785,14 +10559,6 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 @@ -14178,27 +13944,6 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@24.10.10)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: cac: 6.7.14 @@ -14220,48 +13965,6 @@ snapshots: - tsx - yaml - vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@8.1.1) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -14305,22 +14008,6 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.1 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.5.0 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.31.1 - tsx: 4.21.0 - yaml: 2.8.2 - vitefu@1.1.1(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) @@ -14411,133 +14098,6 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3(supports-color@8.1.1) - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 25.5.0 - jsdom: 27.4.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vitest@3.2.4(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3(supports-color@8.1.1) - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.10.10 - jsdom: 27.4.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vitest@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.10.10)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3(supports-color@8.1.1) - expect-type: 1.3.0 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.5.0 - jsdom: 27.4.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.10)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 739ac31..58995e0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,8 +4,8 @@ packages: - plugins/* catalog: - '@powersync/management-client': ^0.0.6 - '@powersync/management-types': ^0.0.5 + '@powersync/management-client': ^0.1.0 + '@powersync/management-types': ^0.1.0 '@powersync/service-client': ^0.0.3 '@powersync/service-schema': ^1.20.2 '@powersync/service-sync-rules': ^0.34.0 From 90d66dbdc3545f934248b3f9c32dd435c74ddeff Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Mon, 25 May 2026 13:28:44 +0200 Subject: [PATCH 5/5] Changeset --- .changeset/lazy-trees-judge.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/lazy-trees-judge.md diff --git a/.changeset/lazy-trees-judge.md b/.changeset/lazy-trees-judge.md new file mode 100644 index 0000000..7018de4 --- /dev/null +++ b/.changeset/lazy-trees-judge.md @@ -0,0 +1,6 @@ +--- +'@powersync/cli-core': minor +'powersync': minor +--- + +Resolve organization ID and project ID from instance ID automatically.