@creatorem/cli 0.0.1 → 1.0.4

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 (39) hide show
  1. package/package.json +26 -5
  2. package/src/cli.tsx +141 -0
  3. package/src/commands/create-dashboard.tsx +455 -0
  4. package/src/commands/create-mobile.tsx +555 -0
  5. package/src/commands/create.tsx +1119 -0
  6. package/src/commands/generate-migration.mjs +17 -66
  7. package/src/commands/generate-migration.tsx +46 -0
  8. package/src/commands/generate-schemas.mjs +2 -2
  9. package/src/commands/generate-schemas.tsx +36 -0
  10. package/src/dashboard-features/ai/index.ts +102 -0
  11. package/src/dashboard-features/analytics/index.ts +31 -0
  12. package/src/dashboard-features/billing/index.ts +349 -0
  13. package/src/dashboard-features/content-type/index.ts +64 -0
  14. package/src/dashboard-features/email-templates/index.ts +17 -0
  15. package/src/dashboard-features/emailer/index.ts +27 -0
  16. package/src/dashboard-features/index.ts +28 -0
  17. package/src/dashboard-features/keybindings/index.ts +52 -0
  18. package/src/dashboard-features/manager.ts +349 -0
  19. package/src/dashboard-features/monitoring/index.ts +16 -0
  20. package/src/dashboard-features/notification/index.ts +40 -0
  21. package/src/dashboard-features/onboarding/index.ts +65 -0
  22. package/src/dashboard-features/organization/index.ts +38 -0
  23. package/src/dashboard-features/types.ts +41 -0
  24. package/src/mobile-features/index.ts +12 -0
  25. package/src/mobile-features/manager.ts +1 -0
  26. package/src/mobile-features/notification/index.ts +41 -0
  27. package/src/mobile-features/onboarding/index.ts +35 -0
  28. package/src/mobile-features/organization/index.ts +38 -0
  29. package/src/mobile-features/types.ts +1 -0
  30. package/src/shims/signal-exit.js +9 -0
  31. package/src/ui/app.tsx +68 -0
  32. package/src/ui/multi-select.tsx +106 -0
  33. package/src/utils/ast.ts +422 -0
  34. package/src/utils/env-template.ts +635 -0
  35. package/tests/test-cli-features.sh +81 -0
  36. package/tests/test-cli-mobile.sh +65 -0
  37. package/tsconfig.json +15 -0
  38. package/tsup.config.ts +21 -0
  39. package/bin/cli.mjs +0 -40
@@ -1,36 +1,8 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
2
2
  import { basename, join } from 'path';
3
- import { showHelp } from './help.mjs';
4
3
 
