instant-cli 0.22.120-experimental.drewh-tanstack-start.21601761390.1 → 0.22.120-experimental.drewh-clief.21602421293.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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/new/commands/init.d.ts +9 -0
- package/dist/new/commands/init.d.ts.map +1 -0
- package/dist/new/commands/init.js +9 -0
- package/dist/new/commands/init.js.map +1 -0
- package/dist/new/commands/initWithoutFiles.d.ts +10 -0
- package/dist/new/commands/initWithoutFiles.d.ts.map +1 -0
- package/dist/new/commands/initWithoutFiles.js +36 -0
- package/dist/new/commands/initWithoutFiles.js.map +1 -0
- package/dist/new/commands/login.d.ts +9 -0
- package/dist/new/commands/login.d.ts.map +1 -0
- package/dist/new/commands/login.js +51 -0
- package/dist/new/commands/login.js.map +1 -0
- package/dist/new/commands/logout.d.ts +4 -0
- package/dist/new/commands/logout.d.ts.map +1 -0
- package/dist/new/commands/logout.js +22 -0
- package/dist/new/commands/logout.js.map +1 -0
- package/dist/new/context/authToken.d.ts +19 -0
- package/dist/new/context/authToken.d.ts.map +1 -0
- package/dist/new/context/authToken.js +51 -0
- package/dist/new/context/authToken.js.map +1 -0
- package/dist/new/context/currentApp.d.ts +36 -0
- package/dist/new/context/currentApp.d.ts.map +1 -0
- package/dist/new/context/currentApp.js +145 -0
- package/dist/new/context/currentApp.js.map +1 -0
- package/dist/new/context/globalOpts.d.ts +11 -0
- package/dist/new/context/globalOpts.d.ts.map +1 -0
- package/dist/new/context/globalOpts.js +10 -0
- package/dist/new/context/globalOpts.js.map +1 -0
- package/dist/new/context/platformApi.d.ts +19 -0
- package/dist/new/context/platformApi.d.ts.map +1 -0
- package/dist/new/context/platformApi.js +24 -0
- package/dist/new/context/platformApi.js.map +1 -0
- package/dist/new/context/projectInfo.d.ts +25 -0
- package/dist/new/context/projectInfo.d.ts.map +1 -0
- package/dist/new/context/projectInfo.js +120 -0
- package/dist/new/context/projectInfo.js.map +1 -0
- package/dist/new/errors.d.ts +10 -0
- package/dist/new/errors.d.ts.map +1 -0
- package/dist/new/errors.js +6 -0
- package/dist/new/errors.js.map +1 -0
- package/dist/new/index.d.ts +17 -0
- package/dist/new/index.d.ts.map +1 -0
- package/dist/new/index.js +159 -0
- package/dist/new/index.js.map +1 -0
- package/dist/new/layer.d.ts +15 -0
- package/dist/new/layer.d.ts.map +1 -0
- package/dist/new/layer.js +41 -0
- package/dist/new/layer.js.map +1 -0
- package/dist/new/lib/createApp.d.ts +4 -0
- package/dist/new/lib/createApp.d.ts.map +1 -0
- package/dist/new/lib/createApp.js +13 -0
- package/dist/new/lib/createApp.js.map +1 -0
- package/dist/new/lib/handleEnv.d.ts +7 -0
- package/dist/new/lib/handleEnv.d.ts.map +1 -0
- package/dist/new/lib/handleEnv.js +88 -0
- package/dist/new/lib/handleEnv.js.map +1 -0
- package/dist/new/lib/http.d.ts +15 -0
- package/dist/new/lib/http.d.ts.map +1 -0
- package/dist/new/lib/http.js +32 -0
- package/dist/new/lib/http.js.map +1 -0
- package/dist/new/lib/login.d.ts +13 -0
- package/dist/new/lib/login.d.ts.map +1 -0
- package/dist/new/lib/login.js +36 -0
- package/dist/new/lib/login.js.map +1 -0
- package/dist/new/lib/ui.d.ts +16 -0
- package/dist/new/lib/ui.d.ts.map +1 -0
- package/dist/new/lib/ui.js +30 -0
- package/dist/new/lib/ui.js.map +1 -0
- package/dist/new/logging.d.ts +3 -0
- package/dist/new/logging.d.ts.map +1 -0
- package/dist/new/logging.js +8 -0
- package/dist/new/logging.js.map +1 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/util/getAuthPaths.d.ts.map +1 -1
- package/dist/util/getAuthPaths.js.map +1 -1
- package/package.json +14 -5
- package/src/index.js +1 -1
- package/src/new/commands/init.ts +12 -0
- package/src/new/commands/initWithoutFiles.ts +44 -0
- package/src/new/commands/login.ts +73 -0
- package/src/new/commands/logout.ts +23 -0
- package/src/new/context/authToken.ts +77 -0
- package/src/new/context/currentApp.ts +207 -0
- package/src/new/context/globalOpts.ts +22 -0
- package/src/new/context/platformApi.ts +35 -0
- package/src/new/context/projectInfo.ts +172 -0
- package/src/new/errors.ts +7 -0
- package/src/new/index.ts +245 -0
- package/src/new/layer.ts +78 -0
- package/src/new/lib/createApp.ts +18 -0
- package/src/new/lib/handleEnv.ts +107 -0
- package/src/new/lib/http.ts +63 -0
- package/src/new/lib/login.ts +50 -0
- package/src/new/lib/ui.ts +45 -0
- package/src/new/logging.ts +9 -0
- package/src/ui/index.ts +1 -1
- package/src/util/getAuthPaths.ts +1 -0
- package/tsconfig.json +7 -2
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { HttpClientRequest, HttpClientResponse } from '@effect/platform';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { Context, Data, Effect, Layer, Runtime, Schema } from 'effect';
|
|
4
|
+
import { UI } from '../../ui/index.js';
|
|
5
|
+
import { handleEnv } from '../lib/handleEnv.js';
|
|
6
|
+
import { getBaseUrl, InstantHttpAuthed } from '../lib/http.js';
|
|
7
|
+
import { runUIEffect } from '../lib/ui.js';
|
|
8
|
+
import { AuthToken } from './authToken.js';
|
|
9
|
+
import { GlobalOpts } from './globalOpts.js';
|
|
10
|
+
import { PlatformApi } from './platformApi.js';
|
|
11
|
+
|
|
12
|
+
export type CurrentAppInfo = {
|
|
13
|
+
appId: string;
|
|
14
|
+
adminToken?: string;
|
|
15
|
+
source: 'create' | 'import' | 'env' | 'flag' | 'ephemeral';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class CurrentApp extends Context.Tag(
|
|
19
|
+
'instant-cli/new/context/currentApp',
|
|
20
|
+
)<CurrentApp, CurrentAppInfo>() {}
|
|
21
|
+
|
|
22
|
+
function isUUID(uuid) {
|
|
23
|
+
const uuidRegex =
|
|
24
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25
|
+
return uuidRegex.test(uuid);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class CurrentAppContextError extends Data.TaggedError(
|
|
29
|
+
'CurrentAppContextError',
|
|
30
|
+
)<{
|
|
31
|
+
message: string;
|
|
32
|
+
}> {}
|
|
33
|
+
|
|
34
|
+
export class AppNotFoundError extends Data.TaggedError('AppNotFoundError')<{
|
|
35
|
+
message: string;
|
|
36
|
+
}> {}
|
|
37
|
+
|
|
38
|
+
export const potentialEnvs: Record<string, string> = {
|
|
39
|
+
catchall: 'INSTANT_APP_ID',
|
|
40
|
+
next: 'NEXT_PUBLIC_INSTANT_APP_ID',
|
|
41
|
+
svelte: 'PUBLIC_INSTANT_APP_ID',
|
|
42
|
+
vite: 'VITE_INSTANT_APP_ID',
|
|
43
|
+
expo: 'EXPO_PUBLIC_INSTANT_APP_ID',
|
|
44
|
+
nuxt: 'NUXT_PUBLIC_INSTANT_APP_ID',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// TODO: add instant.config.ts support
|
|
48
|
+
export const CurrentAppLive = (args: {
|
|
49
|
+
appId?: string;
|
|
50
|
+
coerce?: boolean;
|
|
51
|
+
title?: string;
|
|
52
|
+
applyEnv?: boolean;
|
|
53
|
+
}) =>
|
|
54
|
+
Layer.effect(
|
|
55
|
+
CurrentApp,
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
if (args.appId) {
|
|
58
|
+
return {
|
|
59
|
+
appId: args.appId,
|
|
60
|
+
source: 'flag' as const,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Detect from ENV
|
|
65
|
+
const found = Object.keys(potentialEnvs)
|
|
66
|
+
.map((type) => {
|
|
67
|
+
const envName = potentialEnvs[type];
|
|
68
|
+
const value = process.env[envName];
|
|
69
|
+
return { type, envName, value };
|
|
70
|
+
})
|
|
71
|
+
.find(({ value }) => !!value);
|
|
72
|
+
|
|
73
|
+
if (found?.value && !isUUID(found.value)) {
|
|
74
|
+
return yield* new CurrentAppContextError({
|
|
75
|
+
message: `Invalid UUID: ${found.value}`,
|
|
76
|
+
});
|
|
77
|
+
} else if (found?.value) {
|
|
78
|
+
return {
|
|
79
|
+
appId: found?.value,
|
|
80
|
+
source: 'env' as const,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return yield* new AppNotFoundError({
|
|
84
|
+
message: 'No app found',
|
|
85
|
+
});
|
|
86
|
+
}).pipe(
|
|
87
|
+
// coerce into new app if app not found
|
|
88
|
+
Effect.catchTag('AppNotFoundError', () =>
|
|
89
|
+
Effect.gen(function* () {
|
|
90
|
+
if (!args.coerce)
|
|
91
|
+
return yield* new AppNotFoundError({ message: 'No app found' });
|
|
92
|
+
|
|
93
|
+
// coerce into a new app
|
|
94
|
+
const globalOpts = yield* GlobalOpts;
|
|
95
|
+
if (globalOpts.yes) {
|
|
96
|
+
if (!args.title) {
|
|
97
|
+
return yield* new CurrentAppContextError({
|
|
98
|
+
message: `Title is required when using --yes and no app is linked`,
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
return yield* createApp(args.title);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return yield* promptImportOrCreateApp;
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
|
|
109
|
+
// Handle save env
|
|
110
|
+
Effect.tap((app) =>
|
|
111
|
+
Effect.gen(function* () {
|
|
112
|
+
if (
|
|
113
|
+
args.applyEnv &&
|
|
114
|
+
(app.source === 'import' || app.source == 'create')
|
|
115
|
+
) {
|
|
116
|
+
yield* handleEnv(app);
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const createApp = Effect.fn(function* (title: string, orgId?: string) {
|
|
124
|
+
const id = randomUUID();
|
|
125
|
+
const token = randomUUID();
|
|
126
|
+
const app = { id, title, admin_token: token, org_id: orgId };
|
|
127
|
+
|
|
128
|
+
const http = yield* InstantHttpAuthed;
|
|
129
|
+
yield* HttpClientRequest.post('/dash/apps').pipe(
|
|
130
|
+
HttpClientRequest.bodyJson(app),
|
|
131
|
+
Effect.flatMap(http.execute),
|
|
132
|
+
);
|
|
133
|
+
return {
|
|
134
|
+
appId: id,
|
|
135
|
+
source: 'create',
|
|
136
|
+
adminToken: token,
|
|
137
|
+
} satisfies CurrentAppInfo;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const promptImportOrCreateApp = Effect.gen(function* () {
|
|
141
|
+
const api = yield* getSimpleApi;
|
|
142
|
+
const result = yield* runUIEffect(
|
|
143
|
+
new UI.AppSelector({
|
|
144
|
+
allowEphemeral: true,
|
|
145
|
+
allowCreate: true,
|
|
146
|
+
api,
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
appId: result.appId,
|
|
152
|
+
source: result.approach,
|
|
153
|
+
adminToken: result.adminToken,
|
|
154
|
+
} satisfies CurrentAppInfo;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const getSimpleApi = Effect.gen(function* () {
|
|
158
|
+
const effectRuntime = yield* Effect.runtime<never>();
|
|
159
|
+
|
|
160
|
+
const http = yield* InstantHttpAuthed;
|
|
161
|
+
const dashData = yield* http
|
|
162
|
+
.get('/dash')
|
|
163
|
+
.pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Any)));
|
|
164
|
+
const platform = yield* PlatformApi;
|
|
165
|
+
|
|
166
|
+
const baseUrl = yield* getBaseUrl;
|
|
167
|
+
const { authToken } = yield* AuthToken;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
getDash: () => dashData,
|
|
171
|
+
createApp: async (title, orgId) => {
|
|
172
|
+
return Runtime.runPromise(
|
|
173
|
+
effectRuntime,
|
|
174
|
+
createApp(title, orgId).pipe(
|
|
175
|
+
Effect.provideService(InstantHttpAuthed, http),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
createEphemeralApp: async (title) => {
|
|
181
|
+
return await Runtime.runPromise(
|
|
182
|
+
effectRuntime,
|
|
183
|
+
Effect.gen(function* () {
|
|
184
|
+
const platform = yield* PlatformApi;
|
|
185
|
+
const response = yield* platform.use(
|
|
186
|
+
(p) => p.createTemporaryApp({ title: title }),
|
|
187
|
+
'Error creating temporary app',
|
|
188
|
+
);
|
|
189
|
+
return {
|
|
190
|
+
appId: response.app.id,
|
|
191
|
+
adminToken: response.app.adminToken,
|
|
192
|
+
};
|
|
193
|
+
}).pipe(Effect.provideService(PlatformApi, platform)),
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
async getAppsForOrg(orgId) {
|
|
198
|
+
const response = await fetch(baseUrl + '/dash/orgs/' + orgId, {
|
|
199
|
+
headers: {
|
|
200
|
+
Authorization: `Bearer ${authToken}`,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
const data = await response.json();
|
|
204
|
+
return { apps: data.apps };
|
|
205
|
+
},
|
|
206
|
+
} satisfies UI.AppSelectorApi;
|
|
207
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { program } from 'commander';
|
|
2
|
+
import { Context, Effect, Layer } from 'effect';
|
|
3
|
+
|
|
4
|
+
export class GlobalOpts extends Context.Tag(
|
|
5
|
+
'instant-cli/new/context/globalOpts',
|
|
6
|
+
)<
|
|
7
|
+
GlobalOpts,
|
|
8
|
+
{
|
|
9
|
+
token?: string;
|
|
10
|
+
yes: boolean;
|
|
11
|
+
env?: string;
|
|
12
|
+
}
|
|
13
|
+
>() {}
|
|
14
|
+
|
|
15
|
+
export const GlobalOptsLive = Layer.effect(
|
|
16
|
+
GlobalOpts,
|
|
17
|
+
Effect.gen(function* () {
|
|
18
|
+
return {
|
|
19
|
+
yes: program.optsWithGlobals()?.yes || false,
|
|
20
|
+
};
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Data, Effect, Schema } from 'effect';
|
|
2
|
+
import { getBaseUrl } from '../lib/http.js';
|
|
3
|
+
import { PlatformApi as InstantPlatformApi } from '@instantdb/platform';
|
|
4
|
+
|
|
5
|
+
export class PlatformApiError extends Data.TaggedError('PlatformApiError')<{
|
|
6
|
+
message: string;
|
|
7
|
+
cause: unknown;
|
|
8
|
+
}> {}
|
|
9
|
+
|
|
10
|
+
export class PlatformApi extends Effect.Service<PlatformApi>()(
|
|
11
|
+
'instant-cli/new/context/platformApi',
|
|
12
|
+
{
|
|
13
|
+
effect: Effect.gen(function* () {
|
|
14
|
+
const origin = yield* getBaseUrl;
|
|
15
|
+
const apiClient = new InstantPlatformApi({
|
|
16
|
+
apiURI: origin,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
use: <R>(
|
|
21
|
+
fun: (api: typeof apiClient) => Promise<R>,
|
|
22
|
+
errorMessage?: string,
|
|
23
|
+
) =>
|
|
24
|
+
Effect.tryPromise({
|
|
25
|
+
try: (_signal) => fun(apiClient),
|
|
26
|
+
catch: (e) =>
|
|
27
|
+
new PlatformApiError({
|
|
28
|
+
message: errorMessage || 'Error using platform api',
|
|
29
|
+
cause: e,
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
) {}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Context, Data, Effect, Layer } from 'effect';
|
|
2
|
+
import { detect } from 'package-manager-detector/detect';
|
|
3
|
+
import { PackageJson, readPackage } from 'pkg-types';
|
|
4
|
+
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import { UI } from '../../ui/index.js';
|
|
8
|
+
import { findProjectDir } from '../../util/projectDir.js';
|
|
9
|
+
import { runUIEffect } from '../lib/ui.js';
|
|
10
|
+
|
|
11
|
+
export class ProjectInfo extends Context.Tag(
|
|
12
|
+
'instant-cli/new/context/projectInfo',
|
|
13
|
+
)<
|
|
14
|
+
ProjectInfo,
|
|
15
|
+
{
|
|
16
|
+
pkgDir: string;
|
|
17
|
+
projectType: 'node' | 'deno';
|
|
18
|
+
instantModuleName: string;
|
|
19
|
+
}
|
|
20
|
+
>() {}
|
|
21
|
+
|
|
22
|
+
const execAsync = promisify(exec);
|
|
23
|
+
|
|
24
|
+
export const PACKAGE_ALIAS_AND_FULL_NAMES = {
|
|
25
|
+
react: '@instantdb/react',
|
|
26
|
+
'react-native': '@instantdb/react-native',
|
|
27
|
+
core: '@instantdb/core',
|
|
28
|
+
admin: '@instantdb/admin',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export class ProjectInfoError extends Data.TaggedError('ProjectInfoError')<{
|
|
32
|
+
message: string;
|
|
33
|
+
cause?: unknown;
|
|
34
|
+
}> {}
|
|
35
|
+
|
|
36
|
+
const getProjectInfo = (
|
|
37
|
+
coerce: boolean = true,
|
|
38
|
+
packageName?: keyof typeof PACKAGE_ALIAS_AND_FULL_NAMES,
|
|
39
|
+
) =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const projectDir = yield* Effect.tryPromise({
|
|
42
|
+
try: () => findProjectDir(),
|
|
43
|
+
catch: (e) =>
|
|
44
|
+
new ProjectInfoError({ message: "Couldn't get project dir" }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!projectDir) {
|
|
48
|
+
return yield* new ProjectInfoError({
|
|
49
|
+
message: "Couldn't find a project directory (package.json)",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (projectDir.type === 'deno') {
|
|
54
|
+
return {
|
|
55
|
+
pkgDir: projectDir.dir,
|
|
56
|
+
projectType: projectDir.type,
|
|
57
|
+
instantModuleName: '@instantdb/core',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const pkgJson = yield* Effect.tryPromise({
|
|
62
|
+
try: () => readPackage(),
|
|
63
|
+
catch: () =>
|
|
64
|
+
new ProjectInfoError({ message: "Couldn't read package.json" }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let moduleName = getInstantModuleName(pkgJson);
|
|
68
|
+
if (!moduleName && !coerce) {
|
|
69
|
+
return yield* new ProjectInfoError({
|
|
70
|
+
message: 'No instant client library installed',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// TODO: Clean up with option
|
|
75
|
+
const packageManager = yield* Effect.tryPromise(() => detect()).pipe(
|
|
76
|
+
Effect.flatMap(Effect.fromNullable),
|
|
77
|
+
Effect.mapError(
|
|
78
|
+
() =>
|
|
79
|
+
new ProjectInfoError({
|
|
80
|
+
message: 'Failed to detect package manager',
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!moduleName && coerce) {
|
|
86
|
+
// install the packages
|
|
87
|
+
if (packageName) {
|
|
88
|
+
moduleName = PACKAGE_ALIAS_AND_FULL_NAMES[packageName];
|
|
89
|
+
} else {
|
|
90
|
+
moduleName = yield* runUIEffect(
|
|
91
|
+
new UI.Select({
|
|
92
|
+
promptText: 'Which package would you like to use?',
|
|
93
|
+
options: [
|
|
94
|
+
{ label: '@instantdb/react', value: '@instantdb/react' },
|
|
95
|
+
{
|
|
96
|
+
label: '@instantdb/react-native',
|
|
97
|
+
value: '@instantdb/react-native',
|
|
98
|
+
},
|
|
99
|
+
{ label: '@instantdb/core', value: '@instantdb/core' },
|
|
100
|
+
{ label: '@instantdb/admin', value: '@instantdb/admin' },
|
|
101
|
+
],
|
|
102
|
+
}),
|
|
103
|
+
).pipe(
|
|
104
|
+
Effect.flatMap(Effect.fromNullable),
|
|
105
|
+
Effect.mapError(
|
|
106
|
+
() =>
|
|
107
|
+
new ProjectInfoError({
|
|
108
|
+
message: 'Failed to select package',
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const packagesToInstall = [moduleName];
|
|
114
|
+
if (moduleName === '@instantdb/react-native') {
|
|
115
|
+
packagesToInstall.push(
|
|
116
|
+
'react-native-get-random-values',
|
|
117
|
+
'@react-native-async-storage/async-storage',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const installCommand = getInstallCommand(
|
|
121
|
+
packageManager.agent,
|
|
122
|
+
packagesToInstall.join(' '),
|
|
123
|
+
);
|
|
124
|
+
console.log(installCommand);
|
|
125
|
+
yield* runUIEffect(
|
|
126
|
+
new UI.Spinner({
|
|
127
|
+
promise: execAsync(installCommand, {
|
|
128
|
+
cwd: projectDir.dir,
|
|
129
|
+
}),
|
|
130
|
+
errorText: 'Failed to install packages',
|
|
131
|
+
workingText: `Installing ${packagesToInstall.join(', ')} using ${packageManager.agent}...`,
|
|
132
|
+
doneText: `Installed ${packagesToInstall.join(', ')} using ${packageManager.agent}.`,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
pkgDir: projectDir.dir,
|
|
137
|
+
projectType: projectDir.type,
|
|
138
|
+
instantModuleName: moduleName,
|
|
139
|
+
};
|
|
140
|
+
} else {
|
|
141
|
+
return {
|
|
142
|
+
pkgDir: projectDir.dir,
|
|
143
|
+
projectType: projectDir.type,
|
|
144
|
+
instantModuleName: moduleName!,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export const ProjectInfoLive = (
|
|
150
|
+
coerce: boolean = true,
|
|
151
|
+
packageName?: keyof typeof PACKAGE_ALIAS_AND_FULL_NAMES,
|
|
152
|
+
) => Layer.effect(ProjectInfo, getProjectInfo(coerce, packageName));
|
|
153
|
+
|
|
154
|
+
function getInstantModuleName(pkgJson: PackageJson) {
|
|
155
|
+
const deps = pkgJson.dependencies || {};
|
|
156
|
+
const devDeps = pkgJson.devDependencies || {};
|
|
157
|
+
const instantModuleName = [
|
|
158
|
+
'@instantdb/react',
|
|
159
|
+
'@instantdb/react-native',
|
|
160
|
+
'@instantdb/core',
|
|
161
|
+
'@instantdb/admin',
|
|
162
|
+
].find((name) => deps[name] || devDeps[name]);
|
|
163
|
+
return instantModuleName;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getInstallCommand(packageManager: string, moduleName: string) {
|
|
167
|
+
if (packageManager === 'npm') {
|
|
168
|
+
return `npm install ${moduleName}`;
|
|
169
|
+
} else {
|
|
170
|
+
return `${packageManager} add ${moduleName}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/new/index.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Command, Option, program } from '@commander-js/extra-typings';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
import version from '../version.js';
|
|
5
|
+
import { initCommand } from './commands/init.js';
|
|
6
|
+
import { initWithoutFilesCommand } from './commands/initWithoutFiles.js';
|
|
7
|
+
import { loginCommand } from './commands/login.js';
|
|
8
|
+
import { logoutCommand } from './commands/logout.js';
|
|
9
|
+
import { loadEnv } from '../util/loadEnv.js';
|
|
10
|
+
import {
|
|
11
|
+
AuthLayerLive,
|
|
12
|
+
BaseLayerLive,
|
|
13
|
+
printRedErrors,
|
|
14
|
+
WithAppLayer,
|
|
15
|
+
} from './layer.js';
|
|
16
|
+
|
|
17
|
+
loadEnv();
|
|
18
|
+
|
|
19
|
+
export type ArgsFromCommand<C> =
|
|
20
|
+
C extends Command<any, infer R, any> ? R : never;
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.name('instant-cli')
|
|
24
|
+
.addOption(globalOption('-t --token <token>', 'Auth token override'))
|
|
25
|
+
.addOption(globalOption('-y --yes', "Answer 'yes' to all prompts"))
|
|
26
|
+
.addOption(globalOption('--env <file>', 'Use a specific .env file'))
|
|
27
|
+
.addOption(
|
|
28
|
+
globalOption('-v --version', 'Print the version number', () => {
|
|
29
|
+
console.log(version);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}),
|
|
32
|
+
)
|
|
33
|
+
.addHelpOption(globalOption('-h --help', 'Print the help text for a command'))
|
|
34
|
+
.usage(`<command> ${chalk.dim('[options] [args]')}`);
|
|
35
|
+
|
|
36
|
+
// Command List
|
|
37
|
+
export const initDef = program
|
|
38
|
+
.command('init')
|
|
39
|
+
.description('Set up a new project.')
|
|
40
|
+
.option(
|
|
41
|
+
'-a --app <app-id>',
|
|
42
|
+
'If you have an existing app ID, we can pull schema and perms from there.',
|
|
43
|
+
)
|
|
44
|
+
.option(
|
|
45
|
+
'-p --package <react|react-native|core|admin>',
|
|
46
|
+
'Which package to automatically install if there is not one installed already.',
|
|
47
|
+
)
|
|
48
|
+
.option('--title <title>', 'Title for the created app')
|
|
49
|
+
.action((options) => {
|
|
50
|
+
return Effect.runPromise(
|
|
51
|
+
initCommand(options).pipe(
|
|
52
|
+
Effect.provide(
|
|
53
|
+
WithAppLayer({
|
|
54
|
+
coerce: true,
|
|
55
|
+
title: options.title,
|
|
56
|
+
appId: options.app,
|
|
57
|
+
packageName: options.package as any,
|
|
58
|
+
applyEnv: true,
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
printRedErrors,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const initWithoutFilesDef = program
|
|
67
|
+
.command('init-without-files')
|
|
68
|
+
.description('Generate a new app id and admin token pair without any files.')
|
|
69
|
+
.option('--title <title>', 'Title for the created app.')
|
|
70
|
+
.option(
|
|
71
|
+
'--org-id <org-id>',
|
|
72
|
+
'Organization id for app. Cannot be used with --temp flag.',
|
|
73
|
+
)
|
|
74
|
+
.option(
|
|
75
|
+
'--temp',
|
|
76
|
+
'Create a temporary app which will automatically delete itself after >24 hours.',
|
|
77
|
+
)
|
|
78
|
+
.action((opts) => {
|
|
79
|
+
return Effect.runPromise(
|
|
80
|
+
initWithoutFilesCommand(opts).pipe(
|
|
81
|
+
Effect.provide(AuthLayerLive),
|
|
82
|
+
printRedErrors,
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const loginDef = program
|
|
88
|
+
.command('login')
|
|
89
|
+
.description('Log into your account')
|
|
90
|
+
.option('-p --print', 'Prints the auth token into the console.')
|
|
91
|
+
.option(
|
|
92
|
+
'--headless',
|
|
93
|
+
'Print the login URL instead of trying to open the browser',
|
|
94
|
+
)
|
|
95
|
+
.action(async (opts) => {
|
|
96
|
+
Effect.runPromise(
|
|
97
|
+
loginCommand(opts).pipe(Effect.provide(BaseLayerLive), printRedErrors),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const _logoutDef = program
|
|
102
|
+
.command('logout')
|
|
103
|
+
.description('Log out of your Instant account')
|
|
104
|
+
.action(async () => {
|
|
105
|
+
Effect.runPromise(
|
|
106
|
+
logoutCommand().pipe(Effect.provide(BaseLayerLive), printRedErrors),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
//// Program setup /////
|
|
111
|
+
|
|
112
|
+
function globalOption(
|
|
113
|
+
flags: string,
|
|
114
|
+
description?: string,
|
|
115
|
+
argParser?: (value: string, prev?: unknown) => unknown,
|
|
116
|
+
) {
|
|
117
|
+
const opt = new Option(flags, description);
|
|
118
|
+
if (argParser) {
|
|
119
|
+
opt.argParser(argParser);
|
|
120
|
+
}
|
|
121
|
+
// @ts-ignore
|
|
122
|
+
// __global does not exist on `Option`,
|
|
123
|
+
// but we use it in `getLocalAndGlobalOptions`, to produce
|
|
124
|
+
// our own custom list of local and global options.
|
|
125
|
+
// For more info, see the original PR:
|
|
126
|
+
// https://github.com/instantdb/instant/pull/505
|
|
127
|
+
opt.__global = true;
|
|
128
|
+
return opt;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getLocalAndGlobalOptions(cmd, helper) {
|
|
132
|
+
const mixOfLocalAndGlobal = helper.visibleOptions(cmd);
|
|
133
|
+
const localOptionsFromMix = mixOfLocalAndGlobal.filter(
|
|
134
|
+
(option) => !option.__global,
|
|
135
|
+
);
|
|
136
|
+
const globalOptionsFromMix = mixOfLocalAndGlobal.filter(
|
|
137
|
+
(option) => option.__global,
|
|
138
|
+
);
|
|
139
|
+
const globalOptions = helper.visibleGlobalOptions(cmd);
|
|
140
|
+
|
|
141
|
+
return [localOptionsFromMix, globalOptionsFromMix.concat(globalOptions)];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatHelp(cmd, helper) {
|
|
145
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
146
|
+
const helpWidth = helper.helpWidth || 80;
|
|
147
|
+
const itemIndentWidth = 2;
|
|
148
|
+
const itemSeparatorWidth = 2; // between term and description
|
|
149
|
+
function formatItem(term, description) {
|
|
150
|
+
if (description) {
|
|
151
|
+
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
|
152
|
+
return helper.wrap(
|
|
153
|
+
fullText,
|
|
154
|
+
helpWidth - itemIndentWidth,
|
|
155
|
+
termWidth + itemSeparatorWidth,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return term;
|
|
159
|
+
}
|
|
160
|
+
function formatList(textArray) {
|
|
161
|
+
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Usage
|
|
165
|
+
let output = [`${helper.commandUsage(cmd)}`, ''];
|
|
166
|
+
|
|
167
|
+
// Description
|
|
168
|
+
const commandDescription = helper.commandDescription(cmd);
|
|
169
|
+
if (commandDescription.length > 0) {
|
|
170
|
+
output = output.concat([helper.wrap(commandDescription, helpWidth, 0), '']);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Arguments
|
|
174
|
+
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
175
|
+
return formatItem(
|
|
176
|
+
helper.argumentTerm(argument),
|
|
177
|
+
helper.argumentDescription(argument),
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
if (argumentList.length > 0) {
|
|
181
|
+
output = output.concat([
|
|
182
|
+
chalk.dim.bold('Arguments'),
|
|
183
|
+
formatList(argumentList),
|
|
184
|
+
'',
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
const [visibleOptions, visibleGlobalOptions] = getLocalAndGlobalOptions(
|
|
188
|
+
cmd,
|
|
189
|
+
helper,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Options
|
|
193
|
+
const optionList = visibleOptions.map((option) => {
|
|
194
|
+
return formatItem(
|
|
195
|
+
helper.optionTerm(option),
|
|
196
|
+
helper.optionDescription(option),
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
if (optionList.length > 0) {
|
|
200
|
+
output = output.concat([
|
|
201
|
+
chalk.dim.bold('Options'),
|
|
202
|
+
formatList(optionList),
|
|
203
|
+
'',
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
// Commands
|
|
207
|
+
const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
|
208
|
+
return formatItem(
|
|
209
|
+
helper.subcommandTerm(cmd),
|
|
210
|
+
helper.subcommandDescription(cmd),
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
if (commandList.length > 0) {
|
|
214
|
+
output = output.concat([
|
|
215
|
+
chalk.dim.bold('Commands'),
|
|
216
|
+
formatList(commandList),
|
|
217
|
+
'',
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.showGlobalOptions) {
|
|
222
|
+
const globalOptionList = visibleGlobalOptions.map((option) => {
|
|
223
|
+
return formatItem(
|
|
224
|
+
helper.optionTerm(option),
|
|
225
|
+
helper.optionDescription(option),
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
if (globalOptionList.length > 0) {
|
|
229
|
+
output = output.concat([
|
|
230
|
+
chalk.dim.bold('Global Options'),
|
|
231
|
+
formatList(globalOptionList),
|
|
232
|
+
'',
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return output.join('\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
program.configureHelp({
|
|
241
|
+
showGlobalOptions: true,
|
|
242
|
+
formatHelp,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
program.parse(process.argv);
|