instant-cli 0.22.177 → 0.22.178

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 (213) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/__tests__/e2e/cli.e2e.test.ts +3 -3
  3. package/__tests__/e2e/helpers.ts +1 -1
  4. package/__tests__/effectHelpers.ts +45 -0
  5. package/__tests__/mergeSchema.test.ts +2 -2
  6. package/dist/commands/claim.d.ts +6 -0
  7. package/dist/commands/claim.d.ts.map +1 -0
  8. package/dist/commands/claim.js +22 -0
  9. package/dist/commands/claim.js.map +1 -0
  10. package/dist/commands/explorer.d.ts +6 -0
  11. package/dist/commands/explorer.d.ts.map +1 -0
  12. package/dist/commands/explorer.js +13 -0
  13. package/dist/commands/explorer.js.map +1 -0
  14. package/dist/commands/info.d.ts +3 -0
  15. package/dist/commands/info.d.ts.map +1 -0
  16. package/dist/commands/info.js +24 -0
  17. package/dist/commands/info.js.map +1 -0
  18. package/dist/commands/init.d.ts +5 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +39 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/initWithoutFiles.d.ts +6 -0
  23. package/dist/commands/initWithoutFiles.d.ts.map +1 -0
  24. package/dist/commands/initWithoutFiles.js +64 -0
  25. package/dist/commands/initWithoutFiles.js.map +1 -0
  26. package/dist/commands/login.d.ts +9 -0
  27. package/dist/commands/login.d.ts.map +1 -0
  28. package/dist/commands/login.js +52 -0
  29. package/dist/commands/login.js.map +1 -0
  30. package/dist/commands/logout.d.ts +4 -0
  31. package/dist/commands/logout.d.ts.map +1 -0
  32. package/dist/commands/logout.js +21 -0
  33. package/dist/commands/logout.js.map +1 -0
  34. package/dist/commands/pull.d.ts +6 -0
  35. package/dist/commands/pull.d.ts.map +1 -0
  36. package/dist/commands/pull.js +16 -0
  37. package/dist/commands/pull.js.map +1 -0
  38. package/dist/commands/push.d.ts +6 -0
  39. package/dist/commands/push.d.ts.map +1 -0
  40. package/dist/commands/push.js +20 -0
  41. package/dist/commands/push.js.map +1 -0
  42. package/dist/commands/query.d.ts +7 -0
  43. package/dist/commands/query.d.ts.map +1 -0
  44. package/dist/commands/query.js +52 -0
  45. package/dist/commands/query.js.map +1 -0
  46. package/dist/context/authToken.d.ts +30 -0
  47. package/dist/context/authToken.d.ts.map +1 -0
  48. package/dist/context/authToken.js +86 -0
  49. package/dist/context/authToken.js.map +1 -0
  50. package/dist/context/currentApp.d.ts +37 -0
  51. package/dist/context/currentApp.d.ts.map +1 -0
  52. package/dist/context/currentApp.js +204 -0
  53. package/dist/context/currentApp.js.map +1 -0
  54. package/dist/context/globalOpts.d.ts +11 -0
  55. package/dist/context/globalOpts.d.ts.map +1 -0
  56. package/dist/context/globalOpts.js +13 -0
  57. package/dist/context/globalOpts.js.map +1 -0
  58. package/dist/context/platformApi.d.ts +19 -0
  59. package/dist/context/platformApi.d.ts.map +1 -0
  60. package/dist/context/platformApi.js +24 -0
  61. package/dist/context/platformApi.js.map +1 -0
  62. package/dist/context/projectInfo.d.ts +29 -0
  63. package/dist/context/projectInfo.d.ts.map +1 -0
  64. package/dist/context/projectInfo.js +149 -0
  65. package/dist/context/projectInfo.js.map +1 -0
  66. package/dist/errors.d.ts +10 -0
  67. package/dist/errors.d.ts.map +1 -0
  68. package/dist/errors.js +6 -0
  69. package/dist/errors.js.map +1 -0
  70. package/dist/index.d.ts +41 -7
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +169 -1781
  73. package/dist/index.js.map +1 -1
  74. package/dist/layer.d.ts +23 -0
  75. package/dist/layer.d.ts.map +1 -0
  76. package/dist/layer.js +68 -0
  77. package/dist/layer.js.map +1 -0
  78. package/dist/lib/createApp.d.ts +12 -0
  79. package/dist/lib/createApp.d.ts.map +1 -0
  80. package/dist/lib/createApp.js +15 -0
  81. package/dist/lib/createApp.js.map +1 -0
  82. package/dist/lib/handleEnv.d.ts +7 -0
  83. package/dist/lib/handleEnv.d.ts.map +1 -0
  84. package/dist/lib/handleEnv.js +91 -0
  85. package/dist/lib/handleEnv.js.map +1 -0
  86. package/dist/lib/http.d.ts +32 -0
  87. package/dist/lib/http.d.ts.map +1 -0
  88. package/dist/lib/http.js +67 -0
  89. package/dist/lib/http.js.map +1 -0
  90. package/dist/lib/login.d.ts +13 -0
  91. package/dist/lib/login.d.ts.map +1 -0
  92. package/dist/lib/login.js +39 -0
  93. package/dist/lib/login.js.map +1 -0
  94. package/dist/lib/pullPerms.d.ts +7 -0
  95. package/dist/lib/pullPerms.d.ts.map +1 -0
  96. package/dist/lib/pullPerms.js +41 -0
  97. package/dist/lib/pullPerms.js.map +1 -0
  98. package/dist/lib/pullSchema.d.ts +12 -0
  99. package/dist/lib/pullSchema.d.ts.map +1 -0
  100. package/dist/lib/pullSchema.js +62 -0
  101. package/dist/lib/pullSchema.js.map +1 -0
  102. package/dist/lib/pushPerms.d.ts +13 -0
  103. package/dist/lib/pushPerms.d.ts.map +1 -0
  104. package/dist/lib/pushPerms.js +54 -0
  105. package/dist/lib/pushPerms.js.map +1 -0
  106. package/dist/lib/pushSchema.d.ts +53 -0
  107. package/dist/lib/pushSchema.d.ts.map +1 -0
  108. package/dist/lib/pushSchema.js +160 -0
  109. package/dist/lib/pushSchema.js.map +1 -0
  110. package/dist/lib/ui.d.ts +16 -0
  111. package/dist/lib/ui.d.ts.map +1 -0
  112. package/dist/lib/ui.js +22 -0
  113. package/dist/lib/ui.js.map +1 -0
  114. package/dist/logging.d.ts +4 -0
  115. package/dist/logging.d.ts.map +1 -0
  116. package/dist/logging.js +17 -0
  117. package/dist/logging.js.map +1 -0
  118. package/dist/old.d.ts +14 -0
  119. package/dist/old.d.ts.map +1 -0
  120. package/dist/old.js +417 -0
  121. package/dist/old.js.map +1 -0
  122. package/dist/program.d.ts +3 -0
  123. package/dist/program.d.ts.map +1 -0
  124. package/dist/program.js +3 -0
  125. package/dist/program.js.map +1 -0
  126. package/dist/renderSchemaPlan.d.ts +3 -3
  127. package/dist/renderSchemaPlan.d.ts.map +1 -1
  128. package/dist/renderSchemaPlan.js +2 -14
  129. package/dist/renderSchemaPlan.js.map +1 -1
  130. package/dist/ui/index.d.ts +4 -3
  131. package/dist/ui/index.d.ts.map +1 -1
  132. package/dist/ui/index.js +2 -2
  133. package/dist/ui/index.js.map +1 -1
  134. package/dist/ui/lib.js +1 -0
  135. package/dist/ui/lib.js.map +1 -1
  136. package/dist/util/findConfigCandidates.d.ts +1 -1
  137. package/dist/util/findConfigCandidates.d.ts.map +1 -1
  138. package/dist/util/findConfigCandidates.js +1 -3
  139. package/dist/util/findConfigCandidates.js.map +1 -1
  140. package/dist/util/fs.d.ts +1 -1
  141. package/dist/util/fs.d.ts.map +1 -1
  142. package/dist/util/fs.js.map +1 -1
  143. package/dist/util/getAuthPaths.d.ts.map +1 -1
  144. package/dist/util/getAuthPaths.js.map +1 -1
  145. package/dist/util/isHeadlessEnvironment.d.ts +3 -1
  146. package/dist/util/isHeadlessEnvironment.d.ts.map +1 -1
  147. package/dist/util/isHeadlessEnvironment.js.map +1 -1
  148. package/dist/util/loadConfig.d.ts +1 -1
  149. package/dist/util/loadConfig.d.ts.map +1 -1
  150. package/dist/util/loadConfig.js +2 -2
  151. package/dist/util/loadConfig.js.map +1 -1
  152. package/dist/util/mergeSchema.d.ts +9 -1
  153. package/dist/util/mergeSchema.d.ts.map +1 -1
  154. package/dist/util/mergeSchema.js +4 -0
  155. package/dist/util/mergeSchema.js.map +1 -1
  156. package/dist/util/renamePrompt.d.ts +2 -1
  157. package/dist/util/renamePrompt.d.ts.map +1 -1
  158. package/dist/util/renamePrompt.js +1 -1
  159. package/dist/util/renamePrompt.js.map +1 -1
  160. package/package.json +17 -7
  161. package/src/commands/claim.ts +31 -0
  162. package/src/commands/explorer.ts +21 -0
  163. package/src/commands/info.ts +34 -0
  164. package/src/commands/init.ts +58 -0
  165. package/src/commands/initWithoutFiles.ts +107 -0
  166. package/src/commands/login.ts +76 -0
  167. package/src/commands/logout.ts +23 -0
  168. package/src/commands/pull.ts +23 -0
  169. package/src/commands/push.ts +25 -0
  170. package/src/commands/query.ts +61 -0
  171. package/src/context/authToken.ts +149 -0
  172. package/src/context/currentApp.ts +277 -0
  173. package/src/context/globalOpts.ts +22 -0
  174. package/src/context/platformApi.ts +35 -0
  175. package/src/context/projectInfo.ts +215 -0
  176. package/src/errors.ts +7 -0
  177. package/src/index.ts +428 -0
  178. package/src/layer.ts +155 -0
  179. package/src/lib/createApp.ts +28 -0
  180. package/src/lib/handleEnv.ts +115 -0
  181. package/src/lib/http.ts +148 -0
  182. package/src/lib/login.ts +54 -0
  183. package/src/lib/pullPerms.ts +50 -0
  184. package/src/lib/pullSchema.ts +95 -0
  185. package/src/lib/pushPerms.ts +80 -0
  186. package/src/lib/pushSchema.ts +240 -0
  187. package/src/lib/ui.ts +36 -0
  188. package/src/logging.ts +32 -0
  189. package/src/old.js +495 -0
  190. package/src/program.ts +3 -0
  191. package/src/renderSchemaPlan.ts +6 -18
  192. package/src/ui/index.ts +4 -3
  193. package/src/util/findConfigCandidates.ts +1 -2
  194. package/src/util/fs.ts +1 -1
  195. package/src/util/getAuthPaths.ts +1 -0
  196. package/src/util/isHeadlessEnvironment.ts +1 -1
  197. package/src/util/loadConfig.ts +3 -6
  198. package/src/util/{mergeSchema.js → mergeSchema.ts} +26 -16
  199. package/src/util/renamePrompt.ts +2 -1
  200. package/tsconfig.build.json +20 -0
  201. package/tsconfig.json +15 -5
  202. package/vitest.config.ts +2 -1
  203. package/dist/util/packageManager.d.ts +0 -3
  204. package/dist/util/packageManager.d.ts.map +0 -1
  205. package/dist/util/packageManager.js +0 -70
  206. package/dist/util/packageManager.js.map +0 -1
  207. package/dist/util/promptOk.d.ts +0 -4
  208. package/dist/util/promptOk.d.ts.map +0 -1
  209. package/dist/util/promptOk.js +0 -18
  210. package/dist/util/promptOk.js.map +0 -1
  211. package/src/index.js +0 -2333
  212. package/src/util/packageManager.js +0 -78
  213. package/src/util/promptOk.ts +0 -26
