@cleosync/cosmos-cli 0.1.0
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/README.md +42 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +371 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/utils/fs.d.ts +6 -0
- package/dist/utils/fs.js +26 -0
- package/dist/utils/installer.d.ts +3 -0
- package/dist/utils/installer.js +29 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +18 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @cleosync/cosmos-cli
|
|
2
|
+
|
|
3
|
+
Config-driven project initialization for Cosmos UI.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @cleosync/cosmos-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Create `cosmos.config.ts` in your app, then run:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
cosmos init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The CLI reads enabled modules from config:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
modules: {
|
|
24
|
+
api: { baseUrl: process.env.EXPO_PUBLIC_API_URL },
|
|
25
|
+
query: true,
|
|
26
|
+
auth: { provider: 'custom', nin: true },
|
|
27
|
+
errors: true,
|
|
28
|
+
push: { provider: 'expo' },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`cosmos init` installs required peer packages and writes project-specific scaffolds. It does not copy design-system components into your app.
|
|
34
|
+
|
|
35
|
+
## Options
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
cosmos init --config cosmos.config.ts
|
|
39
|
+
cosmos init --package @cleosync/cosmos-ui
|
|
40
|
+
cosmos init --skip-install
|
|
41
|
+
cosmos init --dry-run
|
|
42
|
+
```
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { pathExists, readText, resolveFromCwd, writeFileIfMissing } from '../utils/fs.js';
|
|
4
|
+
import { installExpoPackages, installNpmPackages } from '../utils/installer.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
const moduleOrder = ['api', 'query', 'auth', 'errors', 'push'];
|
|
7
|
+
const npmPackages = {
|
|
8
|
+
api: ['axios'],
|
|
9
|
+
auth: ['zustand'],
|
|
10
|
+
errors: [],
|
|
11
|
+
push: [],
|
|
12
|
+
query: ['@tanstack/react-query'],
|
|
13
|
+
};
|
|
14
|
+
const expoPackages = {
|
|
15
|
+
api: [],
|
|
16
|
+
auth: ['expo-secure-store'],
|
|
17
|
+
errors: [],
|
|
18
|
+
push: ['expo-notifications', 'expo-device', 'expo-constants'],
|
|
19
|
+
query: [],
|
|
20
|
+
};
|
|
21
|
+
export async function initCommand(options) {
|
|
22
|
+
const configPath = resolveFromCwd(options.config);
|
|
23
|
+
if (!(await pathExists(configPath))) {
|
|
24
|
+
logger.error(`Could not find ${options.config}. Create it first, then run ${pc.bold('cosmos init')}.`);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const configSource = await readText(configPath);
|
|
29
|
+
const modules = getEnabledModules(configSource);
|
|
30
|
+
logger.title('Cosmos init');
|
|
31
|
+
logger.info(`${pc.gray('config')} ${path.relative(process.cwd(), configPath)}`);
|
|
32
|
+
logger.info(`${pc.gray('modules')} ${modules.length ? modules.join(', ') : 'none'}`);
|
|
33
|
+
if (modules.length === 0) {
|
|
34
|
+
logger.warn('No modules declared. Nothing to scaffold.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const npmDeps = unique(modules.flatMap((moduleName) => npmPackages[moduleName]));
|
|
38
|
+
const expoDeps = unique(modules.flatMap((moduleName) => expoPackages[moduleName]));
|
|
39
|
+
if (!options.skipInstall && !options.dryRun) {
|
|
40
|
+
if (npmDeps.length) {
|
|
41
|
+
logger.info(`${pc.gray('npm install')} ${npmDeps.join(' ')}`);
|
|
42
|
+
await installNpmPackages(npmDeps);
|
|
43
|
+
}
|
|
44
|
+
if (expoDeps.length) {
|
|
45
|
+
logger.info(`${pc.gray('npx expo install')} ${expoDeps.join(' ')}`);
|
|
46
|
+
await installExpoPackages(expoDeps);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (npmDeps.length || expoDeps.length) {
|
|
50
|
+
logger.dim(`install skipped: ${[...npmDeps, ...expoDeps].join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
const files = getScaffoldFiles(modules, options.package);
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const destination = resolveFromCwd(file.path);
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
logger.info(`${pc.gray('[dry-run]')} write ${file.path}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const written = await writeFileIfMissing(destination, file.contents);
|
|
60
|
+
logger.info(`${written ? pc.green('[created]') : pc.yellow('[exists]')} ${file.path}`);
|
|
61
|
+
}
|
|
62
|
+
logger.success('Cosmos init complete.');
|
|
63
|
+
}
|
|
64
|
+
function getEnabledModules(configSource) {
|
|
65
|
+
const modulesBlock = getObjectOrArrayBlock(configSource, 'modules');
|
|
66
|
+
if (!modulesBlock) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return moduleOrder.filter((moduleName) => isModuleEnabled(modulesBlock, moduleName));
|
|
70
|
+
}
|
|
71
|
+
function isModuleEnabled(modulesBlock, moduleName) {
|
|
72
|
+
const arrayPattern = new RegExp(`['"\`]${moduleName}['"\`]`);
|
|
73
|
+
const objectPattern = new RegExp(`\\b${moduleName}\\s*:\\s*(?!false\\b|undefined\\b|null\\b)`);
|
|
74
|
+
return arrayPattern.test(modulesBlock) || objectPattern.test(modulesBlock);
|
|
75
|
+
}
|
|
76
|
+
function getObjectOrArrayBlock(source, propertyName) {
|
|
77
|
+
const propertyIndex = source.search(new RegExp(`\\b${propertyName}\\s*:`));
|
|
78
|
+
if (propertyIndex < 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const openerIndex = source.slice(propertyIndex).search(/[{\[]/);
|
|
82
|
+
if (openerIndex < 0) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const blockStart = propertyIndex + openerIndex;
|
|
86
|
+
const opener = source[blockStart];
|
|
87
|
+
const closer = opener === '[' ? ']' : '}';
|
|
88
|
+
let depth = 0;
|
|
89
|
+
for (let index = blockStart; index < source.length; index += 1) {
|
|
90
|
+
const char = source[index];
|
|
91
|
+
if (char === opener)
|
|
92
|
+
depth += 1;
|
|
93
|
+
if (char === closer)
|
|
94
|
+
depth -= 1;
|
|
95
|
+
if (depth === 0) {
|
|
96
|
+
return source.slice(blockStart, index + 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function getScaffoldFiles(modules, packageName) {
|
|
102
|
+
const files = [];
|
|
103
|
+
const has = (moduleName) => modules.includes(moduleName);
|
|
104
|
+
if (has('api')) {
|
|
105
|
+
files.push(file('lib/api/client.ts', apiClientTemplate(packageName)), file('lib/api/index.ts', "export { api } from './client';\n"));
|
|
106
|
+
}
|
|
107
|
+
if (has('query')) {
|
|
108
|
+
files.push(file('lib/query/client.ts', queryClientTemplate(packageName)), file('lib/query/provider.tsx', queryProviderTemplate(packageName)), file('lib/query/index.ts', "export { queryClient } from './client';\nexport { QueryProvider } from './provider';\n"));
|
|
109
|
+
}
|
|
110
|
+
if (has('auth')) {
|
|
111
|
+
files.push(file('features/auth/types.ts', authTypesTemplate()), file('features/auth/api/index.ts', authKeysTemplate()), file('features/auth/api/auth.api.ts', authApiTemplate(packageName)), file('features/auth/hooks/use-auth.queries.ts', authQueriesTemplate(packageName)), file('features/auth/hooks/use-auth.mutations.ts', authMutationsTemplate(packageName)), file('features/auth/index.ts', authIndexTemplate(packageName)));
|
|
112
|
+
}
|
|
113
|
+
if (has('errors')) {
|
|
114
|
+
files.push(file('lib/errors/index.ts', errorsIndexTemplate(packageName)), file('lib/errors/fallback.tsx', errorsFallbackTemplate(packageName)));
|
|
115
|
+
}
|
|
116
|
+
if (has('push')) {
|
|
117
|
+
files.push(file('features/notifications/types.ts', notificationsTypesTemplate(packageName)), file('features/notifications/hooks/use-notifications.queries.ts', notificationQueriesTemplate(packageName)), file('features/notifications/hooks/use-notifications.mutations.ts', notificationMutationsTemplate(packageName)), file('features/notifications/index.ts', notificationsIndexTemplate(packageName)));
|
|
118
|
+
}
|
|
119
|
+
files.push(file('app/_layout.cosmos.example.tsx', layoutExampleTemplate(packageName, modules)));
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
122
|
+
function file(filePath, contents) {
|
|
123
|
+
return { path: filePath, contents };
|
|
124
|
+
}
|
|
125
|
+
function unique(values) {
|
|
126
|
+
return Array.from(new Set(values)).filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
function apiClientTemplate(packageName) {
|
|
129
|
+
return `import axios from 'axios';
|
|
130
|
+
import { normalizeApiError } from '${packageName}';
|
|
131
|
+
|
|
132
|
+
export const api = axios.create({
|
|
133
|
+
baseURL: process.env.EXPO_PUBLIC_API_URL,
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
timeout: 15000,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
api.interceptors.request.use((config) => {
|
|
139
|
+
// TODO: inject auth token when your auth module is wired.
|
|
140
|
+
return config;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
api.interceptors.response.use(
|
|
144
|
+
(response) => response,
|
|
145
|
+
(error) => Promise.reject(normalizeApiError(error)),
|
|
146
|
+
);
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
function queryClientTemplate(packageName) {
|
|
150
|
+
return `import { createCosmosQueryClient } from '${packageName}';
|
|
151
|
+
|
|
152
|
+
export const queryClient = createCosmosQueryClient();
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
function queryProviderTemplate(packageName) {
|
|
156
|
+
return `export { QueryProvider } from '${packageName}';
|
|
157
|
+
export type { QueryProviderProps } from '${packageName}';
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
function authTypesTemplate() {
|
|
161
|
+
return `export interface User {
|
|
162
|
+
id: string;
|
|
163
|
+
email: string;
|
|
164
|
+
name?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface LoginPayload {
|
|
168
|
+
email: string;
|
|
169
|
+
password: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface RegisterPayload {
|
|
173
|
+
name: string;
|
|
174
|
+
email: string;
|
|
175
|
+
password: string;
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
function authKeysTemplate() {
|
|
180
|
+
return `export const authKeys = {
|
|
181
|
+
all: ['auth'] as const,
|
|
182
|
+
session: () => [...authKeys.all, 'session'] as const,
|
|
183
|
+
profile: (userId: string) => [...authKeys.all, 'profile', userId] as const,
|
|
184
|
+
};
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
function authApiTemplate(packageName) {
|
|
188
|
+
return `import { api } from '../../../lib/api';
|
|
189
|
+
import type { AuthTokens } from '${packageName}';
|
|
190
|
+
import type { LoginPayload, RegisterPayload, User } from '../types';
|
|
191
|
+
|
|
192
|
+
export async function login(payload: LoginPayload): Promise<{ user: User; tokens: AuthTokens }> {
|
|
193
|
+
const { data } = await api.post('/auth/login', payload);
|
|
194
|
+
return data;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function register(payload: RegisterPayload): Promise<{ user: User; tokens: AuthTokens }> {
|
|
198
|
+
const { data } = await api.post('/auth/register', payload);
|
|
199
|
+
return data;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function getSession(): Promise<User> {
|
|
203
|
+
const { data } = await api.get('/auth/session');
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function logout(): Promise<void> {
|
|
208
|
+
await api.post('/auth/logout');
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
function authQueriesTemplate(packageName) {
|
|
213
|
+
return `import { useQuery } from '@tanstack/react-query';
|
|
214
|
+
import { useAuthStore } from '${packageName}';
|
|
215
|
+
import { authKeys } from '../api';
|
|
216
|
+
import { getSession } from '../api/auth.api';
|
|
217
|
+
|
|
218
|
+
export function useSessionQuery() {
|
|
219
|
+
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
|
220
|
+
|
|
221
|
+
return useQuery({
|
|
222
|
+
enabled: isAuthenticated,
|
|
223
|
+
queryKey: authKeys.session(),
|
|
224
|
+
queryFn: async () => {
|
|
225
|
+
const user = await getSession();
|
|
226
|
+
useAuthStore.getState().setUser(user);
|
|
227
|
+
return user;
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
function authMutationsTemplate(packageName) {
|
|
234
|
+
return `import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
235
|
+
import { toast, useAuthStore } from '${packageName}';
|
|
236
|
+
import { authKeys } from '../api';
|
|
237
|
+
import { login, logout, register } from '../api/auth.api';
|
|
238
|
+
|
|
239
|
+
export function useLoginMutation() {
|
|
240
|
+
const queryClient = useQueryClient();
|
|
241
|
+
|
|
242
|
+
return useMutation({
|
|
243
|
+
mutationFn: login,
|
|
244
|
+
onSuccess: async ({ tokens, user }) => {
|
|
245
|
+
useAuthStore.getState().setTokens(tokens);
|
|
246
|
+
useAuthStore.getState().setUser(user);
|
|
247
|
+
await queryClient.invalidateQueries({ queryKey: authKeys.session() });
|
|
248
|
+
},
|
|
249
|
+
onError: () => {
|
|
250
|
+
toast.error('Unable to log in', { description: 'Check your details and try again.' });
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function useRegisterMutation() {
|
|
256
|
+
return useMutation({
|
|
257
|
+
mutationFn: register,
|
|
258
|
+
onError: () => {
|
|
259
|
+
toast.error('Unable to create account', { description: 'Please review the form and try again.' });
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function useLogoutMutation() {
|
|
265
|
+
const queryClient = useQueryClient();
|
|
266
|
+
|
|
267
|
+
return useMutation({
|
|
268
|
+
mutationFn: logout,
|
|
269
|
+
onSettled: () => {
|
|
270
|
+
useAuthStore.getState().clearAuth();
|
|
271
|
+
queryClient.clear();
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
function authIndexTemplate(packageName) {
|
|
278
|
+
return `export { AuthGuard, ProtectedRoute, useAuthStore } from '${packageName}';
|
|
279
|
+
export * from './api/auth.api';
|
|
280
|
+
export * from './hooks/use-auth.queries';
|
|
281
|
+
export * from './hooks/use-auth.mutations';
|
|
282
|
+
export * from './types';
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
function errorsIndexTemplate(packageName) {
|
|
286
|
+
return `export {
|
|
287
|
+
AppError,
|
|
288
|
+
CosmosErrorFallback,
|
|
289
|
+
ErrorBoundary,
|
|
290
|
+
globalErrorHandler,
|
|
291
|
+
NetworkError,
|
|
292
|
+
ValidationError,
|
|
293
|
+
} from '${packageName}';
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
function errorsFallbackTemplate(packageName) {
|
|
297
|
+
return `import { CosmosErrorFallback as BaseCosmosErrorFallback } from '${packageName}';
|
|
298
|
+
|
|
299
|
+
export function CosmosErrorFallback(props: { error?: Error; reset: () => void }) {
|
|
300
|
+
return <BaseCosmosErrorFallback {...props} />;
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
function notificationsTypesTemplate(packageName) {
|
|
305
|
+
return `export type { NotificationData, NotificationHandler, PushToken } from '${packageName}';
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
function notificationQueriesTemplate(packageName) {
|
|
309
|
+
return `import { useQuery } from '@tanstack/react-query';
|
|
310
|
+
import { getPushPermissionStatus } from '${packageName}';
|
|
311
|
+
|
|
312
|
+
export function usePushPermissionQuery() {
|
|
313
|
+
return useQuery({
|
|
314
|
+
queryKey: ['notifications', 'permission'],
|
|
315
|
+
queryFn: getPushPermissionStatus,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
function notificationMutationsTemplate(packageName) {
|
|
321
|
+
return `import { useMutation } from '@tanstack/react-query';
|
|
322
|
+
import { registerForPushNotificationsAsync, sendLocalNotification } from '${packageName}';
|
|
323
|
+
|
|
324
|
+
export function useRegisterPushTokenMutation() {
|
|
325
|
+
return useMutation({
|
|
326
|
+
mutationFn: async () => {
|
|
327
|
+
const token = await registerForPushNotificationsAsync();
|
|
328
|
+
// TODO: send token to your backend.
|
|
329
|
+
return { token };
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function useSendLocalNotificationMutation() {
|
|
335
|
+
return useMutation({
|
|
336
|
+
mutationFn: ({ title, body }: { title: string; body: string }) =>
|
|
337
|
+
sendLocalNotification(title, body),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
function notificationsIndexTemplate(packageName) {
|
|
343
|
+
return `export {
|
|
344
|
+
getPushPermissionStatus,
|
|
345
|
+
registerForPushNotificationsAsync,
|
|
346
|
+
sendLocalNotification,
|
|
347
|
+
} from '${packageName}';
|
|
348
|
+
export * from './hooks/use-notifications.queries';
|
|
349
|
+
export * from './hooks/use-notifications.mutations';
|
|
350
|
+
export * from './types';
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
function layoutExampleTemplate(packageName, modules) {
|
|
354
|
+
const hasQuery = modules.includes('query');
|
|
355
|
+
const hasErrors = modules.includes('errors');
|
|
356
|
+
return `import { CosmosProvider${hasErrors ? ', ErrorBoundary, CosmosErrorFallback' : ''} } from '${packageName}';
|
|
357
|
+
${hasQuery ? "import { QueryProvider } from '../lib/query';\n" : ''}import config from '../cosmos.config';
|
|
358
|
+
|
|
359
|
+
export default function RootLayout() {
|
|
360
|
+
const app = (
|
|
361
|
+
<CosmosProvider config={config}>
|
|
362
|
+
{/* TODO: render your router stack here */}
|
|
363
|
+
</CosmosProvider>
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const withQuery = ${hasQuery ? '<QueryProvider>{app}</QueryProvider>' : 'app'};
|
|
367
|
+
|
|
368
|
+
return ${hasErrors ? '<ErrorBoundary fallback={(props) => <CosmosErrorFallback {...props} />}>{withQuery}</ErrorBoundary>' : 'withQuery'};
|
|
369
|
+
}
|
|
370
|
+
`;
|
|
371
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { initCommand } from './commands/init.js';
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program.name('cosmos').description('Cosmos UI project setup CLI').version('0.1.0');
|
|
6
|
+
program
|
|
7
|
+
.command('init')
|
|
8
|
+
.description('Initialize Cosmos UI from cosmos.config.ts')
|
|
9
|
+
.option('--config <path>', 'path to cosmos config', 'cosmos.config.ts')
|
|
10
|
+
.option('--package <name>', 'Cosmos UI package import name', '@cleosync/cosmos-ui')
|
|
11
|
+
.option('--skip-install', 'write scaffold files without installing peer packages')
|
|
12
|
+
.option('--dry-run', 'print what would happen without writing files or installing packages')
|
|
13
|
+
.action(initCommand);
|
|
14
|
+
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function pathExists(filePath: string): Promise<boolean>;
|
|
2
|
+
export declare function copyTemplate(source: string, destination: string): Promise<void>;
|
|
3
|
+
export declare function readText(filePath: string): Promise<string>;
|
|
4
|
+
export declare function writeFileIfMissing(filePath: string, contents: string): Promise<boolean>;
|
|
5
|
+
export declare function interpolateTemplate(template: string, values: Record<string, string>): string;
|
|
6
|
+
export declare function resolveFromCwd(...segments: string[]): string;
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function pathExists(filePath) {
|
|
4
|
+
return fs.pathExists(filePath);
|
|
5
|
+
}
|
|
6
|
+
export async function copyTemplate(source, destination) {
|
|
7
|
+
await fs.ensureDir(path.dirname(destination));
|
|
8
|
+
await fs.copy(source, destination, { overwrite: false });
|
|
9
|
+
}
|
|
10
|
+
export async function readText(filePath) {
|
|
11
|
+
return fs.readFile(filePath, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
export async function writeFileIfMissing(filePath, contents) {
|
|
14
|
+
if (await fs.pathExists(filePath)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
18
|
+
await fs.writeFile(filePath, contents);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
export function interpolateTemplate(template, values) {
|
|
22
|
+
return Object.entries(values).reduce((output, [key, value]) => output.replaceAll(`{{${key}}}`, value), template);
|
|
23
|
+
}
|
|
24
|
+
export function resolveFromCwd(...segments) {
|
|
25
|
+
return path.resolve(process.cwd(), ...segments);
|
|
26
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function runCommand(command: string, args: string[], cwd?: string): Promise<void>;
|
|
2
|
+
export declare function installNpmPackages(packages: string[], cwd?: string): Promise<void>;
|
|
3
|
+
export declare function installExpoPackages(packages: string[], cwd?: string): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export function runCommand(command, args, cwd = process.cwd()) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const child = spawn(command, args, {
|
|
5
|
+
cwd,
|
|
6
|
+
shell: true,
|
|
7
|
+
stdio: 'inherit',
|
|
8
|
+
});
|
|
9
|
+
child.on('close', (code) => {
|
|
10
|
+
if (code === 0) {
|
|
11
|
+
resolve();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function installNpmPackages(packages, cwd = process.cwd()) {
|
|
19
|
+
if (packages.length === 0) {
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
return runCommand('npm', ['install', ...packages], cwd);
|
|
23
|
+
}
|
|
24
|
+
export function installExpoPackages(packages, cwd = process.cwd()) {
|
|
25
|
+
if (packages.length === 0) {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
return runCommand('npx', ['expo', 'install', ...packages], cwd);
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const logger: {
|
|
2
|
+
dim: (message: string) => void;
|
|
3
|
+
error: (message: string) => void;
|
|
4
|
+
info: (message: string) => void;
|
|
5
|
+
success: (message: string) => void;
|
|
6
|
+
title: (message: string) => void;
|
|
7
|
+
warn: (message: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function formatStatus(installed: boolean, planned: boolean): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export const logger = {
|
|
3
|
+
dim: (message) => console.log(pc.gray(message)),
|
|
4
|
+
error: (message) => console.error(pc.red(message)),
|
|
5
|
+
info: (message) => console.log(message),
|
|
6
|
+
success: (message) => console.log(pc.green(message)),
|
|
7
|
+
title: (message) => console.log(pc.bold(message)),
|
|
8
|
+
warn: (message) => console.log(pc.yellow(message)),
|
|
9
|
+
};
|
|
10
|
+
export function formatStatus(installed, planned) {
|
|
11
|
+
if (installed) {
|
|
12
|
+
return pc.green('installed');
|
|
13
|
+
}
|
|
14
|
+
if (planned) {
|
|
15
|
+
return pc.gray('planned');
|
|
16
|
+
}
|
|
17
|
+
return pc.white('available');
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cleosync/cosmos-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Config-driven Cosmos UI project initialization CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"cosmos": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"init": "node dist/index.js init",
|
|
17
|
+
"prepare": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"package.json"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cosmos",
|
|
26
|
+
"cleosync",
|
|
27
|
+
"react-native",
|
|
28
|
+
"expo",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"author": "Cleosync",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"commander": "^14.0.3",
|
|
35
|
+
"fs-extra": "^11.3.5",
|
|
36
|
+
"picocolors": "^1.1.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/fs-extra": "^11.0.4",
|
|
40
|
+
"@types/node": "^25.6.2",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
}
|
|
43
|
+
}
|