instant-cli 1.0.22 → 1.0.23-branch-cli-codex-update.25390498340.1

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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/__tests__/authClientAddGoogle.test.ts +92 -0
  3. package/__tests__/authClientList.test.ts +90 -0
  4. package/__tests__/authClientUpdate.test.ts +583 -0
  5. package/__tests__/oauthMock.ts +9 -1
  6. package/__tests__/redirectUriPrompt.test.ts +26 -0
  7. package/dist/commands/auth/client/add.d.ts +1 -2
  8. package/dist/commands/auth/client/add.d.ts.map +1 -1
  9. package/dist/commands/auth/client/add.js +173 -276
  10. package/dist/commands/auth/client/add.js.map +1 -1
  11. package/dist/commands/auth/client/delete.d.ts +1 -2
  12. package/dist/commands/auth/client/delete.d.ts.map +1 -1
  13. package/dist/commands/auth/client/delete.js +8 -18
  14. package/dist/commands/auth/client/delete.js.map +1 -1
  15. package/dist/commands/auth/client/list.d.ts.map +1 -1
  16. package/dist/commands/auth/client/list.js +11 -2
  17. package/dist/commands/auth/client/list.js.map +1 -1
  18. package/dist/commands/auth/client/shared.d.ts +72 -0
  19. package/dist/commands/auth/client/shared.d.ts.map +1 -0
  20. package/dist/commands/auth/client/shared.js +145 -0
  21. package/dist/commands/auth/client/shared.js.map +1 -0
  22. package/dist/commands/auth/client/update.d.ts +8 -0
  23. package/dist/commands/auth/client/update.d.ts.map +1 -0
  24. package/dist/commands/auth/client/update.js +515 -0
  25. package/dist/commands/auth/client/update.js.map +1 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +60 -13
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/oauth.d.ts +114 -7
  31. package/dist/lib/oauth.d.ts.map +1 -1
  32. package/dist/lib/oauth.js +51 -1
  33. package/dist/lib/oauth.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/commands/auth/client/add.ts +251 -330
  36. package/src/commands/auth/client/delete.ts +8 -20
  37. package/src/commands/auth/client/list.ts +21 -2
  38. package/src/commands/auth/client/shared.ts +195 -0
  39. package/src/commands/auth/client/update.ts +853 -0
  40. package/src/index.ts +74 -13
  41. package/src/lib/oauth.ts +83 -1
@@ -1,9 +1,9 @@
1
1
  import { Effect } from 'effect';
2
2
  import type { authClientDeleteDef, OptsFromCommand } from '../../../index.ts';
3
3
  import { BadArgsError } from '../../../errors.ts';
4
- import { getAppsAuth } from '../../../lib/oauth.ts';
4
+ import { findClientByIdOrName, getAppsAuth } from '../../../lib/oauth.ts';
5
5
  import { GlobalOpts } from '../../../context/globalOpts.ts';
6
- import { runUIEffect, UIError } from '../../../lib/ui.ts';
6
+ import { runUIEffect } from '../../../lib/ui.ts';
7
7
  import { UI } from '../../../ui/index.ts';
8
8
  import chalk from 'chalk';
9
9
  import { InstantHttpAuthed, withCommand } from '../../../lib/http.ts';
