@syncular/cli 0.0.0-44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@syncular/cli",
3
+ "version": "0.0.0-44",
4
+ "description": "Unified CLI for Syncular OSS and Spaces workflows",
5
+ "license": "MIT",
6
+ "author": "Benjamin Kniffler",
7
+ "homepage": "https://syncular.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/syncular/syncular-spaces.git",
11
+ "directory": "cli/bin"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/syncular/syncular-spaces/issues"
15
+ },
16
+ "private": false,
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "type": "module",
21
+ "bin": {
22
+ "syncular": "./src/index.ts",
23
+ "synclr": "./src/index.ts"
24
+ },
25
+ "files": [
26
+ "src"
27
+ ],
28
+ "scripts": {
29
+ "tsgo": "tsgo --noEmit",
30
+ "test": "bun test test",
31
+ "doctor": "bun src/index.ts doctor",
32
+ "console": "bun src/index.ts console",
33
+ "login": "bun src/index.ts login",
34
+ "logout": "bun src/index.ts logout",
35
+ "whoami": "bun src/index.ts whoami",
36
+ "create:space": "bun src/index.ts create-space",
37
+ "create": "bun src/index.ts create",
38
+ "dev": "bun src/index.ts dev",
39
+ "typegen": "bun src/index.ts typegen",
40
+ "migrate:status": "bun src/index.ts migrate-status",
41
+ "migrate:up": "bun src/index.ts migrate-up",
42
+ "build": "bun src/index.ts build",
43
+ "eject": "bun src/index.ts eject",
44
+ "target": "bun src/index.ts target",
45
+ "deploy": "bun src/index.ts deploy",
46
+ "verify": "bun src/index.ts verify",
47
+ "deployments:list": "bun src/index.ts deployments",
48
+ "rollback": "bun src/index.ts rollback",
49
+ "release": "bun ../config/bin/publish.ts"
50
+ },
51
+ "dependencies": {
52
+ "@syncular/cli-buildpack-contract-worker": "0.0.0-44",
53
+ "@syncular/cli-core": "0.0.0-44",
54
+ "@syncular/cli-runtime-dev": "0.0.0-44",
55
+ "@syncular/cli-template-spaces-app": "0.0.0-44",
56
+ "@syncular/cli-template-spaces-demo": "0.0.0-44",
57
+ "@syncular/cli-template-syncular-demo": "0.0.0-44",
58
+ "@syncular/cli-template-syncular-libraries": "0.0.0-44",
59
+ "cross-keychain": "^1.1.0",
60
+ "ink": "^6.8.0",
61
+ "react": "^19.2.4",
62
+ "syncular": "*"
63
+ },
64
+ "devDependencies": {
65
+ "@types/bun": "^1.3.9",
66
+ "@types/react": "^19.2.14"
67
+ }
68
+ }
package/src/args.ts ADDED
@@ -0,0 +1,112 @@
1
+ import process from 'node:process';
2
+ import type { ParsedArgs, RootCommand } from './types';
3
+
4
+ const IMPLICIT_TRUE_FLAGS = new Set([
5
+ '--help',
6
+ '--version',
7
+ '--interactive',
8
+ '--no-interactive',
9
+ '--forms',
10
+ '--json',
11
+ '--force',
12
+ '--watch',
13
+ '--open',
14
+ '--dry-run',
15
+ '--yes',
16
+ ]);
17
+
18
+ export function parseArgs(argv: string[]): ParsedArgs {
19
+ const flags = new Set<string>();
20
+ const flagValues = new Map<string, string>();
21
+ const positionals: string[] = [];
22
+
23
+ for (let index = 0; index < argv.length; index += 1) {
24
+ const arg = argv[index];
25
+ if (!arg) {
26
+ continue;
27
+ }
28
+
29
+ if (!arg.startsWith('-')) {
30
+ positionals.push(arg);
31
+ continue;
32
+ }
33
+
34
+ if (arg.startsWith('--')) {
35
+ const eqIndex = arg.indexOf('=');
36
+ if (eqIndex > 0) {
37
+ const key = arg.slice(0, eqIndex);
38
+ const value = arg.slice(eqIndex + 1);
39
+ flags.add(key);
40
+ flagValues.set(key, value);
41
+ continue;
42
+ }
43
+
44
+ flags.add(arg);
45
+ const next = argv[index + 1];
46
+ if (
47
+ typeof next === 'string' &&
48
+ next.length > 0 &&
49
+ !next.startsWith('-')
50
+ ) {
51
+ flagValues.set(arg, next);
52
+ index += 1;
53
+ } else if (IMPLICIT_TRUE_FLAGS.has(arg)) {
54
+ flagValues.set(arg, 'true');
55
+ }
56
+ continue;
57
+ }
58
+
59
+ flags.add(arg);
60
+ }
61
+
62
+ const commandCandidate = positionals[0] ?? null;
63
+ const subcommand = positionals[1] ?? null;
64
+
65
+ return {
66
+ command: normalizeRootCommand(commandCandidate),
67
+ subcommand,
68
+ flags,
69
+ flagValues,
70
+ positionals,
71
+ };
72
+ }
73
+
74
+ function normalizeRootCommand(value: string | null): RootCommand | null {
75
+ if (!value) {
76
+ return null;
77
+ }
78
+
79
+ if (value === 'help') return 'help';
80
+ if (value === 'version') return 'version';
81
+ if (value === 'doctor') return 'doctor';
82
+ if (value === 'console') return 'console';
83
+ if (value === 'login') return 'login';
84
+ if (value === 'logout') return 'logout';
85
+ if (value === 'whoami') return 'whoami';
86
+ if (value === 'create-space') return 'create-space';
87
+ if (value === 'create') return 'create';
88
+ if (value === 'dev') return 'dev';
89
+ if (value === 'typegen') return 'typegen';
90
+ if (value === 'migrate-status') return 'migrate-status';
91
+ if (value === 'migrate-up') return 'migrate-up';
92
+ if (value === 'build') return 'build';
93
+ if (value === 'eject') return 'eject';
94
+ if (value === 'target') return 'target';
95
+ if (value === 'deploy') return 'deploy';
96
+ if (value === 'verify') return 'verify';
97
+ if (value === 'rollback') return 'rollback';
98
+ if (value === 'deployments') return 'deployments';
99
+ if (value === 'interactive') return 'interactive';
100
+
101
+ return null;
102
+ }
103
+
104
+ export function shouldRunInteractive(args: ParsedArgs): boolean {
105
+ if (args.flags.has('--no-interactive')) {
106
+ return false;
107
+ }
108
+ if (args.flags.has('--interactive')) {
109
+ return true;
110
+ }
111
+ return process.stdout.isTTY === true;
112
+ }
@@ -0,0 +1,57 @@
1
+ import { deletePassword, getPassword, setPassword } from 'cross-keychain';
2
+
3
+ const SYNCULAR_CLI_KEYCHAIN_SERVICE = 'syncular-cli';
4
+
5
+ function sanitizeAccountSegment(value: string): string {
6
+ const sanitized = value.replace(/[^A-Za-z0-9._@-]/g, '-');
7
+ const collapsed = sanitized.replace(/-+/g, '-').replace(/^-+|-+$/g, '');
8
+ return collapsed.length > 0 ? collapsed : 'default';
9
+ }
10
+
11
+ function controlPlaneAccount(controlPlaneBase: string): string {
12
+ try {
13
+ const url = new URL(controlPlaneBase);
14
+ return `control-plane-${sanitizeAccountSegment(url.host)}`;
15
+ } catch {
16
+ return `control-plane-${sanitizeAccountSegment(controlPlaneBase)}`;
17
+ }
18
+ }
19
+
20
+ export async function readStoredControlPlaneToken(
21
+ controlPlaneBase: string
22
+ ): Promise<string | null> {
23
+ try {
24
+ const token = await getPassword(
25
+ SYNCULAR_CLI_KEYCHAIN_SERVICE,
26
+ controlPlaneAccount(controlPlaneBase)
27
+ );
28
+ const normalized = token?.trim() ?? '';
29
+ return normalized.length > 0 ? normalized : null;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export async function writeStoredControlPlaneToken(args: {
36
+ controlPlaneBase: string;
37
+ token: string;
38
+ }): Promise<void> {
39
+ await setPassword(
40
+ SYNCULAR_CLI_KEYCHAIN_SERVICE,
41
+ controlPlaneAccount(args.controlPlaneBase),
42
+ args.token
43
+ );
44
+ }
45
+
46
+ export async function clearStoredControlPlaneToken(
47
+ controlPlaneBase: string
48
+ ): Promise<void> {
49
+ try {
50
+ await deletePassword(
51
+ SYNCULAR_CLI_KEYCHAIN_SERVICE,
52
+ controlPlaneAccount(controlPlaneBase)
53
+ );
54
+ } catch {
55
+ // ignore missing keychain entries
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ export * from './registry';
2
+ export * from './types';
@@ -0,0 +1,47 @@
1
+ import type { ContractBuildpack } from '@syncular/cli-core';
2
+ import { BUILDPACK_EXTENSION_MANIFEST } from '../extensions';
3
+
4
+ type BuildpackManifest = typeof BUILDPACK_EXTENSION_MANIFEST;
5
+
6
+ function buildBuildpackRegistry(manifest: BuildpackManifest) {
7
+ const entries = Object.entries(manifest);
8
+ const seenBuildpackIds = new Set<string>();
9
+
10
+ for (const [registryKey, manifestEntry] of entries) {
11
+ const buildpackId = manifestEntry.buildpack.id;
12
+ if (buildpackId.length === 0) {
13
+ throw new Error(`Buildpack "${registryKey}" has an empty id.`);
14
+ }
15
+
16
+ if (seenBuildpackIds.has(buildpackId)) {
17
+ throw new Error(`Duplicate buildpack id in manifest: "${buildpackId}".`);
18
+ }
19
+ seenBuildpackIds.add(buildpackId);
20
+ }
21
+
22
+ return Object.freeze(
23
+ Object.fromEntries(
24
+ entries.map(([registryKey, manifestEntry]) => [
25
+ registryKey,
26
+ manifestEntry.buildpack,
27
+ ])
28
+ )
29
+ ) as {
30
+ [K in keyof BuildpackManifest]: BuildpackManifest[K]['buildpack'];
31
+ };
32
+ }
33
+
34
+ const buildpackRegistry = buildBuildpackRegistry(BUILDPACK_EXTENSION_MANIFEST);
35
+
36
+ const REGISTERED_BUILDPACKS = Object.freeze(Object.values(buildpackRegistry));
37
+ const BUILDPACK_IDS = Object.freeze(
38
+ REGISTERED_BUILDPACKS.map((buildpack) => buildpack.id)
39
+ ) as readonly string[];
40
+
41
+ export function listBuildpackIds(): readonly string[] {
42
+ return BUILDPACK_IDS;
43
+ }
44
+
45
+ export function getBuildpackById(id: string): ContractBuildpack | null {
46
+ return REGISTERED_BUILDPACKS.find((buildpack) => buildpack.id === id) ?? null;
47
+ }
@@ -0,0 +1 @@
1
+ export type { ContractBuildpack } from '@syncular/cli-core';