From d62de133ba2006e7f85dbb1383bfa5c5b4528992 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 14 Apr 2026 16:31:32 +0200 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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. From 17f3e9bdbf2d893932333da2690cddc7b3882d22 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 11:21:43 +0200 Subject: [PATCH 06/14] Deprecate --project-id and --org-id --- .../src/command-types/CloudInstanceCommand.ts | 12 ++++++++++-- .../src/command-types/SharedInstanceCommand.ts | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/cli-core/src/command-types/CloudInstanceCommand.ts b/packages/cli-core/src/command-types/CloudInstanceCommand.ts index 4acf20c..3aad951 100644 --- a/packages/cli-core/src/command-types/CloudInstanceCommand.ts +++ b/packages/cli-core/src/command-types/CloudInstanceCommand.ts @@ -64,12 +64,20 @@ export abstract class CloudInstanceCommand extends InstanceCommand { required: false }), 'org-id': Flags.string({ - description: 'Organization ID (optional). Resolved from the Cloud instance when omitted.', + deprecated: { + message: + '--org-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + }, + description: '[Deprecated] Organization ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'project-id': Flags.string({ - description: 'Project ID (optional). Resolved from the Cloud instance when omitted.', + deprecated: { + message: + '--project-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + }, + description: '[Deprecated] Project ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }) diff --git a/packages/cli-core/src/command-types/SharedInstanceCommand.ts b/packages/cli-core/src/command-types/SharedInstanceCommand.ts index 1973d6e..6c39711 100644 --- a/packages/cli-core/src/command-types/SharedInstanceCommand.ts +++ b/packages/cli-core/src/command-types/SharedInstanceCommand.ts @@ -73,12 +73,20 @@ export abstract class SharedInstanceCommand extends InstanceCommand { required: false }), 'org-id': Flags.string({ - description: '[Cloud] Organization ID (optional). Resolved from the Cloud instance when omitted.', + deprecated: { + message: + '--org-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + }, + description: '[Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), 'project-id': Flags.string({ - description: '[Cloud] Project ID (optional). Resolved from the Cloud instance when omitted.', + deprecated: { + message: + '--project-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + }, + description: '[Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, required: false }), From 2dfc65dad089678da2c554da738e7b2c995911c1 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 11:51:54 +0200 Subject: [PATCH 07/14] Make instanceId required, unify linkMissingErrorMessage --- .../api/cloud/validate-cloud-link-config.ts | 2 +- .../utils/resolve-cloud-instance-link.test.ts | 7 ------- .../src/command-types/CloudInstanceCommand.ts | 11 +++++----- .../command-types/SharedInstanceCommand.ts | 21 ++++++++++--------- packages/cli-core/src/utils/errors.ts | 7 +++++++ .../src/utils/resolve-cloud-instance-link.ts | 6 +----- 6 files changed, 26 insertions(+), 28 deletions(-) create mode 100644 packages/cli-core/src/utils/errors.ts diff --git a/cli/src/api/cloud/validate-cloud-link-config.ts b/cli/src/api/cloud/validate-cloud-link-config.ts index 87dc7f9..2e14e4b 100644 --- a/cli/src/api/cloud/validate-cloud-link-config.ts +++ b/cli/src/api/cloud/validate-cloud-link-config.ts @@ -13,7 +13,7 @@ export type ValidateCloudProjectOptions = { export type FetchCloudInstanceConfigOptions = { cloudClient: PowerSyncManagementClient; - instanceId?: string; + instanceId: string; orgId?: string; projectId?: string; }; diff --git a/cli/test/utils/resolve-cloud-instance-link.test.ts b/cli/test/utils/resolve-cloud-instance-link.test.ts index 8dc6d08..fc4702a 100644 --- a/cli/test/utils/resolve-cloud-instance-link.test.ts +++ b/cli/test/utils/resolve-cloud-instance-link.test.ts @@ -20,13 +20,6 @@ describe('resolveCloudInstanceLink', () => { 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' diff --git a/packages/cli-core/src/command-types/CloudInstanceCommand.ts b/packages/cli-core/src/command-types/CloudInstanceCommand.ts index 3aad951..5f26383 100644 --- a/packages/cli-core/src/command-types/CloudInstanceCommand.ts +++ b/packages/cli-core/src/command-types/CloudInstanceCommand.ts @@ -12,6 +12,7 @@ import { join } from 'node:path'; import { createCloudClient } from '../clients/create-cloud-client.js'; import { ensureServiceTypeMatches, ServiceType } from '../utils/ensure-service-type.js'; import { env } from '../utils/env.js'; +import { LINK_MISSING_ERROR_MESSAGE } from '../utils/errors.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'; @@ -171,6 +172,10 @@ export abstract class CloudInstanceCommand extends InstanceCommand { this.ensureObjectIdIfPresent(org_id, '--org-id'); this.ensureObjectIdIfPresent(project_id, '--project-id'); + if (!instance_id) { + this.styledError({ message: LINK_MISSING_ERROR_MESSAGE }); + } + try { linked = ResolvedCloudCLIConfig.decode( await resolveCloudInstanceLink({ @@ -181,11 +186,7 @@ export abstract class CloudInstanceCommand extends InstanceCommand { }) ); } catch (error) { - this.styledError({ - error, - message: - 'Linking is required before using this command. Provide --instance-id, link the project (cli.yaml), or set environment variables.' - }); + this.styledError({ error, message: LINK_MISSING_ERROR_MESSAGE }); } } diff --git a/packages/cli-core/src/command-types/SharedInstanceCommand.ts b/packages/cli-core/src/command-types/SharedInstanceCommand.ts index 6c39711..68cff9f 100644 --- a/packages/cli-core/src/command-types/SharedInstanceCommand.ts +++ b/packages/cli-core/src/command-types/SharedInstanceCommand.ts @@ -19,6 +19,7 @@ import { join } from 'node:path'; import { createCloudClient } from '../clients/create-cloud-client.js'; import { ensureServiceTypeMatches, ServiceType } from '../utils/ensure-service-type.js'; import { env } from '../utils/env.js'; +import { LINK_MISSING_ERROR_MESSAGE } from '../utils/errors.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'; @@ -170,14 +171,9 @@ export abstract class SharedInstanceCommand extends InstanceCommand { projectType = hasSelfHostedEnvInputs ? ServiceType.SELF_HOSTED : hasCloudEnvInputs ? ServiceType.CLOUD : null; } - const linkMissingErrorMessage = [ - 'Linking is required before using this command.', - '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 if (!projectType) { - this.styledError({ message: linkMissingErrorMessage }); + this.styledError({ message: LINK_MISSING_ERROR_MESSAGE }); } // 2) Per-field: flags → link file → env (see class JSDoc). @@ -191,26 +187,31 @@ export abstract class SharedInstanceCommand extends InstanceCommand { api_url: flags['api-url'] ?? _rawSelfHostedCLIConfig.api_url! ?? env.API_URL }); } catch (error) { - this.styledError({ error, message: linkMissingErrorMessage }); + this.styledError({ error, message: LINK_MISSING_ERROR_MESSAGE }); } } else { const _rawCloudCLIConfig = (rawCLIConfig as CloudCLIConfig) ?? { type: 'cloud' }; + const instanceId = flags['instance-id'] ?? _rawCloudCLIConfig.instance_id ?? env.INSTANCE_ID; + if (!instanceId) { + this.styledError({ message: LINK_MISSING_ERROR_MESSAGE }); + } + try { cliConfig = ResolvedCloudCLIConfig.decode( await resolveCloudInstanceLink({ client: this.cloudClient, - instanceId: flags['instance-id'] ?? _rawCloudCLIConfig.instance_id ?? env.INSTANCE_ID, + instanceId, 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 }); + this.styledError({ error, message: LINK_MISSING_ERROR_MESSAGE }); } } if (!cliConfig) { - this.styledError({ message: linkMissingErrorMessage }); + this.styledError({ message: LINK_MISSING_ERROR_MESSAGE }); } // ensure the link config is valid diff --git a/packages/cli-core/src/utils/errors.ts b/packages/cli-core/src/utils/errors.ts new file mode 100644 index 0000000..7738fe9 --- /dev/null +++ b/packages/cli-core/src/utils/errors.ts @@ -0,0 +1,7 @@ +/** + * Shown when a command needs an instance link but none of the inputs (flags, cli.yaml, env) resolved one. + */ +export const LINK_MISSING_ERROR_MESSAGE = [ + 'Linking is required before using this command.', + 'Provide --api-url (self-hosted) or --instance-id (cloud), or link the project first.' +].join('\n'); 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 55e656a..4331a44 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -5,7 +5,7 @@ import { ensureObjectId } from './object-id.js'; export type ResolveCloudInstanceLinkInput = { client: PowerSyncManagementClient; - instanceId?: string; + instanceId: string; orgId?: string; projectId?: string; }; @@ -16,10 +16,6 @@ export type ResolveCloudInstanceLinkInput = { export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkInput): Promise { const { client, instanceId, orgId, projectId } = input; - if (!instanceId) { - throw new Error('Cloud instance resolution requires an instance ID.'); - } - ensureObjectId(instanceId, '--instance-id'); if (orgId && projectId) { From 9459ae78e3be8e2f73025771fe1c54c24ad9c527 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 12:19:59 +0200 Subject: [PATCH 08/14] Update documentation --- cli/README.md | 153 ++++++++++------------ cli/src/commands/link/cloud.ts | 3 +- cli/src/commands/pull/instance.ts | 6 +- docs/cli-documentation-conventions.md | 4 +- docs/usage.md | 50 +++---- examples/cloud/basic-cloud-pull/README.md | 5 +- 6 files changed, 90 insertions(+), 131 deletions(-) diff --git a/cli/README.md b/cli/README.md index 60bf217..21a41a7 100644 --- a/cli/README.md +++ b/cli/README.md @@ -129,11 +129,11 @@ This avoids re-supplying the raw password in subsequent deploys while reusing th ## Using an existing instance (pull) -Run **`powersync pull instance`** with the instance identifiers (from the PowerSync Dashboard URL or **`powersync fetch instances`**). This creates the config directory, writes **`cli.yaml`**, and downloads **`service.yaml`** and **`sync-config.yaml`**. Edit as needed, then run **`powersync deploy`**. +Run **`powersync pull instance`** with the instance ID (from the PowerSync Dashboard URL or **`powersync fetch instances`**). This creates the config directory, writes **`cli.yaml`**, and downloads **`service.yaml`** and **`sync-config.yaml`**. Edit as needed, then run **`powersync deploy`**. ```sh powersync login -powersync pull instance --project-id= --instance-id= # add --org-id if multiple orgs +powersync pull instance --instance-id= # then edit powersync/service.yaml and sync-config.yaml as needed powersync deploy ``` @@ -144,18 +144,17 @@ To refresh local config after external edits from the cloud when already linked, You can run CLI commands (e.g. **`powersync generate schema`**, **`powersync generate token`**, **`powersync status`**) against a Cloud instance whose configuration is managed elsewhere—for example in the PowerSync Dashboard. No local config directory or link file is required. -Specify the instance using **environment variables** or **CLI flags** (flags take precedence): `--instance-id` and `--project-id` (or `INSTANCE_ID`, `PROJECT_ID`). **`--org-id` is optional**: when omitted, the CLI uses the token’s single organization if the token has access to exactly one; if the token has multiple orgs, you must pass **`--org-id`** (or set `ORG_ID`). +Specify the instance using **`--instance-id`** (or the `INSTANCE_ID` environment variable). Org and project are resolved automatically from the instance. ```sh powersync login -powersync generate schema --instance-id= --project-id= --output-path=schema.ts --output=ts # add --org-id if multiple orgs - # or with env vars (set ORG_ID only if your token has multiple orgs): -export PROJECT_ID= +powersync generate schema --instance-id= --output-path=schema.ts --output=ts + # or with an env var: export INSTANCE_ID= powersync generate schema --output-path=schema.ts --output=ts ``` -**Tip:** To avoid passing instance params on every command, run **`powersync link cloud`** (e.g. `powersync link cloud --instance-id= --project-id=`) once. The CLI writes `cli.yaml` in the current directory, and subsequent commands use that instance without flags or env vars. +**Tip:** To avoid passing `--instance-id` on every command, run **`powersync link cloud --instance-id=`** once. The CLI writes `cli.yaml` in the current directory, and subsequent commands use that instance without flags or env vars. # Self-hosted @@ -239,7 +238,7 @@ $ npm install -g powersync $ powersync COMMAND running command... $ powersync (--version) -powersync/0.9.5 linux-x64 node-v24.15.0 +powersync/0.9.5 darwin-arm64 node-v24.16.0 $ powersync --help [COMMAND] USAGE $ powersync COMMAND @@ -253,15 +252,13 @@ USAGE You can supply instance and auth context via environment variables (useful for CI or scripts): - **`PS_ADMIN_TOKEN`** — PowerSync personal access token for Cloud commands. [Learn more](https://docs.powersync.com/usage/tools/cli#personal-access-token). -- **`ORG_ID`** — Organization ID (optional for Cloud). Omit when your token has a single organization; required when it has multiple. -- **`PROJECT_ID`** — Project ID (Cloud). - **`INSTANCE_ID`** — Instance ID (Cloud). Get IDs from the [PowerSync Dashboard](https://dashboard.powersync.com) or **`powersync fetch instances`**. - **`API_URL`** — Self-hosted PowerSync API base URL (e.g. `https://powersync.example.com`). Example (Cloud): ```sh -PS_ADMIN_TOKEN=your-token PROJECT_ID=456 INSTANCE_ID=789 powersync status +PS_ADMIN_TOKEN=your-token INSTANCE_ID=123 powersync status ``` See [docs/usage.md](../docs/usage.md) for full usage and resolution order (flags, env, cli.yaml). @@ -377,7 +374,7 @@ _See code: [@oclif/plugin-commands](https://github.com/oclif/plugin-commands/blo ``` USAGE - $ powersync compact [--directory ] [--instance-id --project-id ] [--org-id ] + $ powersync compact [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--timeout ] FLAGS @@ -391,9 +388,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Compact the linked Cloud instance. @@ -436,8 +432,9 @@ _See code: [src/commands/configure/ide.ts](https://github.com/powersync-ja/power ``` USAGE - $ powersync deploy [--deploy-timeout ] [--directory ] [--instance-id --project-id - ] [--org-id ] [--sync-config-file-path ] [--skip-validations | --validate-only ] + $ powersync deploy [--deploy-timeout ] [--directory ] [--instance-id ] [--org-id + ] [--project-id ] [--sync-config-file-path ] [--skip-validations | --validate-only + ] FLAGS --deploy-timeout= [default: 300] Seconds to wait after scheduling a deploy before timing out while polling @@ -456,9 +453,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Deploy local config to the linked Cloud instance (connections + auth + sync config). @@ -471,7 +467,7 @@ DESCRIPTION EXAMPLES $ powersync deploy - $ powersync deploy --instance-id= --project-id= + $ powersync deploy --instance-id= ``` _See code: [src/commands/deploy/index.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/deploy/index.ts)_ @@ -482,8 +478,8 @@ _See code: [src/commands/deploy/index.ts](https://github.com/powersync-ja/powers ``` USAGE - $ powersync deploy service-config [--deploy-timeout ] [--directory ] [--instance-id --project-id - ] [--org-id ] [--skip-validations | --validate-only ] + $ powersync deploy service-config [--deploy-timeout ] [--directory ] [--instance-id ] [--org-id + ] [--project-id ] [--skip-validations | --validate-only ] FLAGS --deploy-timeout= [default: 300] Seconds to wait after scheduling a deploy before timing out while polling @@ -500,9 +496,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Deploy only local service config to the linked Cloud instance. @@ -512,7 +507,7 @@ DESCRIPTION EXAMPLES $ powersync deploy service-config - $ powersync deploy service-config --instance-id= --project-id= + $ powersync deploy service-config --instance-id= ``` _See code: [src/commands/deploy/service-config.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/deploy/service-config.ts)_ @@ -523,8 +518,8 @@ _See code: [src/commands/deploy/service-config.ts](https://github.com/powersync- ``` USAGE - $ powersync deploy sync-config [--deploy-timeout ] [--directory ] [--instance-id --project-id - ] [--org-id ] [--sync-config-file-path ] [--skip-validations | ] + $ powersync deploy sync-config [--deploy-timeout ] [--directory ] [--instance-id ] [--org-id + ] [--project-id ] [--sync-config-file-path ] [--skip-validations | ] FLAGS --deploy-timeout= [default: 300] Seconds to wait after scheduling a deploy before timing out while polling @@ -541,9 +536,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Deploy only local sync config to the linked Cloud instance. @@ -553,7 +547,7 @@ DESCRIPTION EXAMPLES $ powersync deploy sync-config - $ powersync deploy sync-config --instance-id= --project-id= + $ powersync deploy sync-config --instance-id= ``` _See code: [src/commands/deploy/sync-config.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/deploy/sync-config.ts)_ @@ -564,7 +558,7 @@ _See code: [src/commands/deploy/sync-config.ts](https://github.com/powersync-ja/ ``` USAGE - $ powersync destroy [--directory ] [--instance-id --project-id ] [--org-id ] + $ powersync destroy [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--confirm yes] FLAGS @@ -578,9 +572,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Permanently destroy the linked Cloud instance. @@ -757,9 +750,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Open the PowerSync configuration editor (Nitro server). @@ -780,7 +772,7 @@ _See code: [@powersync/cli-plugin-config-edit](https://github.com/powersync-ja/p ``` USAGE - $ powersync fetch config [--directory ] [--instance-id --project-id ] [--org-id ] + $ powersync fetch config [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--output json|yaml] FLAGS @@ -794,9 +786,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Print linked Cloud instance config (YAML or JSON). @@ -864,9 +855,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Show instance diagnostics (connections, sync config, replication). @@ -879,7 +869,7 @@ EXAMPLES $ powersync fetch status --output=json - $ powersync fetch status --instance-id= --project-id= + $ powersync fetch status --instance-id= ``` _See code: [src/commands/fetch/status.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/fetch/status.ts)_ @@ -914,9 +904,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Generate client schema file from instance schema and sync config. @@ -927,7 +916,7 @@ DESCRIPTION EXAMPLES $ powersync generate schema --output=ts --output-path=schema.ts - $ powersync generate schema --output=dart --output-path=lib/schema.dart --instance-id= --project-id= + $ powersync generate schema --output=dart --output-path=lib/schema.dart --instance-id= ``` _See code: [src/commands/generate/schema.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/generate/schema.ts)_ @@ -959,9 +948,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Generate a development JWT for client connections. @@ -1057,16 +1045,16 @@ _See code: [src/commands/init/self-hosted.ts](https://github.com/powersync-ja/po ``` USAGE - $ powersync link cloud --project-id [--directory ] [--instance-id ] [--org-id ] + $ powersync link cloud [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--create] FLAGS --create Create a new Cloud instance in the given org and project, then link. Do not supply --instance-id when using --create. --instance-id= PowerSync Cloud instance ID. Omit when using --create. Resolved: flag → INSTANCE_ID → cli.yaml. - --org-id= Organization ID. Optional when the token has a single org; required when the token has multiple - orgs. Resolved: flag → ORG_ID → cli.yaml. - --project-id= (required) Project ID. Resolved: flag → PROJECT_ID → cli.yaml. + --org-id= Organization ID. Required with --create when the token has multiple orgs; optional when linking + an existing instance. + --project-id= Project ID. Required with --create; optional assertion when linking an existing instance. PROJECT FLAGS --directory= [default: powersync] Directory containing PowerSync config. Defaults to "powersync". This is @@ -1076,16 +1064,15 @@ PROJECT FLAGS DESCRIPTION [Cloud only] Link to a PowerSync Cloud instance (or create one with --create). - 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. EXAMPLES - $ powersync link cloud --project-id= + $ powersync link cloud --instance-id= $ powersync link cloud --create --project-id= - $ powersync link cloud --instance-id= --project-id= --org-id= + $ powersync link cloud --create --project-id= --org-id= ``` _See code: [src/commands/link/cloud.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/link/cloud.ts)_ @@ -1184,9 +1171,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Migrates Sync Rules to Sync Streams @@ -1492,7 +1478,7 @@ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/ ``` USAGE - $ powersync pull instance [--directory ] [--instance-id --project-id ] [--org-id ] + $ powersync pull instance [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--overwrite] FLAGS @@ -1507,23 +1493,19 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Pull an existing Cloud instance: link and download config into local service.yaml and sync-config.yaml. 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. + service.yaml and sync-config.yaml. Cloud only. EXAMPLES $ powersync pull instance - $ powersync pull instance --instance-id= --project-id= - - $ powersync pull instance --instance-id= --project-id= --org-id= + $ powersync pull instance --instance-id= ``` _See code: [src/commands/pull/instance.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/pull/instance.ts)_ @@ -1553,9 +1535,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Show instance diagnostics (connections, sync config, replication). @@ -1568,7 +1549,7 @@ EXAMPLES $ powersync status --output=json - $ powersync status --instance-id= --project-id= + $ powersync status --instance-id= ``` _See code: [src/commands/status.ts](https://github.com/powersync-ja/powersync-cli/blob/v0.9.5/src/commands/status.ts)_ @@ -1579,7 +1560,7 @@ _See code: [src/commands/status.ts](https://github.com/powersync-ja/powersync-cl ``` USAGE - $ powersync stop [--directory ] [--instance-id --project-id ] [--org-id ] + $ powersync stop [--directory ] [--instance-id ] [--org-id ] [--project-id ] [--confirm yes] FLAGS @@ -1593,9 +1574,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= PowerSync Cloud instance ID. Manually passed if the current context has not been linked. - --org-id= Organization ID (optional). Defaults to the token’s single org when only one is available; pass - explicitly if the token has multiple orgs. - --project-id= Project ID. Manually passed if the current context has not been linked. + --org-id= [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION [Cloud only] Stop the linked Cloud instance (restart with deploy). @@ -1642,9 +1622,8 @@ PROJECT FLAGS CLOUD_PROJECT FLAGS --instance-id= [Cloud] PowerSync Cloud instance ID (BSON ObjectID). When set, context is treated as cloud (exclusive with --api-url). Resolved: flag → cli.yaml → INSTANCE_ID. - --org-id= [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. - --project-id= [Cloud] Project ID. Resolved: flag → cli.yaml → PROJECT_ID. + --org-id= [Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id. + --project-id= [Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id. DESCRIPTION Validate config schema, connections, and sync config before deploy. diff --git a/cli/src/commands/link/cloud.ts b/cli/src/commands/link/cloud.ts index a1def68..62a5ec3 100644 --- a/cli/src/commands/link/cloud.ts +++ b/cli/src/commands/link/cloud.ts @@ -22,8 +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=' + '<%= config.bin %> <%= command.id %> --create --project-id= --org-id=' ]; static flags = { create: Flags.boolean({ diff --git a/cli/src/commands/pull/instance.ts b/cli/src/commands/pull/instance.ts index efe3d57..4ef4f2b 100644 --- a/cli/src/commands/pull/instance.ts +++ b/cli/src/commands/pull/instance.ts @@ -33,11 +33,7 @@ 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. Cloud only.'; - static examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --instance-id=', - '<%= config.bin %> <%= command.id %> --instance-id= --project-id= --org-id=' - ]; + static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --instance-id=']; static flags = { overwrite: Flags.boolean({ description: diff --git a/docs/cli-documentation-conventions.md b/docs/cli-documentation-conventions.md index a050ce3..5550fa1 100644 --- a/docs/cli-documentation-conventions.md +++ b/docs/cli-documentation-conventions.md @@ -8,7 +8,7 @@ This document describes how we document commands and help text in the PowerSync - **`static summary`** — Short one-liner for the command list (e.g. in `powersync --help`). Prefer a terse summary; use `[Cloud only]` or `[Self-hosted only]` when applicable. - **`static examples`** — Array of example invocations. Always include at least: - `'<%= config.bin %> <%= command.id %>'` (base form). - - Additional entries for common flag combinations (e.g. `--confirm=yes`, `--output=json`, `--instance-id= --project-id=`). + - Additional entries for common flag combinations (e.g. `--confirm=yes`, `--output=json`, `--instance-id=`). Use the oclif template so the bin name stays correct when the CLI is installed under a different name. ## Flag descriptions @@ -27,7 +27,7 @@ For grouped commands (e.g. `fetch`, `generate`, `init`, `link`, `pull`, `migrate ## README - The CLI README (`cli/README.md`) uses oclif markers: ``, ``, ``. Content between these is replaced by `oclif readme` (run on `prepack` and `version`). Do not hand-edit the generated command blocks. -- An **Environment variables** section (after the usage block) documents `PS_ADMIN_TOKEN`, `ORG_ID`, `PROJECT_ID`, `INSTANCE_ID`, and `API_URL` for script/CI use, with a short example. +- An **Environment variables** section (after the usage block) documents `PS_ADMIN_TOKEN`, `INSTANCE_ID`, and `API_URL` for script/CI use, with a short example. ## Regenerating command docs diff --git a/docs/usage.md b/docs/usage.md index f41fe58..e4ac51f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -15,10 +15,10 @@ You can **explicitly link** your local config to a cloud or self-hosted project. For commands that don’t require locally stored config (or when you don’t want to use it), you can supply **instance information** in either of these ways: - **Inline as flags** - - **Cloud:** `--instance-id`, `--project-id`, and optionally `--org-id`. If `--org-id` (and `ORG_ID`) are omitted, the CLI uses the token’s single organization when the token has access to exactly one; if the token has multiple orgs, you must pass `--org-id` (or set `ORG_ID`). + - **Cloud:** `--instance-id`. Org and project are resolved automatically from the instance. - **Self-hosted:** `--api-url` (API key is not accepted via flags; use link command or `PS_ADMIN_TOKEN` env var) - **Environment variables** - - **Cloud:** `INSTANCE_ID`, `PROJECT_ID`, and optionally `ORG_ID` (same default behaviour as above when omitted) + - **Cloud:** `INSTANCE_ID` - **Self-hosted:** `API_URL`, `PS_ADMIN_TOKEN` (token used as API key) That lets you run one-off or scripted operations (e.g. generating a development token, generating client side schemas) without creating or using a `powersync/` folder or a link file. @@ -56,8 +56,6 @@ Example in **cli.yaml** (cloud — instance resolved from env): ```yaml type: cloud instance_id: !env MY_INSTANCE_ID_VAR -org_id: !env MY_ORG_ID_VAR -project_id: !env MY_PROJECT_ID_VAR ``` Or for self-hosted: @@ -119,9 +117,8 @@ For an instance that already exists (e.g. created in the Dashboard), there is no ```bash powersync login -# IDs from the PowerSync Dashboard URL or `powersync fetch instances`. Creates powersync/, cli.yaml, and downloads config. -powersync pull instance --project-id= --instance-id= -# If your token has multiple orgs: add --org-id= +# Instance ID from the PowerSync Dashboard URL or `powersync fetch instances`. Creates powersync/, cli.yaml, and downloads config. +powersync pull instance --instance-id= # Edit the YAML files in powersync/ as needed powersync validate @@ -145,8 +142,7 @@ You can run commands against an instance whose configuration is managed elsewher ```bash powersync login powersync fetch instances # to see available instances and their IDs -powersync link cloud --instance-id= --project-id= -# If your token has multiple orgs: add --org-id= +powersync link cloud --instance-id= ``` Then run commands without passing IDs again, for example: @@ -156,7 +152,7 @@ powersync generate schema powersync generate token ``` -You can also supply `--instance-id` and `--project-id` (and `--org-id` only when your token has multiple orgs) or the corresponding environment variables on individual commands if you don’t want to link. +You can also supply `--instance-id` (or the `INSTANCE_ID` environment variable) on individual commands if you don’t want to link. --- @@ -226,16 +222,14 @@ If you decline this prompt, login exits without storing a token. Use `PS_ADMIN_T # Supplying Linking Information for Cloud and Self-Hosted Commands -Cloud and self-hosted commands need instance (and for Cloud, org and project) identifiers. **Cloud only:** `powersync deploy`, `powersync deploy service-config`, `powersync deploy sync-config`, `powersync destroy`, `powersync stop`, `powersync fetch config`, `powersync pull instance`. **Both:** `powersync status`, `powersync generate schema`, `powersync generate token`, `powersync validate`. The same three methods apply: the CLI uses the first that is available for each field (flags override environment variables, environment variables override link file). For Cloud, **org_id is optional**: when not set via flags, env, or link file, the CLI fetches the token’s organizations and uses the single org if there is exactly one; if the token has multiple orgs, the command errors and you must pass `--org-id` (or set `ORG_ID`). +Cloud and self-hosted commands need an instance identifier. **Cloud only:** `powersync deploy`, `powersync deploy service-config`, `powersync deploy sync-config`, `powersync destroy`, `powersync stop`, `powersync fetch config`, `powersync pull instance`. **Both:** `powersync status`, `powersync generate schema`, `powersync generate token`, `powersync validate`. The same three methods apply: the CLI uses the first that is available (flags override environment variables, environment variables override link file). For Cloud commands, the org and project are resolved automatically from the instance. 1. **Flags** - - **Cloud:** `--instance-id`, `--project-id` (required when using instance-id), `--org-id` (optional; defaults to token’s single org) + - **Cloud:** `--instance-id` - **Self-hosted:** `--api-url` only (API key from env or link file only) 2. **Environment variables** - - **Cloud:** `INSTANCE_ID`, `PROJECT_ID`, and optionally `ORG_ID` (same default as above) - -- **Self-hosted:** `API_URL`, `PS_ADMIN_TOKEN` (API key) - + - **Cloud:** `INSTANCE_ID` + - **Self-hosted:** `API_URL`, `PS_ADMIN_TOKEN` (API key) 3. **cli.yaml** — a `powersync/cli.yaml` file in the project (written by `powersync link cloud` or `powersync link self-hosted`) --- @@ -250,10 +244,7 @@ Pass the identifiers on each command. Useful for one-off runs or to override the powersync login # Stop a specific instance without linking the directory (overrides cli.yaml if present) -powersync stop --confirm=yes \ - --instance-id=688736sdfcfb46688f509bd0 \ - --project-id=6703fd8a3cfe3000hrydg463 -# If your token has multiple orgs: add --org-id= +powersync stop --confirm=yes --instance-id=688736sdfcfb46688f509bd0 ``` **Self-hosted:** Set `PS_ADMIN_TOKEN` (or use a linked project with API key in cli.yaml), then: @@ -265,8 +256,8 @@ powersync status --api-url=https://powersync.example.com You can use a different project directory with `--directory`: ```bash -# Cloud (add --org-id=... only if your token has multiple orgs) -powersync stop --confirm=yes --directory=my-powersync --instance-id=... --project-id=... +# Cloud +powersync stop --confirm=yes --directory=my-powersync --instance-id=... # Self-hosted (API key from PS_ADMIN_TOKEN or cli.yaml) powersync status --directory=my-powersync --api-url=https://... @@ -282,10 +273,7 @@ Link the project once; later commands use the stored IDs. Best for day-to-day wo powersync login # Link this project to a Cloud instance (writes powersync/cli.yaml) -powersync link cloud \ - --instance-id=688736sdfcfb46688f509bd0 \ - --project-id=6703fd8a3cfe3000hrydg463 -# If your token has multiple orgs: add --org-id=5cc84a3ccudjfhgytw0c08b +powersync link cloud --instance-id=688736sdfcfb46688f509bd0 # No IDs needed on later commands powersync stop --confirm=yes @@ -295,7 +283,7 @@ powersync status If the project lives in a non-default directory: ```bash -powersync link cloud --directory=my-powersync --instance-id=... --project-id=... +powersync link cloud --directory=my-powersync --instance-id=... powersync stop --confirm=yes --directory=my-powersync ``` @@ -305,12 +293,10 @@ powersync stop --confirm=yes --directory=my-powersync Set identifiers in the environment when you don’t want to link the directory or pass flags every time (e.g. CI or scripts). -**Cloud:** (Most tokens have a single org; omit `ORG_ID`. Set it only if your token has multiple orgs.) +**Cloud:** ```bash export INSTANCE_ID=688736sdfcfb46688f509bd0 -export PROJECT_ID=6703fd8a3cfe3000hrydg463 -# export ORG_ID=... # only if your token has multiple orgs powersync stop --confirm=yes powersync fetch config --output=json @@ -328,8 +314,8 @@ powersync status --output=json Inline for a single command: ```bash -# Cloud (add ORG_ID=... only if your token has multiple orgs) -INSTANCE_ID=... PROJECT_ID=... powersync stop --confirm=yes +# Cloud +INSTANCE_ID=... powersync stop --confirm=yes # Self-hosted API_URL=https://... PS_ADMIN_TOKEN=... powersync status --output=json diff --git a/examples/cloud/basic-cloud-pull/README.md b/examples/cloud/basic-cloud-pull/README.md index 304a48d..53db681 100644 --- a/examples/cloud/basic-cloud-pull/README.md +++ b/examples/cloud/basic-cloud-pull/README.md @@ -1,13 +1,12 @@ # Basic Cloud Pull Example -This example was created by pulling an existing PowerSync Cloud instance with **`powersync pull instance`**. You do not need to run **`powersync init`** first: **`pull instance`** with your instance IDs creates the config directory, writes `cli.yaml`, and downloads `service.yaml` and `sync-config.yaml`. +This example was created by pulling an existing PowerSync Cloud instance with **`powersync pull instance`**. You do not need to run **`powersync init`** first: **`pull instance`** with your instance ID creates the config directory, writes `cli.yaml`, and downloads `service.yaml` and `sync-config.yaml`. Log in (`powersync login`) or set the `PS_ADMIN_TOKEN` environment variable, then run: ```bash # Creates powersync/, writes cli.yaml, and downloads config for the given instance -powersync pull instance --project-id=abc --instance-id=def -# If your token has multiple orgs: add --org-id= +powersync pull instance --instance-id=abc ``` The configuration file in `./powersync/service.yaml` can now be edited. From 26192d067118b232a460559ed5432aadd2c32fa8 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 13:36:16 +0200 Subject: [PATCH 09/14] Version bumps --- pnpm-workspace.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 58995e0..ed98a17 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,10 +6,10 @@ packages: catalog: '@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-client': ^0.0.6 + '@powersync/service-schema': ^1.21.0 '@powersync/service-sync-rules': ^0.34.0 - '@powersync/service-types': ^0.15.0 + '@powersync/service-types': ^0.15.2 patchedDependencies: oclif@4.22.81: patches/oclif@4.22.81.patch From 2d48962ceffafb06e0974cb57709fab9fc6bb0de Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 13:36:43 +0200 Subject: [PATCH 10/14] Lockfile --- pnpm-lock.yaml | 69 +++++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 49 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b13277..2ad21d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,17 +13,17 @@ catalogs: specifier: ^0.1.0 version: 0.1.0 '@powersync/service-client': - specifier: ^0.0.3 - version: 0.0.3 + specifier: ^0.0.6 + version: 0.0.6 '@powersync/service-schema': - specifier: ^1.20.2 - version: 1.20.2 + specifier: ^1.21.0 + version: 1.21.0 '@powersync/service-sync-rules': specifier: ^0.34.0 version: 0.34.0 '@powersync/service-types': - specifier: ^0.15.0 - version: 0.15.0 + specifier: ^0.15.2 + version: 0.15.2 patchedDependencies: oclif@4.22.81: @@ -111,7 +111,7 @@ importers: version: 0.34.0 '@powersync/service-types': specifier: 'catalog:' - version: 0.15.0 + version: 0.15.2 '@powersync/sync-config-tools': specifier: ^0.1.1 version: 0.1.1 @@ -193,7 +193,7 @@ importers: 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) + version: 0.0.6(@types/json-schema@7.0.15) keychain: specifier: ^1.5.0 version: 1.5.0 @@ -342,10 +342,10 @@ importers: version: 0.1.0 '@powersync/service-schema': specifier: 'catalog:' - version: 1.20.2 + version: 1.21.0 '@powersync/service-types': specifier: 'catalog:' - version: 0.15.0 + version: 0.15.2 ajv: specifier: ^8.12.0 version: 8.17.1 @@ -416,10 +416,10 @@ importers: version: link:../../packages/schemas '@powersync/service-schema': specifier: 'catalog:' - version: 1.20.2 + version: 1.21.0 '@powersync/service-types': specifier: 'catalog:' - version: 0.15.0 + version: 0.15.2 yaml: specifier: ^2 version: 2.8.2 @@ -1617,21 +1617,18 @@ packages: '@powersync/management-types@0.1.0': resolution: {integrity: sha512-Ypv1UCu2rOlTKS8HjGl21aMd1NQnsG9isrs7OyIamjlMM1n2wLjbGuZNacu7Wzqc9CPeNZisUO4QXKyfuXa2kw==} - '@powersync/service-client@0.0.3': - resolution: {integrity: sha512-K83tOFdjRyFh76c4CKViRFyGJfBH9wSvtOvF2PxtbajAIPMzZEeunpnFj3vphx8CQyrDFWkR8/1Qa0BiCo30xQ==} + '@powersync/service-client@0.0.6': + resolution: {integrity: sha512-7IDBDac4ePg9JNFtoL0aDrP5WmDWqw45/kGYl38pXkF6Km0VhVMEUjor/k2+oB05LiC0sfdHf5NXRDRUfK+maw==} '@powersync/service-jsonbig@0.17.12': resolution: {integrity: sha512-ilssJP6I+v8LfBCIwRJlKZhuxVT7pGhYg/B2+xsodVRRmTkyRDpmBpV+RlabksbtR1H0G3aXm5AtNYwmfblBkQ==} - '@powersync/service-schema@1.20.2': - resolution: {integrity: sha512-hfhKYSkTGVMKMTDt/s91NjyCZYf0Zv5/ptQtGdY0BGYfbMXXNv7J+BJDtGYtkeqqbZKbMe4rXZ/ijckIznk08w==} + '@powersync/service-schema@1.21.0': + resolution: {integrity: sha512-m5QfEwOMk8Gu+6qNeWqS9RaNDNL8WQb3kaeMyo98egpasWPeQzD2BE1nhzJLpbsVSV9WchnNDDr1jRikvnRjLg==} '@powersync/service-sync-rules@0.34.0': resolution: {integrity: sha512-hHJkWzf93rqTcQYybt05lHwaJzUyhmS9BRn6j+inL13VU1/W6rrdH07rr9RYZAPxjis2sUjrV17E0Dt0+8QcqA==} - '@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==} @@ -8795,37 +8792,19 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@powersync/service-client@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)': + '@powersync/service-client@0.0.6(@types/json-schema@7.0.15)': dependencies: - '@journeyapps-labs/common-sdk': 1.0.2(@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/service-types': 0.15.0 + '@journeyapps-labs/common-sdk': 1.0.4(@types/json-schema@7.0.15) + '@powersync/service-types': 0.15.2 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/service-jsonbig@0.17.12': dependencies: lossless-json: 2.0.11 - '@powersync/service-schema@1.20.2': {} + '@powersync/service-schema@1.21.0': {} '@powersync/service-sync-rules@0.34.0': dependencies: @@ -8836,14 +8815,6 @@ snapshots: uuid: 11.1.0 yaml: 2.8.2 - '@powersync/service-types@0.15.0': - dependencies: - dedent: 1.7.1 - ts-codec: 1.3.0 - uri-js: 4.4.1 - transitivePeerDependencies: - - babel-plugin-macros - '@powersync/service-types@0.15.2': dependencies: dedent: 1.7.1 From fead67458c1439abb4d87b65a0cc904bd9ac9019 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Tue, 26 May 2026 16:32:46 +0200 Subject: [PATCH 11/14] More version bumps --- pnpm-lock.yaml | 180 +++++++++++++++++++++++--------------------- pnpm-workspace.yaml | 6 +- 2 files changed, 97 insertions(+), 89 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ad21d7..340c054 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,11 +7,11 @@ settings: catalogs: default: '@powersync/management-client': - specifier: ^0.1.0 - version: 0.1.0 + specifier: ^0.1.1 + version: 0.1.1 '@powersync/management-types': - specifier: ^0.1.0 - version: 0.1.0 + specifier: ^0.2.0 + version: 0.2.0 '@powersync/service-client': specifier: ^0.0.6 version: 0.0.6 @@ -19,8 +19,8 @@ catalogs: specifier: ^1.21.0 version: 1.21.0 '@powersync/service-sync-rules': - specifier: ^0.34.0 - version: 0.34.0 + specifier: ^0.36.0 + version: 0.36.0 '@powersync/service-types': specifier: ^0.15.2 version: 0.15.2 @@ -102,13 +102,13 @@ importers: version: link:../packages/schemas '@powersync/management-client': specifier: 'catalog:' - version: 0.1.0(@types/json-schema@7.0.15) + version: 0.1.1(@types/json-schema@7.0.15) '@powersync/management-types': specifier: 'catalog:' - version: 0.1.0 + version: 0.2.0 '@powersync/service-sync-rules': specifier: 'catalog:' - version: 0.34.0 + version: 0.36.0 '@powersync/service-types': specifier: 'catalog:' version: 0.15.2 @@ -187,10 +187,10 @@ importers: version: link:../schemas '@powersync/management-client': specifier: 'catalog:' - version: 0.1.0(@types/json-schema@7.0.15) + version: 0.1.1(@types/json-schema@7.0.15) '@powersync/management-types': specifier: 'catalog:' - version: 0.1.0 + version: 0.2.0 '@powersync/service-client': specifier: 'catalog:' version: 0.0.6(@types/json-schema@7.0.15) @@ -227,7 +227,7 @@ importers: version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.2.0(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)) + version: 4.2.0(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@tanstack/react-devtools': specifier: ^0.7.0 version: 0.7.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) @@ -245,10 +245,10 @@ importers: version: 1.162.1(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.4))(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.162.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start': specifier: ^1.132.0 - version: 1.162.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(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)) + version: 1.162.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@tanstack/router-plugin': specifier: ^1.132.0 - version: 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(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)) + version: 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) buffer: specifier: ^6.0.3 version: 6.0.3 @@ -272,7 +272,7 @@ importers: version: 5.4.1(monaco-editor@0.52.0) nitro: specifier: npm:nitro-nightly@3.0.1-20260227-181935-bfbb207c - version: nitro-nightly@3.0.1-20260227-181935-bfbb207c(@azure/identity@4.13.0)(dotenv@16.6.1)(jiti@2.6.1)(lru-cache@11.2.6)(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)) + version: nitro-nightly@3.0.1-20260227-181935-bfbb207c(@azure/identity@4.13.0)(dotenv@16.6.1)(jiti@2.6.1)(lru-cache@11.2.6)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) path-browserify: specifier: ^1.0.1 version: 1.0.1 @@ -300,7 +300,7 @@ importers: devDependencies: '@tanstack/devtools-vite': specifier: ^0.3.11 - version: 0.3.12(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)) + version: 0.3.12(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.1 @@ -318,7 +318,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^5.0.4 - version: 5.1.4(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)) + version: 5.1.4(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) jsdom: specifier: ^27.0.0 version: 27.4.0 @@ -327,19 +327,19 @@ importers: version: 5.9.3 vite: specifier: ^7.1.7 - version: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) vite-tsconfig-paths: specifier: ^5.1.4 - version: 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)) + version: 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.9.0)) vitest: specifier: ^3.0.5 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) packages/schemas: dependencies: '@powersync/management-types': specifier: 'catalog:' - version: 0.1.0 + version: 0.2.0 '@powersync/service-schema': specifier: 'catalog:' version: 1.21.0 @@ -358,7 +358,7 @@ importers: devDependencies: '@powersync/service-sync-rules': specifier: 'catalog:' - version: 0.34.0 + version: 0.36.0 '@types/node': specifier: ^22 version: 22.19.7 @@ -1454,9 +1454,6 @@ packages: '@journeyapps-labs/common-utils@1.0.1': resolution: {integrity: sha512-9P1f++wHSveqMQIWulLdGnDgvMdEqXP+j8eIscDm2bAuiMCIbij856AQgdrG0mQPPhL5SlUNlSGrfdKfhul2QA==} - '@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==} @@ -1611,23 +1608,23 @@ packages: resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} - '@powersync/management-client@0.1.0': - resolution: {integrity: sha512-YGJPNZHrSwUF3ofhz8VhbU1/QCF4KUKWGTyfzsDkmGJ9p8DESF5tZ1r0O7Nnmt1o2C0JOaQ0pV8z2pgnE1qb0g==} + '@powersync/management-client@0.1.1': + resolution: {integrity: sha512-/A/OLxIdVisC22IRdav//j6K5qENVhf2dDedVUKF7/lwAdcncZZnv2b8lVS+ZFjlpNoJ3RXngXaVwXooAFIffQ==} - '@powersync/management-types@0.1.0': - resolution: {integrity: sha512-Ypv1UCu2rOlTKS8HjGl21aMd1NQnsG9isrs7OyIamjlMM1n2wLjbGuZNacu7Wzqc9CPeNZisUO4QXKyfuXa2kw==} + '@powersync/management-types@0.2.0': + resolution: {integrity: sha512-Snbx1ZC5WxieV6R20Ll/wBbJQx5Px5v6KqYWfewFT4w0jLw0NtZT8kkw2HTPcfiOO1L463JBOVcJNiiDxG9BzQ==} '@powersync/service-client@0.0.6': resolution: {integrity: sha512-7IDBDac4ePg9JNFtoL0aDrP5WmDWqw45/kGYl38pXkF6Km0VhVMEUjor/k2+oB05LiC0sfdHf5NXRDRUfK+maw==} - '@powersync/service-jsonbig@0.17.12': - resolution: {integrity: sha512-ilssJP6I+v8LfBCIwRJlKZhuxVT7pGhYg/B2+xsodVRRmTkyRDpmBpV+RlabksbtR1H0G3aXm5AtNYwmfblBkQ==} + '@powersync/service-jsonbig@0.17.13': + resolution: {integrity: sha512-S90x8xgYYaiNBi6pnBbSxqiZlXUhEDb10HfTs5maLPVYIlDKXG5YlhP1DVv/csFNCuIuMC1F5ABXnN2rNjfajg==} '@powersync/service-schema@1.21.0': resolution: {integrity: sha512-m5QfEwOMk8Gu+6qNeWqS9RaNDNL8WQb3kaeMyo98egpasWPeQzD2BE1nhzJLpbsVSV9WchnNDDr1jRikvnRjLg==} - '@powersync/service-sync-rules@0.34.0': - resolution: {integrity: sha512-hHJkWzf93rqTcQYybt05lHwaJzUyhmS9BRn6j+inL13VU1/W6rrdH07rr9RYZAPxjis2sUjrV17E0Dt0+8QcqA==} + '@powersync/service-sync-rules@0.36.0': + resolution: {integrity: sha512-WlV4pOK1QwsM+6Bvy5Vkq04xmb0E1FcbWgqnQcbVJpKnSGul8osRDoIC/2JSNOauwxTqNXf0Ec8n6tzD9SSWOQ==} '@powersync/service-types@0.15.2': resolution: {integrity: sha512-GXQqCuTME+S1nV9CLbxtgYuAtMGFFLkDubm+DRTNGQqur8wBVzJp3haHCHFBVJ03Bhec+K7AIrB6CTii7b/HJg==} @@ -3184,6 +3181,9 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -6295,14 +6295,14 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - uuid@13.0.0: resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} hasBin: true + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + 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). @@ -6590,6 +6590,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yarn@1.22.22: resolution: {integrity: sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==} engines: {node: '>=4.0.0'} @@ -8457,12 +8462,6 @@ snapshots: dependencies: uuid: 13.0.0 - '@journeyapps-labs/micro-codecs@1.0.2': - dependencies: - '@types/node': 24.10.10 - bson: 6.10.4 - ts-codec: 1.3.0 - '@journeyapps-labs/micro-codecs@1.0.3': dependencies: '@types/node': 24.10.10 @@ -8474,7 +8473,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.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) @@ -8775,15 +8774,15 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@powersync/management-client@0.1.0(@types/json-schema@7.0.15)': + '@powersync/management-client@0.1.1(@types/json-schema@7.0.15)': dependencies: '@journeyapps-labs/common-sdk': 1.0.4(@types/json-schema@7.0.15) - '@powersync/management-types': 0.1.0 + '@powersync/management-types': 0.2.0 transitivePeerDependencies: - '@types/json-schema' - babel-plugin-macros - '@powersync/management-types@0.1.0': + '@powersync/management-types@0.2.0': dependencies: '@journeyapps-labs/micro-codecs': 1.0.3 '@powersync/service-types': 0.15.2 @@ -8800,20 +8799,20 @@ snapshots: - '@types/json-schema' - babel-plugin-macros - '@powersync/service-jsonbig@0.17.12': + '@powersync/service-jsonbig@0.17.13': dependencies: lossless-json: 2.0.11 '@powersync/service-schema@1.21.0': {} - '@powersync/service-sync-rules@0.34.0': + '@powersync/service-sync-rules@0.36.0': dependencies: - '@powersync/service-jsonbig': 0.17.12 + '@powersync/service-jsonbig': 0.17.13 '@syncpoint/wkx': 0.5.2 - ajv: 8.17.1 + ajv: 8.20.0 pgsql-ast-parser: 11.2.0 - uuid: 11.1.0 - yaml: 2.8.2 + uuid: 14.0.0 + yaml: 2.9.0 '@powersync/service-types@0.15.2': dependencies: @@ -9859,12 +9858,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.0 '@tailwindcss/oxide-win32-x64-msvc': 4.2.0 - '@tailwindcss/vite@4.2.0(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))': + '@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.2.0 '@tailwindcss/oxide': 4.2.0 tailwindcss: 4.2.0 - 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) '@tanstack/devtools-client@0.0.3': dependencies: @@ -9893,7 +9892,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.3.12(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))': + '@tanstack/devtools-vite@0.3.12(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -9905,7 +9904,7 @@ snapshots: chalk: 5.6.2 launch-editor: 2.13.0 picomatch: 4.0.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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - bufferutil - supports-color @@ -10004,19 +10003,19 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start@1.162.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(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))': + '@tanstack/react-start@1.162.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@tanstack/react-router': 1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-client': 1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-server': 1.162.1(crossws@0.4.4(srvx@0.11.7))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-utils': 1.161.4 '@tanstack/start-client-core': 1.162.1 - '@tanstack/start-plugin-core': 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.11.7))(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)) + '@tanstack/start-plugin-core': 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.11.7))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@tanstack/start-server-core': 1.162.1(crossws@0.4.4(srvx@0.11.7)) pathe: 2.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@rsbuild/core' - crossws @@ -10063,7 +10062,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(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))': + '@tanstack/router-plugin@1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -10080,7 +10079,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -10114,7 +10113,7 @@ snapshots: '@tanstack/start-fn-stubs@1.161.4': {} - '@tanstack/start-plugin-core@1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.11.7))(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))': + '@tanstack/start-plugin-core@1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.11.7))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0 @@ -10122,7 +10121,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.40 '@tanstack/router-core': 1.162.1 '@tanstack/router-generator': 1.162.1 - '@tanstack/router-plugin': 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(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)) + '@tanstack/router-plugin': 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@tanstack/router-utils': 1.161.4 '@tanstack/start-client-core': 1.162.1 '@tanstack/start-server-core': 1.162.1(crossws@0.4.4(srvx@0.11.7)) @@ -10134,8 +10133,8 @@ snapshots: srvx: 0.11.7 tinyglobby: 0.2.15 ufo: 1.6.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) - 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)) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) + 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.9.0)) xmlbuilder2: 4.0.3 zod: 3.25.76 transitivePeerDependencies: @@ -10485,7 +10484,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-react@5.1.4(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))': + '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -10493,7 +10492,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -10514,13 +10513,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(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@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) '@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: @@ -10622,6 +10621,13 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -12675,7 +12681,7 @@ snapshots: nf3@0.3.10: {} - nitro-nightly@3.0.1-20260227-181935-bfbb207c(@azure/identity@4.13.0)(dotenv@16.6.1)(jiti@2.6.1)(lru-cache@11.2.6)(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)): + nitro-nightly@3.0.1-20260227-181935-bfbb207c(@azure/identity@4.13.0)(dotenv@16.6.1)(jiti@2.6.1)(lru-cache@11.2.6)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.4(srvx@0.11.7) @@ -12692,7 +12698,7 @@ snapshots: optionalDependencies: dotenv: 16.6.1 jiti: 2.6.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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13878,10 +13884,10 @@ snapshots: dependencies: react: 19.2.4 - uuid@11.1.0: {} - uuid@13.0.0: {} + uuid@14.0.0: {} + uuid@8.3.2: optional: true @@ -13894,13 +13900,13 @@ snapshots: validate-npm-package-name@5.0.1: {} - vite-node@3.2.4(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + vite-node@3.2.4(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0): 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@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -13936,18 +13942,18 @@ snapshots: - 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)): + 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.9.0)): dependencies: debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - 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): + vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -13961,7 +13967,7 @@ snapshots: jiti: 2.6.1 lightningcss: 1.31.1 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.9.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): dependencies: @@ -13979,15 +13985,15 @@ snapshots: 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)): + 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.9.0)): 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) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(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@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -14005,8 +14011,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - 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) - vite-node: 3.2.4(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -14263,6 +14269,8 @@ snapshots: yaml@2.8.2: {} + yaml@2.9.0: {} + yarn@1.22.22: {} yn@3.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ed98a17..6a1e138 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,11 +4,11 @@ packages: - plugins/* catalog: - '@powersync/management-client': ^0.1.0 - '@powersync/management-types': ^0.1.0 + '@powersync/management-client': ^0.1.1 + '@powersync/management-types': ^0.2.0 '@powersync/service-client': ^0.0.6 '@powersync/service-schema': ^1.21.0 - '@powersync/service-sync-rules': ^0.34.0 + '@powersync/service-sync-rules': ^0.36.0 '@powersync/service-types': ^0.15.2 patchedDependencies: From b4cbf1e298200408c7d812b9858ce175aa772ffc Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 27 May 2026 11:28:36 +0200 Subject: [PATCH 12/14] Make --project-id, --org-id, PROJECT_ID, ORG_ID no-ops --- cli/src/commands/link/cloud.ts | 16 ++--- cli/src/commands/pull/instance.ts | 12 +--- .../command-types/resolution-order.test.ts | 67 +++++++++---------- .../commands/deploy/service-config.test.ts | 23 ++----- cli/test/commands/deploy/sync-config.test.ts | 23 ++----- cli/test/commands/validate.test.ts | 8 --- .../src/command-types/CloudInstanceCommand.ts | 13 ++-- .../command-types/SharedInstanceCommand.ts | 17 ++--- .../cli-core/src/utils/ensure-service-type.ts | 2 +- .../src/utils/resolve-cloud-instance-link.ts | 7 +- 10 files changed, 71 insertions(+), 117 deletions(-) diff --git a/cli/src/commands/link/cloud.ts b/cli/src/commands/link/cloud.ts index 62a5ec3..19b43b1 100644 --- a/cli/src/commands/link/cloud.ts +++ b/cli/src/commands/link/cloud.ts @@ -37,13 +37,12 @@ export default class LinkCloud extends CloudInstanceCommand { }), 'org-id': Flags.string({ default: env.ORG_ID, - description: - 'Organization ID. Required with --create when the token has multiple orgs; optional when linking an existing instance.', + description: 'Organization ID. Required with --create when the token has multiple orgs.', required: false }), 'project-id': Flags.string({ default: env.PROJECT_ID, - description: 'Project ID. Required with --create; optional assertion when linking an existing instance.', + description: 'Project ID. Required with --create.', required: false }) }; @@ -131,11 +130,6 @@ export default class LinkCloud extends CloudInstanceCommand { 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, @@ -143,6 +137,12 @@ export default class LinkCloud extends CloudInstanceCommand { expectedType: ServiceType.CLOUD, projectDir: projectDirectory }); + + writeCloudLink(projectDirectory, { + instanceId: linked.instance_id, + orgId: linked.org_id, + projectId: linked.project_id + }); this.log(ux.colorize('green', `Updated ${directory}/${CLI_FILENAME} with Cloud instance link.`)); } } diff --git a/cli/src/commands/pull/instance.ts b/cli/src/commands/pull/instance.ts index 4ef4f2b..9041ee6 100644 --- a/cli/src/commands/pull/instance.ts +++ b/cli/src/commands/pull/instance.ts @@ -45,10 +45,8 @@ 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 { directory, 'instance-id': instanceId } = flags; const inputInstanceId = instanceId ?? env.INSTANCE_ID; - const inputOrgId = _orgId ?? env.ORG_ID; - const inputProjectId = projectId ?? env.PROJECT_ID; let resolvedLink: ResolvedCloudCLIConfig | undefined; let instanceConfig; @@ -63,9 +61,7 @@ export default class PullInstance extends CloudInstanceCommand { try { const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - instanceId: inputInstanceId, - orgId: inputOrgId, - projectId: inputProjectId + instanceId: inputInstanceId }); resolvedLink = validationResult.linked; instanceConfig = validationResult.instanceConfig; @@ -96,9 +92,7 @@ export default class PullInstance extends CloudInstanceCommand { try { const validationResult = await fetchCloudInstanceConfig({ cloudClient: this.client, - instanceId: inputInstanceId, - orgId: inputOrgId, - projectId: inputProjectId + instanceId: inputInstanceId }); resolvedLink = validationResult.linked; instanceConfig = validationResult.instanceConfig; diff --git a/cli/test/command-types/resolution-order.test.ts b/cli/test/command-types/resolution-order.test.ts index ba7bd6d..50dea8e 100644 --- a/cli/test/command-types/resolution-order.test.ts +++ b/cli/test/command-types/resolution-order.test.ts @@ -9,12 +9,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import DestroyCommand from '../../src/commands/destroy.js'; import FetchStatusCommand from '../../src/commands/fetch/status.js'; import { root } from '../helpers/root.js'; +import { managementClientMock, MOCK_CLOUD_IDS } from '../setup.js'; type EnvSnapshot = { API_URL: string | undefined; INSTANCE_ID: string | undefined; - ORG_ID: string | undefined; - PROJECT_ID: string | undefined; PS_ADMIN_TOKEN: string | undefined; }; @@ -60,8 +59,6 @@ describe('instance resolution order', () => { envSnapshot = { API_URL: env.API_URL, INSTANCE_ID: env.INSTANCE_ID, - ORG_ID: env.ORG_ID, - PROJECT_ID: env.PROJECT_ID, PS_ADMIN_TOKEN: env.PS_ADMIN_TOKEN }; }); @@ -70,14 +67,17 @@ describe('instance resolution order', () => { process.chdir(origCwd); env.API_URL = envSnapshot.API_URL; env.INSTANCE_ID = envSnapshot.INSTANCE_ID; - env.ORG_ID = envSnapshot.ORG_ID; env.PS_ADMIN_TOKEN = envSnapshot.PS_ADMIN_TOKEN; - env.PROJECT_ID = envSnapshot.PROJECT_ID; vi.restoreAllMocks(); rmSync(tmpRoot, { force: true, recursive: true }); }); - it('CloudInstanceCommand resolves cloud fields as flag → cli.yaml → env', async () => { + it('CloudInstanceCommand resolves instance_id as flag → cli.yaml → env; org/project from cli.yaml or API', async () => { + // getInstance echoes the requested id so we can verify which instance was resolved + managementClientMock.getInstance.mockImplementation(({ id }: { id: string }) => + Promise.resolve({ app_id: MOCK_CLOUD_IDS.projectId, id, org_id: MOCK_CLOUD_IDS.orgId }) + ); + const projectDir = join(tmpRoot, 'powersync'); const cliPath = join(projectDir, 'cli.yaml'); mkdirSync(projectDir, { recursive: true }); @@ -95,23 +95,18 @@ describe('instance resolution order', () => { ); env.INSTANCE_ID = IDS.env.instance; - env.ORG_ID = IDS.env.org; - env.PROJECT_ID = IDS.env.project; const loadProjectSpy = vi.spyOn(CloudInstanceCommand.prototype, 'loadProject'); - await runDestroyDirect([ - '--confirm=yes', - `--instance-id=${IDS.flag.instance}`, - `--org-id=${IDS.flag.org}`, - `--project-id=${IDS.flag.project}` - ]); + // Flag takes precedence for instance_id; org/project come from cli.yaml (API skipped when both present) + await runDestroyDirect(['--confirm=yes', `--instance-id=${IDS.flag.instance}`]); expect(loadProjectSpy).toHaveBeenCalledTimes(1); const fromFlag = await loadProjectSpy.mock.results[0]!.value; expect(fromFlag.linked.instance_id).toBe(IDS.flag.instance); - expect(fromFlag.linked.org_id).toBe(IDS.flag.org); - expect(fromFlag.linked.project_id).toBe(IDS.flag.project); + expect(fromFlag.linked.org_id).toBe(IDS.cli.org); + expect(fromFlag.linked.project_id).toBe(IDS.cli.project); + // cli.yaml is the source for all three fields when no flag is passed await runDestroyDirect(['--confirm=yes']); expect(loadProjectSpy).toHaveBeenCalledTimes(2); const fromCli = await loadProjectSpy.mock.results[1]!.value; @@ -119,13 +114,14 @@ describe('instance resolution order', () => { expect(fromCli.linked.org_id).toBe(IDS.cli.org); expect(fromCli.linked.project_id).toBe(IDS.cli.project); + // With no cli.yaml, instance_id comes from env and org/project are resolved via getInstance rmSync(cliPath, { force: true }); await runDestroyDirect(['--confirm=yes']); expect(loadProjectSpy).toHaveBeenCalledTimes(3); const fromEnv = await loadProjectSpy.mock.results[2]!.value; expect(fromEnv.linked.instance_id).toBe(IDS.env.instance); - expect(fromEnv.linked.org_id).toBe(IDS.env.org); - expect(fromEnv.linked.project_id).toBe(IDS.env.project); + expect(fromEnv.linked.org_id).toBe(MOCK_CLOUD_IDS.orgId); + expect(fromEnv.linked.project_id).toBe(MOCK_CLOUD_IDS.projectId); }); it('CloudInstanceCommand rejects invalid cloud BSON ObjectID values', async () => { @@ -185,7 +181,12 @@ describe('instance resolution order', () => { expect(fromEnv.linked.api_url).toBe('https://env.example.com'); }); - it('SharedInstanceCommand resolves cloud context and fields as flag → cli.yaml → env', async () => { + it('SharedInstanceCommand resolves cloud instance_id as flag → cli.yaml → env; org/project from cli.yaml or API', async () => { + // getInstance echoes the requested id so we can verify which instance was resolved + managementClientMock.getInstance.mockImplementation(({ id }: { id: string }) => + Promise.resolve({ app_id: MOCK_CLOUD_IDS.projectId, id, org_id: MOCK_CLOUD_IDS.orgId }) + ); + const projectDir = join(tmpRoot, 'powersync'); const cliPath = join(projectDir, 'cli.yaml'); mkdirSync(projectDir, { recursive: true }); @@ -204,25 +205,20 @@ describe('instance resolution order', () => { env.API_URL = 'https://env-self-hosted.example.com'; env.INSTANCE_ID = IDS.env.instance; - env.ORG_ID = IDS.env.org; - env.PROJECT_ID = IDS.env.project; const loadProjectSpy = vi.spyOn(SharedInstanceCommand.prototype, 'loadProject'); vi.spyOn(FetchStatusCommand.prototype, 'getCloudStatus').mockRejectedValue(new Error('expected-test-failure')); - await runFetchStatusDirect([ - '--output=json', - `--instance-id=${IDS.flag.instance}`, - `--org-id=${IDS.flag.org}`, - `--project-id=${IDS.flag.project}` - ]); + // Flag takes precedence for instance_id; org/project come from cli.yaml (API skipped when both present) + await runFetchStatusDirect(['--output=json', `--instance-id=${IDS.flag.instance}`]); expect(loadProjectSpy).toHaveBeenCalledTimes(1); - const fromCli = await loadProjectSpy.mock.results[0]!.value; - expect(fromCli.linked.type).toBe('cloud'); - expect(fromCli.linked.instance_id).toBe(IDS.flag.instance); - expect(fromCli.linked.org_id).toBe(IDS.flag.org); - expect(fromCli.linked.project_id).toBe(IDS.flag.project); + const fromFlag = await loadProjectSpy.mock.results[0]!.value; + expect(fromFlag.linked.type).toBe('cloud'); + expect(fromFlag.linked.instance_id).toBe(IDS.flag.instance); + expect(fromFlag.linked.org_id).toBe(IDS.cli.org); + expect(fromFlag.linked.project_id).toBe(IDS.cli.project); + // cli.yaml is the source for all three fields when no flag is passed await runFetchStatusDirect(['--output=json']); expect(loadProjectSpy).toHaveBeenCalledTimes(2); const fromLink = await loadProjectSpy.mock.results[1]!.value; @@ -231,6 +227,7 @@ describe('instance resolution order', () => { expect(fromLink.linked.org_id).toBe(IDS.cli.org); expect(fromLink.linked.project_id).toBe(IDS.cli.project); + // With no cli.yaml, instance_id comes from env and org/project are resolved via getInstance rmSync(cliPath, { force: true }); env.API_URL = undefined; await runFetchStatusDirect(['--output=json']); @@ -238,7 +235,7 @@ describe('instance resolution order', () => { const fromEnv = await loadProjectSpy.mock.results[2]!.value; expect(fromEnv.linked.type).toBe('cloud'); expect(fromEnv.linked.instance_id).toBe(IDS.env.instance); - expect(fromEnv.linked.org_id).toBe(IDS.env.org); - expect(fromEnv.linked.project_id).toBe(IDS.env.project); + expect(fromEnv.linked.org_id).toBe(MOCK_CLOUD_IDS.orgId); + expect(fromEnv.linked.project_id).toBe(MOCK_CLOUD_IDS.projectId); }); }); diff --git a/cli/test/commands/deploy/service-config.test.ts b/cli/test/commands/deploy/service-config.test.ts index 1508e49..aaa0baf 100644 --- a/cli/test/commands/deploy/service-config.test.ts +++ b/cli/test/commands/deploy/service-config.test.ts @@ -64,7 +64,7 @@ function writeLinkYaml(projectDir: string): void { describe('deploy:service-config', () => { let tmpDir: string; let origCwd: string; - let origEnv: { INSTANCE_ID?: string; ORG_ID?: string; PROJECT_ID?: string; PS_ADMIN_TOKEN?: string }; + let origEnv: { INSTANCE_ID?: string; PS_ADMIN_TOKEN?: string }; beforeEach(() => { resetManagementClientMocks(); @@ -72,8 +72,6 @@ describe('deploy:service-config', () => { origCwd = process.cwd(); origEnv = { INSTANCE_ID: env.INSTANCE_ID, - ORG_ID: env.ORG_ID, - PROJECT_ID: env.PROJECT_ID, PS_ADMIN_TOKEN: env.PS_ADMIN_TOKEN }; @@ -81,8 +79,6 @@ describe('deploy:service-config', () => { process.chdir(tmpDir); env.PS_ADMIN_TOKEN = 'test-token'; env.INSTANCE_ID = undefined; - env.ORG_ID = undefined; - env.PROJECT_ID = undefined; managementClientMock.getInstanceConfig.mockResolvedValue(MOCK_CLOUD_CONFIG); managementClientMock.getInstanceStatus.mockResolvedValue({ operations: [], provisioned: true }); @@ -100,8 +96,6 @@ describe('deploy:service-config', () => { env.PS_ADMIN_TOKEN = origEnv.PS_ADMIN_TOKEN; env.INSTANCE_ID = origEnv.INSTANCE_ID; - env.ORG_ID = origEnv.ORG_ID; - env.PROJECT_ID = origEnv.PROJECT_ID; if (tmpDir && existsSync(tmpDir)) rmSync(tmpDir, { recursive: true }); }); @@ -119,31 +113,22 @@ describe('deploy:service-config', () => { expect(result.error?.message).toMatch(/mock deploy failure/); }); - it('works with --instance-id / --project-id / --org-id flags (no sync-config.yaml)', async () => { + it('works with --instance-id flag (no sync-config.yaml)', async () => { const projectDir = makeProjectDir(tmpDir); writeServiceYaml(projectDir); // No cli.yaml, no sync-config.yaml - const result = await runServiceConfigDirect([ - '--instance-id', - INSTANCE_ID, - '--project-id', - PROJECT_ID, - '--org-id', - ORG_ID - ]); + const result = await runServiceConfigDirect(['--instance-id', INSTANCE_ID]); expect(result.error?.message).toMatch(/mock deploy failure/); }); - it('works with INSTANCE_ID / ORG_ID / PROJECT_ID env vars (no sync-config.yaml)', async () => { + it('works with INSTANCE_ID env var (no sync-config.yaml)', async () => { const projectDir = makeProjectDir(tmpDir); writeServiceYaml(projectDir); // No cli.yaml, no sync-config.yaml env.INSTANCE_ID = INSTANCE_ID; - env.ORG_ID = ORG_ID; - env.PROJECT_ID = PROJECT_ID; const result = await runServiceConfigDirect(); diff --git a/cli/test/commands/deploy/sync-config.test.ts b/cli/test/commands/deploy/sync-config.test.ts index 2ddad81..63a8521 100644 --- a/cli/test/commands/deploy/sync-config.test.ts +++ b/cli/test/commands/deploy/sync-config.test.ts @@ -50,7 +50,7 @@ function writeLinkYaml(projectDir: string): void { describe('deploy:sync-config', () => { let tmpDir: string; let origCwd: string; - let origEnv: { INSTANCE_ID?: string; ORG_ID?: string; PROJECT_ID?: string; PS_ADMIN_TOKEN?: string }; + let origEnv: { INSTANCE_ID?: string; PS_ADMIN_TOKEN?: string }; beforeEach(() => { resetManagementClientMocks(); @@ -58,8 +58,6 @@ describe('deploy:sync-config', () => { origCwd = process.cwd(); origEnv = { INSTANCE_ID: env.INSTANCE_ID, - ORG_ID: env.ORG_ID, - PROJECT_ID: env.PROJECT_ID, PS_ADMIN_TOKEN: env.PS_ADMIN_TOKEN }; @@ -67,8 +65,6 @@ describe('deploy:sync-config', () => { process.chdir(tmpDir); env.PS_ADMIN_TOKEN = 'test-token'; env.INSTANCE_ID = undefined; - env.ORG_ID = undefined; - env.PROJECT_ID = undefined; managementClientMock.getInstanceConfig.mockResolvedValue(MOCK_CLOUD_CONFIG); managementClientMock.getInstanceStatus.mockResolvedValue({ operations: [], provisioned: true }); @@ -82,8 +78,6 @@ describe('deploy:sync-config', () => { env.PS_ADMIN_TOKEN = origEnv.PS_ADMIN_TOKEN; env.INSTANCE_ID = origEnv.INSTANCE_ID; - env.ORG_ID = origEnv.ORG_ID; - env.PROJECT_ID = origEnv.PROJECT_ID; if (tmpDir && existsSync(tmpDir)) rmSync(tmpDir, { recursive: true }); }); @@ -101,31 +95,22 @@ describe('deploy:sync-config', () => { expect(result.error?.message).toMatch(/mock deploy failure/); }); - it('works with --instance-id / --project-id / --org-id flags (no service.yaml, no cli.yaml)', async () => { + it('works with --instance-id flag (no service.yaml, no cli.yaml)', async () => { const projectDir = makeProjectDir(tmpDir); // No service.yaml, no cli.yaml writeFileSync(join(projectDir, SYNC_FILENAME), SYNC_CONFIG_CONTENT, 'utf8'); - const result = await runSyncConfigDirect([ - '--instance-id', - INSTANCE_ID, - '--project-id', - PROJECT_ID, - '--org-id', - ORG_ID - ]); + const result = await runSyncConfigDirect(['--instance-id', INSTANCE_ID]); expect(result.error?.message).toMatch(/mock deploy failure/); }); - it('works with INSTANCE_ID / ORG_ID / PROJECT_ID env vars (no service.yaml, no cli.yaml)', async () => { + it('works with INSTANCE_ID env var (no service.yaml, no cli.yaml)', async () => { const projectDir = makeProjectDir(tmpDir); // No service.yaml, no cli.yaml writeFileSync(join(projectDir, SYNC_FILENAME), SYNC_CONFIG_CONTENT, 'utf8'); env.INSTANCE_ID = INSTANCE_ID; - env.ORG_ID = ORG_ID; - env.PROJECT_ID = PROJECT_ID; const result = await runSyncConfigDirect(); expect(result.error?.message).toMatch(/mock deploy failure/); diff --git a/cli/test/commands/validate.test.ts b/cli/test/commands/validate.test.ts index 220e158..123be8e 100644 --- a/cli/test/commands/validate.test.ts +++ b/cli/test/commands/validate.test.ts @@ -20,8 +20,6 @@ const emptySyncValidation = { type EnvSnapshot = { API_URL: string | undefined; INSTANCE_ID: string | undefined; - ORG_ID: string | undefined; - PROJECT_ID: string | undefined; PS_ADMIN_TOKEN: string | undefined; }; @@ -35,8 +33,6 @@ describe('validate', () => { origEnv = { API_URL: env.API_URL, INSTANCE_ID: env.INSTANCE_ID, - ORG_ID: env.ORG_ID, - PROJECT_ID: env.PROJECT_ID, PS_ADMIN_TOKEN: env.PS_ADMIN_TOKEN }; tmpRoot = mkdtempSync(join(tmpdir(), 'validate-cmd-test-')); @@ -47,8 +43,6 @@ describe('validate', () => { process.chdir(origCwd); env.API_URL = origEnv.API_URL; env.INSTANCE_ID = origEnv.INSTANCE_ID; - env.ORG_ID = origEnv.ORG_ID; - env.PROJECT_ID = origEnv.PROJECT_ID; env.PS_ADMIN_TOKEN = origEnv.PS_ADMIN_TOKEN; vi.restoreAllMocks(); if (tmpRoot && existsSync(tmpRoot)) { @@ -123,8 +117,6 @@ describe('validate', () => { ); env.PS_ADMIN_TOKEN = 'token'; env.INSTANCE_ID = undefined; - env.ORG_ID = undefined; - env.PROJECT_ID = undefined; managementClientMock.getInstanceConfig.mockResolvedValue({ config: { diff --git a/packages/cli-core/src/command-types/CloudInstanceCommand.ts b/packages/cli-core/src/command-types/CloudInstanceCommand.ts index 5f26383..734ae71 100644 --- a/packages/cli-core/src/command-types/CloudInstanceCommand.ts +++ b/packages/cli-core/src/command-types/CloudInstanceCommand.ts @@ -66,20 +66,20 @@ export abstract class CloudInstanceCommand extends InstanceCommand { }), 'org-id': Flags.string({ deprecated: { - message: - '--org-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + message: '--org-id is a no-op. Organization ID is resolved automatically.' }, description: '[Deprecated] Organization ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, + hidden: true, required: false }), 'project-id': Flags.string({ deprecated: { - message: - '--project-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + message: '--project-id is a no-op. Project ID is resolved automatically.' }, description: '[Deprecated] Project ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, + hidden: true, required: false }) }; @@ -163,9 +163,10 @@ export abstract class CloudInstanceCommand extends InstanceCommand { } } + // Only instance_id is accepted as a CLI flag - project_id and org_id overrides must come from cli.yaml 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; - const org_id = flags['org-id'] ?? (rawLink?.org_id as string | undefined) ?? env.ORG_ID; + const project_id = rawLink?.project_id as string | undefined; + const org_id = rawLink?.org_id as string | undefined; if (instance_id != null || project_id != null || org_id != null) { this.ensureObjectIdIfPresent(instance_id, '--instance-id'); diff --git a/packages/cli-core/src/command-types/SharedInstanceCommand.ts b/packages/cli-core/src/command-types/SharedInstanceCommand.ts index 68cff9f..bb384a9 100644 --- a/packages/cli-core/src/command-types/SharedInstanceCommand.ts +++ b/packages/cli-core/src/command-types/SharedInstanceCommand.ts @@ -75,20 +75,20 @@ export abstract class SharedInstanceCommand extends InstanceCommand { }), 'org-id': Flags.string({ deprecated: { - message: - '--org-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + message: '--org-id is a no-op. Organization ID is resolved automatically.' }, description: '[Cloud] [Deprecated] Organization ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, + hidden: true, required: false }), 'project-id': Flags.string({ deprecated: { - message: - '--project-id is automatically resolved from --instance-id. This option currently remains as a manual override, but may be removed in a future version.' + message: '--project-id is a no-op. Project ID is resolved automatically.' }, description: '[Cloud] [Deprecated] Project ID. Automatically resolved from --instance-id.', helpGroup: HelpGroup.CLOUD_PROJECT, + hidden: true, required: false }), ...InstanceCommand.baseFlags @@ -134,7 +134,7 @@ export abstract class SharedInstanceCommand extends InstanceCommand { const linkPath = join(projectDir, CLI_FILENAME); // 1) Context type: flags first, then link file, then env (see class JSDoc for resolution order). - const hasCloudFlagInputs = flags['instance-id'] || flags['org-id'] || flags['project-id']; + const hasCloudFlagInputs = flags['instance-id']; const hasSelfHostedFlagInputs = flags['api-url']; if (hasCloudFlagInputs && hasSelfHostedFlagInputs) { @@ -161,7 +161,7 @@ export abstract class SharedInstanceCommand extends InstanceCommand { // If type still not set, use env inputs. if (!projectType) { - const hasCloudEnvInputs = env.INSTANCE_ID || env.ORG_ID || env.PROJECT_ID; + const hasCloudEnvInputs = env.INSTANCE_ID; const hasSelfHostedEnvInputs = env.API_URL; if (hasCloudEnvInputs && hasSelfHostedEnvInputs) { @@ -201,8 +201,9 @@ export abstract class SharedInstanceCommand extends InstanceCommand { await resolveCloudInstanceLink({ client: this.cloudClient, instanceId, - orgId: flags['org-id'] ?? _rawCloudCLIConfig.org_id ?? env.ORG_ID, - projectId: flags['project-id'] ?? _rawCloudCLIConfig.project_id ?? env.PROJECT_ID + // orgId and projectId can either be set via cli.yaml or resolved via instanceId + orgId: _rawCloudCLIConfig.org_id, + projectId: _rawCloudCLIConfig.project_id }) ); } catch (error) { diff --git a/packages/cli-core/src/utils/ensure-service-type.ts b/packages/cli-core/src/utils/ensure-service-type.ts index 0ae86bd..55a0c21 100644 --- a/packages/cli-core/src/utils/ensure-service-type.ts +++ b/packages/cli-core/src/utils/ensure-service-type.ts @@ -39,7 +39,7 @@ export function ensureServiceTypeMatches(options: EnsureServiceTypeMatchesOption const service = parseYamlFile(servicePath); const serviceJson = service.contents?.toJSON(); - if (serviceJson?._type === undefined || serviceJson?._type === null) { + if (serviceJson?._type == null) { command.styledError({ message: `${SERVICE_FILENAME} in "./${directoryLabel}/" is missing \`_type\`. Add \`_type: ${expectedType}\` for this command.` }); 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 4331a44..f4002ec 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -12,12 +12,15 @@ export type ResolveCloudInstanceLinkInput = { /** * Resolves the full Cloud link from an instance ID. If org/project IDs are missing, fetches them from the instance. + * + * Note that this function does NOT check if the org/project IDs reference valid destinations if provided manually. */ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkInput): Promise { const { client, instanceId, orgId, projectId } = input; ensureObjectId(instanceId, '--instance-id'); + // Skip API request when org and project IDs are both provided; otherwise fetch them via getInstance if (orgId && projectId) { ensureObjectId(orgId, '--org-id'); ensureObjectId(projectId, '--project-id'); @@ -29,10 +32,6 @@ 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 }); From 54cf962bdd3d2fce43dcdf5e68f5f2f6d75cef47 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 27 May 2026 14:18:23 +0200 Subject: [PATCH 13/14] Comment --- .../cli-core/src/utils/resolve-cloud-instance-link.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 f4002ec..ef6e211 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -20,7 +20,14 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn ensureObjectId(instanceId, '--instance-id'); - // Skip API request when org and project IDs are both provided; otherwise fetch them via getInstance + // Skip the API request when org and project IDs are **both** provided; otherwise ignore + // the provided values and re-fetch them via getInstance. + // + // If both values are provided (i.e. if the values are present in cli.yaml), we trust that the + // org and project ID are both correct for the given instance. This lets us save an API call, and + // if we are incorrect, then we will receive a server-side validation error later in the chain. + // + // In most cases the ID fields in the cli.yaml file are generated by the CLI and will therefore contain the correct values. if (orgId && projectId) { ensureObjectId(orgId, '--org-id'); ensureObjectId(projectId, '--project-id'); From c7244e3a93d3ead1c2006b4c058313ee7fae758c Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Wed, 27 May 2026 14:38:50 +0200 Subject: [PATCH 14/14] Test fixes --- cli/test/commands/pull/instance.test.ts | 3 +++ packages/cli-core/src/utils/resolve-cloud-instance-link.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/test/commands/pull/instance.test.ts b/cli/test/commands/pull/instance.test.ts index 65b697b..afed8b4 100644 --- a/cli/test/commands/pull/instance.test.ts +++ b/cli/test/commands/pull/instance.test.ts @@ -34,6 +34,7 @@ const MOCK_CONFIG_WITH_EMPTY_JWKS_KEYS = { const mockCloudClient = { deployInstance: vi.fn(), + getInstance: vi.fn(), getInstanceConfig: vi.fn() }; @@ -79,6 +80,8 @@ describe('pull instance', () => { origCwd = process.cwd(); tmpDir = mkdtempSync(join(tmpdir(), 'pull-instance-test-')); process.chdir(tmpDir); + mockCloudClient.getInstance.mockReset(); + mockCloudClient.getInstance.mockResolvedValue({ app_id: PROJECT_ID, id: INSTANCE_ID, org_id: ORG_ID }); mockCloudClient.getInstanceConfig.mockReset(); mockCloudClient.getInstanceConfig.mockRejectedValue(new Error('network error')); accountsClientMock.getOrganization.mockResolvedValue({ id: ORG_ID, label: 'Test Org' }); 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 ef6e211..9bf94c5 100644 --- a/packages/cli-core/src/utils/resolve-cloud-instance-link.ts +++ b/packages/cli-core/src/utils/resolve-cloud-instance-link.ts @@ -19,6 +19,8 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn const { client, instanceId, orgId, projectId } = input; ensureObjectId(instanceId, '--instance-id'); + if (orgId) ensureObjectId(orgId, '--org-id'); + if (projectId) ensureObjectId(projectId, '--project-id'); // Skip the API request when org and project IDs are **both** provided; otherwise ignore // the provided values and re-fetch them via getInstance. @@ -29,8 +31,6 @@ export async function resolveCloudInstanceLink(input: ResolveCloudInstanceLinkIn // // In most cases the ID fields in the cli.yaml file are generated by the CLI and will therefore contain the correct values. if (orgId && projectId) { - ensureObjectId(orgId, '--org-id'); - ensureObjectId(projectId, '--project-id'); return { instance_id: instanceId, org_id: orgId,