@@ -12,13 +12,8 @@ import { CurrentApp } from '../../../context/currentApp.ts';
12
12
  export const authClientDeleteCmd = Effect.fn(function* (
13
13
  opts: OptsFromCommand<typeof authClientDeleteDef>,
14
14
  ) {
15
- if (opts.id && opts.name) {
16
- return yield* BadArgsError.make({
17
- message: 'Cannot specify both --id and --name',
18
- });
19
- }
20
- const info = yield* getAppsAuth();
21
15
  if (!opts.id && !opts.name) {
16
+ const info = yield* getAppsAuth();
22
17
  // user must pick manually
23
18
  const { yes } = yield* GlobalOpts;
24
19
  if (yes) {
@@ -44,18 +39,11 @@ export const authClientDeleteCmd = Effect.fn(function* (
44
39
  );
45
40
 
46
41
  yield* deleteOauthClient(picked.id);
47
- }
48
-
49
- if (opts.id) {
50
- yield* deleteOauthClient(opts.id);
51
- }
52
- if (opts.name) {
53
- const client = info.oauth_clients?.find((c) => c.client_name === opts.name);
54
- if (!client) {
55
- return yield* BadArgsError.make({
56
- message: `OAuth client not found: ${opts.name}`,
57
- });
58
- }
42
+ } else {
43
+ const { client } = yield* findClientByIdOrName({
44
+ id: opts.id,
45
+ name: opts.name,
46
+ });
59
47
  yield* deleteOauthClient(client.id);
60
48
  }
61
49
 
@@ -34,8 +34,27 @@ export const authClientListCmd = Effect.fn(function* (
34
34
  yield* Effect.log(
35
35
  ` Provider: ${provider?.provider_name ?? client.provider_id}`,
36
36
  );
37
+ const clientAppType = client.meta?.appType;
38
+ if (clientAppType) {
39
+ yield* Effect.log(` App type: ${clientAppType}`);
40
+ }
41
+ yield* Effect.log(
42
+ ` Credentials: ${client.use_shared_credentials ? 'Instant dev credentials' : 'custom'}`,
43
+ );
37
44
  yield* Effect.log(` ID: ${client.id}`);
38
- yield* Effect.log(` Client id: ${formatValue(client.client_id)}`);
39
- yield* Effect.log(` Redirect URL: ${formatValue(client.redirect_to)}`);
45
+ yield* Effect.log(
46
+ ` Client id: ${
47
+ client.use_shared_credentials
48
+ ? 'managed by Instant'
49
+ : formatValue(client.client_id)
50
+ }`,
51
+ );
52
+ yield* Effect.log(
53
+ ` Redirect URL: ${
54
+ client.use_shared_credentials
55
+ ? 'localhost and Expo allowed automatically'
56
+ : formatValue(client.redirect_to)
57
+ }`,
58
+ );
40
59
  }
41
60
  });
@@ -0,0 +1,195 @@
1
+ import { FileSystem } from '@effect/platform';
2
+ import { Effect } from 'effect';
3
+ import { DEFAULT_OAUTH_CALLBACK_URL } from '@instantdb/platform';
4
+ import chalk from 'chalk';
5
+ import { BadArgsError } from '../../../errors.ts';
6
+ import { link } from '../../../logging.ts';
7
+ import { stripFirstBlankLine, validateRequired } from '../../../lib/ui.ts';
8
+ import { UI } from '../../../ui/index.ts';
9
+
10
+ type EmptyPromptArgs = Record<string, never>;
11
+
12
+ export const getFlag = (opts: Record<string, unknown>, flag: string) =>
13
+ opts[flag];
14
+
15
+ export const hasFlag = (opts: Record<string, unknown>, flag: string) =>
16
+ flag in opts;
17
+
18
+ export const hasAnyFlag = (opts: Record<string, unknown>, flags: string[]) =>
19
+ flags.some((flag) => hasFlag(opts, flag));
20
+
21
+ export const isTrueFlag = (value: unknown) =>
22
+ value === true || value === 'true';
23
+
24
+ export const getMetaString = (meta: unknown, key: string) => {
25
+ if (!meta || typeof meta !== 'object') return undefined;
26
+ const value = (meta as Record<string, unknown>)[key];
27
+ return typeof value === 'string' ? value : undefined;
28
+ };
29
+
30
+ export const clientIdPrompt = ({ providerUrl }: { providerUrl: string }) => ({
31
+ prompt: `Client ID: ${chalk.dim(`(from ${link(providerUrl)})`)}`,
32
+ validate: validateRequired,
33
+ modifyOutput: UI.modifiers.piped([
34
+ UI.modifiers.topPadding,
35
+ UI.modifiers.dimOnComplete,
36
+ ]),
37
+ });
38
+
39
+ export const clientSecretPrompt = ({
40
+ providerUrl,
41
+ }: {
42
+ providerUrl: string;
43
+ }) => ({
44
+ prompt: `Client Secret: ${chalk.dim(`(from ${link(providerUrl)})`)}`,
45
+ validate: validateRequired,
46
+ sensitive: true,
47
+ modifyOutput: UI.modifiers.piped([
48
+ UI.modifiers.topPadding,
49
+ UI.modifiers.dimOnComplete,
50
+ ]),
51
+ });
52
+
53
+ export const redirectUriPrompt = ({ heading }: { heading: string }) => ({
54
+ prompt: '',
55
+ placeholder: 'https://yoursite.com/oauth/callback',
56
+ modifyOutput: UI.modifiers.piped([
57
+ (output, status) => {
58
+ if (status === 'idle') {
59
+ return (
60
+ `\n${heading}
61
+ ${chalk.dim('With a custom redirect URI, users will see "Redirecting to yoursite.com..." for a more branded experience.')}
62
+ ${chalk.dim(`Your URI must forward to ${DEFAULT_OAUTH_CALLBACK_URL} with all query parameters preserved.`)}\n\n` +
63
+ stripFirstBlankLine(output)
64
+ );
65
+ }
66
+ const answer = stripFirstBlankLine(output);
67
+ if (status === 'submitted' && answer.trim() === '') {
68
+ return `\n${heading}\n(skipped)`;
69
+ }
70
+ return `\n${heading}\n${answer}`;
71
+ },
72
+ UI.modifiers.dimOnComplete,
73
+ ]),
74
+ });
75
+
76
+ export const redirectSetupMessages = ({
77
+ prompt,
78
+ redirectUri,
79
+ showCustomRedirectInstructions,
80
+ }: {
81
+ prompt: string;
82
+ redirectUri: string;
83
+ showCustomRedirectInstructions?: boolean;
84
+ }) => {
85
+ const messages = ['', chalk.bold(`${prompt}:`), chalk.bold(redirectUri)];
86
+
87
+ if (showCustomRedirectInstructions) {
88
+ messages.push(
89
+ '',
90
+ `Your custom redirect must forward to ${chalk.bold(DEFAULT_OAUTH_CALLBACK_URL)} with all query parameters preserved.`,
91
+ );
92
+ messages.push(
93
+ `You can test it by visiting: ${chalk.bold(redirectUri + '?test-redirect=true')}`,
94
+ );
95
+ }
96
+
97
+ return messages;
98
+ };
99
+
100
+ export const appleServicesIdPrompt = (_opts: EmptyPromptArgs) => ({
101
+ prompt: `Services ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/identifiers/list/serviceId')})`)}`,
102
+ validate: validateRequired,
103
+ modifyOutput: UI.modifiers.piped([
104
+ UI.modifiers.topPadding,
105
+ UI.modifiers.dimOnComplete,
106
+ ]),
107
+ });
108
+
109
+ export const appleTeamIdPrompt = (_opts: EmptyPromptArgs) => ({
110
+ prompt: `Team ID ${chalk.dim(`(from ${link('https://developer.apple.com/account#MembershipDetailsCard')})`)}`,
111
+ validate: validateRequired,
112
+ modifyOutput: UI.modifiers.piped([
113
+ UI.modifiers.topPadding,
114
+ UI.modifiers.dimOnComplete,
115
+ ]),
116
+ });
117
+
118
+ export const appleKeyIdPrompt = (_opts: EmptyPromptArgs) => ({
119
+ prompt: `Key ID ${chalk.dim(`(from ${link('https://developer.apple.com/account/resources/authkeys/list')})`)}`,
120
+ validate: validateRequired,
121
+ modifyOutput: UI.modifiers.piped([
122
+ UI.modifiers.topPadding,
123
+ UI.modifiers.dimOnComplete,
124
+ ]),
125
+ });
126
+
127
+ export const applePrivateKeyFilePrompt = (_opts: EmptyPromptArgs) => ({
128
+ prompt: `Path to .p8 private key file ${chalk.dim('(downloaded from Apple)')}`,
129
+ validate: validateRequired,
130
+ modifyOutput: UI.modifiers.piped([
131
+ UI.modifiers.topPadding,
132
+ UI.modifiers.dimOnComplete,
133
+ ]),
134
+ });
135
+
136
+ export const clerkPublishableKeyPrompt = (_opts: EmptyPromptArgs) => ({
137
+ prompt: `Clerk publishable key ${chalk.dim(`(from ${link('https://dashboard.clerk.com/last-active?path=api-keys')})`)}`,
138
+ placeholder: 'pk_********************************************************',
139
+ validate: (val: string) => {
140
+ if (!val) return 'Publishable key is required';
141
+ if (!val.startsWith('pk_')) {
142
+ return 'Invalid publishable key. It should start with "pk_".';
143
+ }
144
+ },
145
+ modifyOutput: UI.modifiers.piped([
146
+ UI.modifiers.topPadding,
147
+ UI.modifiers.dimOnComplete,
148
+ ]),
149
+ });
150
+
151
+ export const firebaseProjectIdPrompt = (_opts: EmptyPromptArgs) => ({
152
+ prompt: `Firebase project ID: (From Project Settings page on ${link('https://console.firebase.google.com/')})`,
153
+ validate: validateFirebaseProjectId,
154
+ modifyOutput: UI.modifiers.piped([
155
+ UI.modifiers.topPadding,
156
+ UI.modifiers.dimOnComplete,
157
+ ]),
158
+ });
159
+
160
+ export const readPrivateKeyFile = Effect.fn('readPrivateKeyFile')(function* (
161
+ path: string,
162
+ ) {
163
+ const fs = yield* FileSystem.FileSystem;
164
+ // Strip shell escapes so paths like "file\ (2).p8" resolve on POSIX.
165
+ const normalizedPath =
166
+ process.platform === 'win32' ? path : path.replace(/\\(.)/g, '$1');
167
+ const contents = yield* fs.readFileString(normalizedPath, 'utf8').pipe(
168
+ Effect.mapError(
169
+ (e) =>
170
+ new BadArgsError({
171
+ message: `Could not read private key file at ${normalizedPath}: ${e.message}`,
172
+ }),
173
+ ),
174
+ );
175
+
176
+ const trimmed = contents.trim();
177
+ if (!trimmed) {
178
+ return yield* BadArgsError.make({
179
+ message: `Private key file at ${normalizedPath} is empty.`,
180
+ });
181
+ }
182
+ return trimmed;
183
+ });
184
+
185
+ export function validateFirebaseProjectId(value: string) {
186
+ const projectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
187
+ if (!value) return 'Project ID is required';
188
+ if (!projectIdRegex.test(value)) {
189
+ return 'Invalid Firebase project ID. It must be 6-30 characters, start with a lowercase letter, contain only lowercase letters, digits, and hyphens, and not end with a hyphen.';
190
+ }
191
+ }
192
+
193
+ export function firebaseDiscoveryEndpoint(projectId: string) {
194
+ return `https://securetoken.google.com/${encodeURIComponent(projectId)}/.well-known/openid-configuration`;
195
+ }