5
- export function runGenerateMigration(args) {
6
- // Parse arguments specific to this command
7
- let includeEnvSchemas = '';
8
- let listEnvSchemas = false;
9
-
10
- for (let i = 0; i < args.length; i++) {
11
- const arg = args[i];
12
- switch (arg) {
13
- case '--app-schemas': {
14
- const nextArg = args[i + 1];
15
- if (i + 1 >= args.length || !nextArg || nextArg.startsWith('--')) {
16
- console.error('Error: --app-schemas requires a comma-separated list of schema names');
17
- console.error('Example: --app-schemas booking,other-schema');
18
- process.exit(1);
19
- }
20
- includeEnvSchemas = nextArg;
21
- i++;
22
- break;
23
- }
24
- case '--list-app-schemas':
25
- listEnvSchemas = true;
26
- break;
27
- default:
28
- console.error(`Unknown option: ${arg}`);
29
- showHelp();
30
- process.exit(1);
31
- }
32
- }
33
4
 
5
+ export function runGenerateMigration(args) {
34
6
  // Default paths based on CWD
35
7
  const baseDir = process.cwd();
36
8
  const supabaseDir = join(baseDir, 'supabase');
@@ -38,26 +10,11 @@ export function runGenerateMigration(args) {
38
10
  const schemasDir = join(supabaseDir, 'schemas');
39
11
  const migrationsDir = join(supabaseDir, 'migrations');
40
12
 
41
- if (listEnvSchemas) {
42
- console.log('Available environment schemas:');
43
- if (existsSync(envSchemasDir)) {
44
- const files = readdirSync(envSchemasDir).filter((file) => file.endsWith('.sql'));
45
- files.forEach((file) => {
46
- console.log(` ${basename(file, '.sql')}`);
47
- });
48
- } else {
49
- console.log('No app-schemas directory found at ' + envSchemasDir);
50
- }
51
- process.exit(0);
52
- }
53
-
54
13
  const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+/, '').replace('T', '');
55
14
  const migrationFile = join(migrationsDir, `${timestamp}_generated_from_schemas.sql`);
56
15
 
57
16
  console.log('Generating migration file from schema files...');
58
- if (includeEnvSchemas) {
59
- console.log(`Environment schemas will be included: ${includeEnvSchemas}`);
60
- }
17
+ console.log('Including all schemas from app-schemas directory...');
61
18
 
62
19
  // Create migrations directory if it doesn't exist
63
20
  if (!existsSync(migrationsDir)) {
@@ -85,27 +42,21 @@ export function runGenerateMigration(args) {
85
42
  console.warn(`Warning: schemas directory not found at ${schemasDir}`);
86
43
  }
87
44
 
88
- // Add environment schemas if specified
89
- if (includeEnvSchemas) {
90
- const envSchemas = includeEnvSchemas.split(',');
91
-
92
- envSchemas.forEach((schema) => {
93
- if (!existsSync(envSchemasDir)) {
94
- console.warn(`Warning: app-schemas directory not found at ${envSchemasDir}`);
95
- return;
96
- }
97
-
98
- const envFiles = readdirSync(envSchemasDir)
99
- .filter((file) => file.includes(schema) && file.endsWith('.sql'))
100
- .sort()
101
- .map((file) => join(envSchemasDir, file));
102
-
103
- if (envFiles.length === 0) {
104
- console.warn(`Warning: No environment schema files found for '${schema}'`);
105
- } else {
106
- schemaFiles.push(...envFiles);
107
- }
108
- });
45
+ // Add all environment schemas
46
+ if (existsSync(envSchemasDir)) {
47
+ console.log(`Checking app-schemas directory: ${envSchemasDir}`);
48
+ const envFiles = readdirSync(envSchemasDir)
49
+ .filter((file) => file.endsWith('.sql'))
50
+ .sort()
51
+ .map((file) => join(envSchemasDir, file));
52
+
53
+ if (envFiles.length === 0) {
54
+ console.log('No schema files found in app-schemas directory');
55
+ } else {
56
+ schemaFiles.push(...envFiles);
57
+ }
58
+ } else {
59
+ console.log(`No app-schemas directory found at ${envSchemasDir}, skipping...`);
109
60
  }
110
61
 
111
62
  // Check if any schema files were found
@@ -0,0 +1,46 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { runGenerateMigration } from '../commands/generate-migration.mjs';
4
+
5
+ type Props = {
6
+ args: string[];
7
+ };
8
+
9
+ export default function GenerateMigration({ args }: Props) {
10
+ const [status, setStatus] = useState<'running' | 'success' | 'error'>('running');
11
+ const [error, setError] = useState<string | null>(null);
12
+
13
+ useEffect(() => {
14
+ try {
15
+ // We are reusing the logic from the mjs file for now.
16
+ // Ideally we would rewrite it to be more "reactive" or just emit events,
17
+ // but for now we just wrap the execution.
18
+ // Note: runGenerateMigration currently uses console.log which might interfere with Ink.
19
+ // Implementing a proper Ink version would require refactoring the logic to not use console.log directly
20
+ // or capturing it. For this first pass, we'll let it output to stdout/stderr
21
+ // and just show a completion message here.
22
+ // However, Ink captures stdout, so console.log might get eaten or rendered weirdly if not using Static.
23
+ // A better approach for the migration command (which is synchronous file ops) might be to just run it.
24
+
25
+ runGenerateMigration(args);
26
+ setStatus('success');
27
+ } catch (e: any) {
28
+ setError(e.message || String(e));
29
+ setStatus('error');
30
+ }
31
+ }, [args]);
32
+
33
+ if (status === 'running') {
34
+ return <Text>Generating migration...</Text>;
35
+ }
36
+
37
+ if (status === 'error') {
38
+ return <Text color="red">Error: {error}</Text>;
39
+ }
40
+
41
+ return (
42
+ <Box flexDirection="column">
43
+ <Text color="green">Migration generated successfully!</Text>
44
+ </Box>
45
+ );
46
+ }
@@ -17,7 +17,7 @@ export function runGenerateSchemas(args) {
17
17
  let data;
18
18
  let inputDir;
19
19
 
20
- let arg0 = 'cm-supabase'; // Default to looking for cm-supabase folder in cwd
20
+ let arg0 = '.creatorem'; // Default to looking for .creatorem folder in cwd
21
21
  if (args.length > 0) {
22
22
  arg0 = args[0].trim();
23
23
  }
@@ -42,7 +42,7 @@ export function runGenerateSchemas(args) {
42
42
  // If directory, look for setup.json
43
43
  inputPath = join(inputPath, 'setup.json');
44
44
  if (!existsSync(inputPath)) {
45
- console.error(`Error: setup.json not found in directory ${args[0]} (defaults to setup.json if directory provided)`);
45
+ console.error(`Error: setup.json not found in directory ${arg0} (defaults to setup.json if directory provided)`);
46
46
  process.exit(1);
47
47
  }
48
48
  }
@@ -0,0 +1,36 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Text, Box } from 'ink';
3
+ import { runGenerateSchemas } from '../commands/generate-schemas.mjs';
4
+
5
+ type Props = {
6
+ args: string[];
7
+ };
8
+
9
+ export default function GenerateSchemas({ args }: Props) {
10
+ const [status, setStatus] = useState<'running' | 'success' | 'error'>('running');
11
+ const [error, setError] = useState<string | null>(null);
12
+
13
+ useEffect(() => {
14
+ try {
15
+ runGenerateSchemas(args);
16
+ setStatus('success');
17
+ } catch (e: any) {
18
+ setError(e.message || String(e));
19
+ setStatus('error');
20
+ }
21
+ }, [args]);
22
+
23
+ if (status === 'running') {
24
+ return <Text>Generating schemas...</Text>;
25
+ }
26
+
27
+ if (status === 'error') {
28
+ return <Text color="red">Error: {error}</Text>;
29
+ }
30
+
31
+ return (
32
+ <Box flexDirection="column">
33
+ <Text color="green">Schemas generated successfully!</Text>
34
+ </Box>
35
+ );
36
+ }
@@ -0,0 +1,102 @@
1
+ import { FeatureRemover } from '../types.js';
2
+ import { createProject, loadFile, removeImport, removeInlineJSX } from '../../utils/ast.js';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+
6
+ export const AIFeature: FeatureRemover = {
7
+ key: 'ai',
8
+ cliUI: {
9
+ title: 'AI chat',
10
+ features: [
11
+ 'AI chat UI',
12
+ 'Tools integration',
13
+ 'AI wallet & usage tracking'
14
+ ]
15
+ },
16
+ dependenciesToRemove: [
17
+ '@kit/ai',
18
+ '@ai-sdk/google',
19
+ '@ai-sdk/openai',
20
+ '@ai-sdk/react',
21
+ '@assistant-ui/react',
22
+ '@assistant-ui/react-ai-sdk',
23
+ '@assistant-ui/react-data-stream',
24
+ '@assistant-ui/react-markdown',
25
+ '@assistant-ui/react-ui',
26
+ 'ai',
27
+ 'ollama-ai-provider-v2'
28
+ ],
29
+ i18nNamespacePrefix: ['p_ai', 'ai-content'],
30
+ useFilters: 'useAIFilters',
31
+ crossEnvFilter: 'initAiFilters',
32
+ filesToDelete: [
33
+ 'components/providers/ai-assistant-provider.tsx',
34
+ 'config/ai.config.ts',
35
+ 'app/api/chat',
36
+ 'app/dashboard/[slug]/ai-chat',
37
+ 'lib/ai-backend-tools'
38
+ ],
39
+ apply: async (projectRoot: string) => {
40
+ const dashboardRoot = projectRoot;
41
+
42
+ // 1. Remove from use-filters.ts (extra cleanup)
43
+ const useFiltersPath = path.join(dashboardRoot, 'hooks/use-filters.ts');
44
+ if (fs.existsSync(useFiltersPath)) {
45
+ const project = createProject();
46
+ const sourceFile = loadFile(project, useFiltersPath);
47
+
48
+ removeImport(sourceFile, '~/config/ai.config');
49
+
50
+ await sourceFile.save();
51
+ }
52
+
53
+ // 2. Update layout-client.tsx
54
+ const layoutClientPath = path.join(dashboardRoot, 'app/dashboard/[slug]/layout-client.tsx');
55
+ if (fs.existsSync(layoutClientPath)) {
56
+ const project = createProject();
57
+ const sourceFile = loadFile(project, layoutClientPath);
58
+
59
+ removeImport(sourceFile, '~/components/providers/ai-assistant-provider');
60
+
61
+ // Remove <AIAssistantProvider> wrapper
62
+ let text = sourceFile.getFullText();
63
+ text = text.replace(/<AIAssistantProvider>/g, '<>');
64
+ text = text.replace(/<\/AIAssistantProvider>/g, '</>');
65
+
66
+ sourceFile.replaceWithText(text);
67
+ await sourceFile.save();
68
+ }
69
+
70
+ // 3. Update globals.css
71
+ const globalsCssPath = path.join(dashboardRoot, 'app/globals.css');
72
+ if (fs.existsSync(globalsCssPath)) {
73
+ let content = await fs.readFile(globalsCssPath, 'utf-8');
74
+ content = content.replace("@import '@kit/ai/style.css';", '');
75
+ await fs.writeFile(globalsCssPath, content);
76
+ }
77
+
78
+ // 4. Update dashboard-action-group.tsx
79
+ const actionGroupPath = path.join(dashboardRoot, 'components/dashboard/dashboard-action-group.tsx');
80
+ if (fs.existsSync(actionGroupPath)) {
81
+ const project = createProject();
82
+ const sourceFile = loadFile(project, actionGroupPath);
83
+
84
+ removeImport(sourceFile, '@kit/ai/ui/floating-ai-chat');
85
+ removeInlineJSX(sourceFile, 'FloatingAIChatTrigger');
86
+
87
+ await sourceFile.save();
88
+ }
89
+ },
90
+ router: {
91
+ importName: 'aiRouter',
92
+ importPath: '@kit/ai/router',
93
+ },
94
+ repo: {
95
+ filesToDelete: [
96
+ 'supabase/schemas/026-user-ai-conversation.sql',
97
+ 'supabase/schemas/027-user-ai-usage.sql',
98
+ 'supabase/schemas/028-user-ai-wallet.sql',
99
+ ],
100
+ },
101
+ };
102
+
@@ -0,0 +1,31 @@
1
+ import { FeatureRemover } from '../types.js';
2
+ import { createProject, loadFile, removeImport, removePropertyAssignment } from '../../utils/ast.js';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+
6
+ export const AnalyticsFeature: FeatureRemover = {
7
+ key: 'analytics',
8
+ cliUI: {
9
+ title: 'Analytics',
10
+ description: 'Support for Google analytics & Umami'
11
+ },
12
+ dependenciesToRemove: ['@kit/analytics'],
13
+ useFilters: 'useAnalyticsFilters',
14
+ filesToDelete: [
15
+ 'config/analytics.config.ts',
16
+ ],
17
+ apply: async (projectRoot: string) => {
18
+ const dashboardRoot = projectRoot;
19
+ const appProviderPath = path.join(dashboardRoot, 'components/providers/app-provider.tsx');
20
+
21
+ if (fs.existsSync(appProviderPath)) {
22
+ const project = createProject();
23
+ const sourceFile = loadFile(project, appProviderPath);
24
+
25
+ removeImport(sourceFile, '~/config/analytics.config');
26
+ removePropertyAssignment(sourceFile, 'analytics');
27
+
28
+ await sourceFile.save();
29
+ }
30
+ }
31
+ };