package/src/index.ts ADDED
@@ -0,0 +1,428 @@
1
+ import { loadEnv } from './util/loadEnv.ts';
2
+ loadEnv();
3
+
4
+ import { Command, Option } from '@commander-js/extra-typings';
5
+ import chalk from 'chalk';
6
+ import { Effect, Layer } from 'effect';
7
+ import version from './version.js';
8
+ import { initCommand } from './commands/init.ts';
9
+ import { initWithoutFilesCommand } from './commands/initWithoutFiles.ts';
10
+ import { loginCommand } from './commands/login.ts';
11
+ import { logoutCommand } from './commands/logout.ts';
12
+ import {
13
+ AuthLayerLive,
14
+ BaseLayerLive,
15
+ runCommandEffect,
16
+ WithAppLayer,
17
+ } from './layer.ts';
18
+ import { infoCommand } from './commands/info.ts';
19
+ import { pullCommand } from './commands/pull.ts';
20
+ import type { SchemaPermsOrBoth } from './commands/pull.ts';
21
+ import { claimCommand } from './commands/claim.ts';
22
+ import { pushCommand } from './commands/push.ts';
23
+ import { explorerCmd } from './commands/explorer.ts';
24
+ import { queryCmd } from './commands/query.ts';
25
+ import { program } from './program.ts';
26
+ import { PACKAGE_ALIAS_AND_FULL_NAMES } from './context/projectInfo.ts';
27
+
28
+ export type OptsFromCommand<C> =
29
+ C extends Command<any, infer R, any> ? R : never;
30
+
31
+ program
32
+ .name('instant-cli')
33
+ .addOption(globalOption('-t --token <token>', 'Auth token override'))
34
+ .addOption(globalOption('-y --yes', "Answer 'yes' to all prompts"))
35
+ .addOption(globalOption('--env <file>', 'Use a specific .env file'))
36
+ .addOption(
37
+ globalOption('-v --version', 'Print the version number', () => {
38
+ console.log(version);
39
+ process.exit(0);
40
+ }),
41
+ )
42
+ .addHelpOption(globalOption('-h --help', 'Print the help text for a command'))
43
+ .usage(`<command> ${chalk.dim('[options] [args]')}`);
44
+
45
+ // Command List
46
+ export const initDef = program
47
+ .command('init')
48
+ .description('Set up a new project.')
49
+ .option(
50
+ '-a --app <app-id>',
51
+ 'If you have an existing app ID, we can pull schema and perms from there.',
52
+ )
53
+ .option(
54
+ '-p --package <react|react-native|core|admin|solid|svelte>',
55
+ 'Which package to automatically install if there is not one installed already.',
56
+ )
57
+ .option('--title <title>', 'Title for the created app')
58
+ .action((options) => {
59
+ return runCommandEffect(
60
+ initCommand(options).pipe(
61
+ Effect.provide(
62
+ WithAppLayer({
63
+ coerce: true,
64
+ coerceAuth: true,
65
+ title: options.title,
66
+ appId: options.app,
67
+ packageName: options.package as any,
68
+ applyEnv: true,
69
+ }),
70
+ ),
71
+ ),
72
+ );
73
+ });
74
+
75
+ export const initWithoutFilesDef = program
76
+ .command('init-without-files')
77
+ .description('Generate a new app id and admin token pair without any files.')
78
+ .option('--title <title>', 'Title for the created app.')
79
+ .option(
80
+ '--org-id <org-id>',
81
+ 'Organization id for app. Cannot be used with --temp flag.',
82
+ )
83
+ .option(
84
+ '--temp',
85
+ 'Create a temporary app which will automatically delete itself after >24 hours.',
86
+ )
87
+ .action((opts) => {
88
+ return runCommandEffect(
89
+ initWithoutFilesCommand(opts).pipe(Effect.provide(BaseLayerLive)),
90
+ );
91
+ });
92
+
93
+ export const loginDef = program
94
+ .command('login')
95
+ .description('Log into your account')
96
+ .option('-p --print', 'Prints the auth token into the console.')
97
+ .option(
98
+ '--headless',
99
+ 'Print the login URL instead of trying to open the browser',
100
+ )
101
+ .action(async (opts) => {
102
+ await runCommandEffect(
103
+ loginCommand(opts).pipe(Effect.provide(BaseLayerLive)),
104
+ );
105
+ });
106
+
107
+ program
108
+ .command('logout')
109
+ .description('Log out of your Instant account')
110
+ .action(async () => {
111
+ return runCommandEffect(
112
+ logoutCommand().pipe(Effect.provide(BaseLayerLive)),
113
+ );
114
+ });
115
+
116
+ export const infoDef = program
117
+ .command('info')
118
+ .description('Display CLI version and login status')
119
+ .action(async () => {
120
+ return runCommandEffect(
121
+ infoCommand().pipe(
122
+ Effect.provide(
123
+ AuthLayerLive({
124
+ coerce: false,
125
+ allowAdminToken: false,
126
+ }).pipe(Layer.catchAll(() => Layer.empty)), // make the auth layer optional
127
+ ),
128
+ ),
129
+ );
130
+ });
131
+
132
+ export const explorerDef = program
133
+ .command('explorer')
134
+ .description('Opens the Explorer in your browser')
135
+ .option(
136
+ '-a --app <app-id>',
137
+ 'App ID to open the explorer to. Defaults to *_INSTANT_APP_ID in .env',
138
+ )
139
+ .action(async (opts) => {
140
+ return runCommandEffect(
141
+ explorerCmd(opts).pipe(
142
+ Effect.provide(
143
+ WithAppLayer({
144
+ coerce: true,
145
+ coerceAuth: true,
146
+ appId: opts.app,
147
+ }),
148
+ ),
149
+ ),
150
+ );
151
+ });
152
+
153
+ export const queryDef = program
154
+ .command('query')
155
+ .argument('<query>', 'InstaQL query as JSON/JSON5')
156
+ .option(
157
+ '-a --app <app-id>',
158
+ 'App ID to query. Defaults to *_INSTANT_APP_ID in .env',
159
+ )
160
+ .option('--admin', 'Run the query as admin (bypasses permissions)')
161
+ .option('--as-email <email>', 'Run the query as a specific user by email')
162
+ .option('--as-guest', 'Run the query as an unauthenticated guest')
163
+ .option(
164
+ '--as-token <refresh-token>',
165
+ 'Run the query as a user identified by refresh token',
166
+ )
167
+ .description('Run an InstaQL query against your app.')
168
+ .action(async function (queryArg, opts) {
169
+ return runCommandEffect(queryCmd(queryArg, opts));
170
+ });
171
+
172
+ export const pullDef = program
173
+ .command('pull')
174
+ .argument(
175
+ '[schema|perms|all]',
176
+ 'Which configuration to pull. Defaults to `all`',
177
+ )
178
+ .option(
179
+ '-a --app <app-id>',
180
+ 'App ID to pull to. Defaults to *_INSTANT_APP_ID in .env',
181
+ )
182
+ .option(
183
+ '-p --package <react|react-native|core|admin|solid|svelte>',
184
+ 'Which package to automatically install if there is not one installed already.',
185
+ )
186
+ .option(
187
+ '--experimental-type-preservation',
188
+ "[Experimental] Preserve manual type changes like `status: i.json<'online' | 'offline'>()` when doing `instant-cli pull schema`",
189
+ )
190
+ .description('Pull schema and perm files from production.')
191
+ .addHelpText(
192
+ 'after',
193
+ `
194
+ Environment Variables:
195
+ INSTANT_SCHEMA_FILE_PATH Override schema file location (default: instant.schema.ts)
196
+ INSTANT_PERMS_FILE_PATH Override perms file location (default: instant.perms.ts)
197
+ `,
198
+ )
199
+ .action(async function (arg, inputOpts) {
200
+ return runCommandEffect(
201
+ pullCommand(arg as SchemaPermsOrBoth, inputOpts).pipe(
202
+ Effect.provide(
203
+ WithAppLayer({
204
+ coerce: true,
205
+ packageName: inputOpts.package as
206
+ | 'react'
207
+ | 'react-native'
208
+ | 'core'
209
+ | 'admin'
210
+ | undefined,
211
+ appId: inputOpts.app,
212
+ }),
213
+ ),
214
+ ),
215
+ );
216
+ });
217
+
218
+ export const pushDef = program
219
+ .command('push')
220
+ .argument(
221
+ '[schema|perms|all]',
222
+ 'Which configuration to push. Defaults to `all`',
223
+ )
224
+ .option(
225
+ '-a --app <app-id>',
226
+ 'App ID to push to. Defaults to *_INSTANT_APP_ID in .env',
227
+ )
228
+ .option(
229
+ '--skip-check-types',
230
+ "Don't check types on the server when pushing schema",
231
+ )
232
+ .option(
233
+ '--rename [renames...]',
234
+ 'List of full attribute names separated by a ":"\n Example:`push --rename posts.author:posts.creator stores.owner:stores.manager`',
235
+ )
236
+ .option(
237
+ '-p --package <react|react-native|core|admin|solid|svelte>',
238
+ 'Which package to automatically install if there is not one installed already.',
239
+ )
240
+ .description('Push schema and perm files to production.')
241
+ .addHelpText(
242
+ 'after',
243
+ `
244
+ Environment Variables:
245
+ INSTANT_SCHEMA_FILE_PATH Override schema file location (default: instant.schema.ts)
246
+ INSTANT_PERMS_FILE_PATH Override perms file location (default: instant.perms.ts)
247
+ `,
248
+ )
249
+ .action(async function (arg, inputOpts) {
250
+ return runCommandEffect(
251
+ pushCommand(arg, inputOpts).pipe(
252
+ Effect.provide(
253
+ WithAppLayer({
254
+ coerce: false,
255
+ appId: inputOpts.app,
256
+ coerceLibraryInstall: true,
257
+ coerceAuth: false,
258
+ allowAdminToken: true,
259
+ applyEnv: true,
260
+ packageName:
261
+ inputOpts.package as keyof typeof PACKAGE_ALIAS_AND_FULL_NAMES,
262
+ }),
263
+ ),
264
+ ),
265
+ );
266
+ });
267
+
268
+ export const claimDef = program
269
+ .command('claim')
270
+ .description('Transfer a temporary app into your Instant account')
271
+ .option(
272
+ '-a --app <app-id>',
273
+ 'App to claim. Defaults to *_INSTANT_APP_ID in .env',
274
+ )
275
+ .action(async function (opts) {
276
+ return runCommandEffect(
277
+ claimCommand.pipe(
278
+ Effect.provide(
279
+ WithAppLayer({
280
+ coerce: false,
281
+ allowAdminToken: false,
282
+ appId: opts.app,
283
+ applyEnv: false,
284
+ }),
285
+ ),
286
+ ),
287
+ );
288
+ });
289
+ //// Program setup /////
290
+
291
+ function globalOption(
292
+ flags: string,
293
+ description?: string,
294
+ argParser?: (value: string, prev?: unknown) => unknown,
295
+ ) {
296
+ const opt = new Option(flags, description);
297
+ if (argParser) {
298
+ opt.argParser(argParser);
299
+ }
300
+ // @ts-ignore
301
+ // __global does not exist on `Option`,
302
+ // but we use it in `getLocalAndGlobalOptions`, to produce
303
+ // our own custom list of local and global options.
304
+ // For more info, see the original PR:
305
+ // https://github.com/instantdb/instant/pull/505
306
+ opt.__global = true;
307
+ return opt;
308
+ }
309
+
310
+ function getLocalAndGlobalOptions(cmd: any, helper: any) {
311
+ const mixOfLocalAndGlobal = helper.visibleOptions(cmd);
312
+ const localOptionsFromMix = mixOfLocalAndGlobal.filter(
313
+ (option: any) => !option.__global,
314
+ );
315
+ const globalOptionsFromMix = mixOfLocalAndGlobal.filter(
316
+ (option: any) => option.__global,
317
+ );
318
+ const globalOptions = helper.visibleGlobalOptions(cmd);
319
+
320
+ return [localOptionsFromMix, globalOptionsFromMix.concat(globalOptions)];
321
+ }
322
+
323
+ function formatHelp(
324
+ this: { showGlobalOptions: boolean },
325
+ cmd: any,
326
+ helper: any,
327
+ ) {
328
+ const termWidth = helper.padWidth(cmd, helper);
329
+ const helpWidth = helper.helpWidth || 80;
330
+ const itemIndentWidth = 2;
331
+ const itemSeparatorWidth = 2; // between term and description
332
+ function formatItem(term: string, description: string | undefined) {
333
+ if (description) {
334
+ const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
335
+ return helper.wrap(
336
+ fullText,
337
+ helpWidth - itemIndentWidth,
338
+ termWidth + itemSeparatorWidth,
339
+ );
340
+ }
341
+ return term;
342
+ }
343
+ function formatList(textArray: string[]) {
344
+ return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
345
+ }
346
+
347
+ // Usage
348
+ let output = [`${helper.commandUsage(cmd)}`, ''];
349
+
350
+ // Description
351
+ const commandDescription = helper.commandDescription(cmd);
352
+ if (commandDescription.length > 0) {
353
+ output = output.concat([helper.wrap(commandDescription, helpWidth, 0), '']);
354
+ }
355
+
356
+ // Arguments
357
+ const argumentList = helper.visibleArguments(cmd).map((argument: any) => {
358
+ return formatItem(
359
+ helper.argumentTerm(argument),
360
+ helper.argumentDescription(argument),
361
+ );
362
+ });
363
+ if (argumentList.length > 0) {
364
+ output = output.concat([
365
+ chalk.dim.bold('Arguments'),
366
+ formatList(argumentList),
367
+ '',
368
+ ]);
369
+ }
370
+ const [visibleOptions, visibleGlobalOptions] = getLocalAndGlobalOptions(
371
+ cmd,
372
+ helper,
373
+ );
374
+
375
+ // Options
376
+ const optionList = visibleOptions.map((option: any) => {
377
+ return formatItem(
378
+ helper.optionTerm(option),
379
+ helper.optionDescription(option),
380
+ );
381
+ });
382
+ if (optionList.length > 0) {
383
+ output = output.concat([
384
+ chalk.dim.bold('Options'),
385
+ formatList(optionList),
386
+ '',
387
+ ]);
388
+ }
389
+ // Commands
390
+ const commandList = helper.visibleCommands(cmd).map((cmd: any) => {
391
+ return formatItem(
392
+ helper.subcommandTerm(cmd),
393
+ helper.subcommandDescription(cmd),
394
+ );
395
+ });
396
+ if (commandList.length > 0) {
397
+ output = output.concat([
398
+ chalk.dim.bold('Commands'),
399
+ formatList(commandList),
400
+ '',
401
+ ]);
402
+ }
403
+
404
+ if (this.showGlobalOptions) {
405
+ const globalOptionList = visibleGlobalOptions.map((option: any) => {
406
+ return formatItem(
407
+ helper.optionTerm(option),
408
+ helper.optionDescription(option),
409
+ );
410
+ });
411
+ if (globalOptionList.length > 0) {
412
+ output = output.concat([
413
+ chalk.dim.bold('Global Options'),
414
+ formatList(globalOptionList),
415
+ '',
416
+ ]);
417
+ }
418
+ }
419
+
420
+ return output.join('\n');
421
+ }
422
+
423
+ program.configureHelp({
424
+ showGlobalOptions: true,
425
+ formatHelp,
426
+ });
427
+
428
+ program.parse(process.argv);
package/src/layer.ts ADDED
@@ -0,0 +1,155 @@
1
+ import { NodeContext, NodeHttpClient } from '@effect/platform-node';
2
+ import { Cause, Effect, Layer, ManagedRuntime } from 'effect';
3
+ import { AuthTokenLive } from './context/authToken.ts';
4
+ import { CurrentAppLive } from './context/currentApp.ts';
5
+ import { GlobalOptsLive } from './context/globalOpts.ts';
6
+ import { PlatformApi } from './context/platformApi.ts';
7
+ import {
8
+ PACKAGE_ALIAS_AND_FULL_NAMES,
9
+ ProjectInfoLive,
10
+ } from './context/projectInfo.ts';
11
+ import {
12
+ InstantHttpAuthedLive,
13
+ InstantHttpError,
14
+ InstantHttpLive,
15
+ } from './lib/http.ts';
16
+ import { SimpleLogLayer } from './logging.ts';
17
+
18
+ const runtime = ManagedRuntime.make(SimpleLogLayer);
19
+
20
+ export const runCommandEffect = <A, E, R extends never>(
21
+ effect: Effect.Effect<A, E, R>,
22
+ ): Promise<A> => runtime.runPromise(effect.pipe(printRedErrors) as any);
23
+
24
+ export const printRedErrors = Effect.catchAllCause((cause) =>
25
+ Effect.gen(function* () {
26
+ const failure = Cause.failureOption(cause);
27
+
28
+ // This should never happen because the catchAllCause should only fire when there IS a failure
29
+ if (failure._tag !== 'Some') {
30
+ return;
31
+ }
32
+
33
+ const theError = failure.value;
34
+
35
+ // Special error handling for specific error types
36
+ if (theError instanceof InstantHttpError) {
37
+ if (theError?.message) {
38
+ yield* Effect.logError(
39
+ 'Error making request to Instant API: ' + theError.message,
40
+ );
41
+ }
42
+ if (Array.isArray(theError?.hint?.errors)) {
43
+ for (const err of theError.hint.errors) {
44
+ yield* Effect.logError(
45
+ `${err.in ? err.in.join('->') + ': ' : ''}${err.message}`,
46
+ );
47
+ }
48
+ }
49
+ return process.exit(1);
50
+ }
51
+
52
+ // Print just the message if the error has a message attribute and no cause
53
+ if (
54
+ typeof failure.value === 'object' &&
55
+ failure.value !== null &&
56
+ 'message' in failure.value &&
57
+ typeof failure.value.message === 'string' &&
58
+ !('cause' in failure.value)
59
+ ) {
60
+ return yield* Effect.logError(failure.value.message).pipe(
61
+ Effect.tap(() => {
62
+ process.exit(1);
63
+ }),
64
+ );
65
+ }
66
+
67
+ return yield* Effect.logError(
68
+ Cause.pretty(cause, { renderErrorCause: true }),
69
+ ).pipe(
70
+ Effect.tap(() => {
71
+ process.exit(1);
72
+ }),
73
+ );
74
+ }),
75
+ );
76
+
77
+ /**
78
+ * Note:
79
+ Avoid Duplicate Layer Creation
80
+
81
+ Layers are memoized using reference equality. Therefore, if you have a layer that is created by calling a function like f(), you should only call that f once and re-use the resulting layer so that you are always using the same instance.
82
+ */
83
+
84
+ // TODO: make coerce param work for auth too
85
+
86
+ // Base layers
87
+ const AuthTokenLayer = ({
88
+ allowAdminToken = true,
89
+ coerce = false,
90
+ }: {
91
+ allowAdminToken: boolean;
92
+ coerce: boolean;
93
+ }) =>
94
+ Layer.provide(AuthTokenLive({ allowAdminToken, coerce }), NodeContext.layer);
95
+
96
+ const InstantHttpLayer = Layer.provide(InstantHttpLive, NodeHttpClient.layer);
97
+
98
+ // Unauthenticated layer with InstantHttp + PlatformApi + GlobalOpts + NodeContext
99
+ export const BaseLayerLive = Layer.provideMerge(
100
+ Layer.mergeAll(InstantHttpLayer, PlatformApi.Default, GlobalOptsLive),
101
+ NodeContext.layer,
102
+ );
103
+
104
+ // Authenticated layer extends BaseLayerLive with InstantHttpAuthed
105
+ export const AuthLayerLive = ({
106
+ allowAdminToken = true,
107
+ coerce = false,
108
+ }: {
109
+ allowAdminToken: boolean;
110
+ coerce: boolean;
111
+ }) =>
112
+ Layer.provideMerge(
113
+ Layer.provideMerge(
114
+ InstantHttpAuthedLive,
115
+ Layer.merge(
116
+ AuthTokenLayer({ allowAdminToken, coerce }),
117
+ InstantHttpLayer,
118
+ ),
119
+ ),
120
+ BaseLayerLive,
121
+ );
122
+
123
+ export const WithAppLayer = (args: {
124
+ appId?: string;
125
+ title?: string;
126
+ coerce: boolean;
127
+ coerceAuth?: boolean;
128
+ coerceLibraryInstall?: boolean;
129
+ packageName?: keyof typeof PACKAGE_ALIAS_AND_FULL_NAMES;
130
+ allowAdminToken?: boolean;
131
+ applyEnv?: boolean;
132
+ }) =>
133
+ Layer.mergeAll(
134
+ CurrentAppLive({
135
+ coerce: args.coerce,
136
+ appId: args.appId,
137
+ title: args.title,
138
+ applyEnv: args.applyEnv,
139
+ }),
140
+ ).pipe(
141
+ Layer.provideMerge(
142
+ AuthLayerLive({
143
+ allowAdminToken:
144
+ args.allowAdminToken !== undefined ? args.allowAdminToken : true,
145
+ coerce: args.coerceAuth ?? false,
146
+ }),
147
+ ),
148
+ Layer.provideMerge(
149
+ ProjectInfoLive(
150
+ args.coerceLibraryInstall ?? args.coerce,
151
+ args.packageName,
152
+ ),
153
+ ),
154
+ Layer.provideMerge(BaseLayerLive),
155
+ );
@@ -0,0 +1,28 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { Data, Effect, Schema } from 'effect';
3
+ import { InstantHttpAuthed } from './http.ts';
4
+ import { HttpClientRequest, HttpClientResponse } from '@effect/platform';
5
+
6
+ export class CreateAppError extends Data.TaggedError('CreateAppError')<{
7
+ message: string;
8
+ }> {}
9
+
10
+ export const createApp = Effect.fn(
11
+ function* (title: string, orgId?: string) {
12
+ const http = yield* InstantHttpAuthed;
13
+ const id = randomUUID();
14
+ const token = randomUUID();
15
+ const app = { id, title, admin_token: token, org_id: orgId };
16
+
17
+ const res = yield* HttpClientRequest.post('/dash/apps').pipe(
18
+ HttpClientRequest.bodyJson(app),
19
+ Effect.flatMap(http.execute),
20
+ Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Any)),
21
+ );
22
+ return res;
23
+ },
24
+ Effect.catchTag(
25
+ 'HttpBodyError',
26
+ (e) => new CreateAppError({ message: 'Error constructing http body' }),
27
+ ),
28
+ );