@archlast/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.
Files changed (83) hide show
  1. package/README.md +149 -0
  2. package/dist/analyzer.d.ts +96 -0
  3. package/dist/analyzer.d.ts.map +1 -0
  4. package/dist/analyzer.js +404 -0
  5. package/dist/auth.d.ts +14 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +106 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +322337 -0
  11. package/dist/commands/build.d.ts +6 -0
  12. package/dist/commands/build.d.ts.map +1 -0
  13. package/dist/commands/build.js +36 -0
  14. package/dist/commands/config.d.ts +8 -0
  15. package/dist/commands/config.d.ts.map +1 -0
  16. package/dist/commands/config.js +23 -0
  17. package/dist/commands/data.d.ts +6 -0
  18. package/dist/commands/data.d.ts.map +1 -0
  19. package/dist/commands/data.js +300 -0
  20. package/dist/commands/deploy.d.ts +8 -0
  21. package/dist/commands/deploy.d.ts.map +1 -0
  22. package/dist/commands/deploy.js +59 -0
  23. package/dist/commands/dev.d.ts +9 -0
  24. package/dist/commands/dev.d.ts.map +1 -0
  25. package/dist/commands/dev.js +132 -0
  26. package/dist/commands/generate.d.ts +6 -0
  27. package/dist/commands/generate.d.ts.map +1 -0
  28. package/dist/commands/generate.js +100 -0
  29. package/dist/commands/logs.d.ts +10 -0
  30. package/dist/commands/logs.d.ts.map +1 -0
  31. package/dist/commands/logs.js +38 -0
  32. package/dist/commands/pull.d.ts +16 -0
  33. package/dist/commands/pull.d.ts.map +1 -0
  34. package/dist/commands/pull.js +415 -0
  35. package/dist/commands/restart.d.ts +11 -0
  36. package/dist/commands/restart.d.ts.map +1 -0
  37. package/dist/commands/restart.js +63 -0
  38. package/dist/commands/start.d.ts +11 -0
  39. package/dist/commands/start.d.ts.map +1 -0
  40. package/dist/commands/start.js +74 -0
  41. package/dist/commands/status.d.ts +8 -0
  42. package/dist/commands/status.d.ts.map +1 -0
  43. package/dist/commands/status.js +69 -0
  44. package/dist/commands/stop.d.ts +8 -0
  45. package/dist/commands/stop.d.ts.map +1 -0
  46. package/dist/commands/stop.js +23 -0
  47. package/dist/commands/upgrade.d.ts +12 -0
  48. package/dist/commands/upgrade.d.ts.map +1 -0
  49. package/dist/commands/upgrade.js +77 -0
  50. package/dist/docker/compose.d.ts +3 -0
  51. package/dist/docker/compose.d.ts.map +1 -0
  52. package/dist/docker/compose.js +47 -0
  53. package/dist/docker/config.d.ts +11 -0
  54. package/dist/docker/config.d.ts.map +1 -0
  55. package/dist/docker/config.js +183 -0
  56. package/dist/docker/manager.d.ts +18 -0
  57. package/dist/docker/manager.d.ts.map +1 -0
  58. package/dist/docker/manager.js +239 -0
  59. package/dist/docker/types.d.ts +35 -0
  60. package/dist/docker/types.d.ts.map +1 -0
  61. package/dist/docker/types.js +1 -0
  62. package/dist/events-listener.d.ts +19 -0
  63. package/dist/events-listener.d.ts.map +1 -0
  64. package/dist/events-listener.js +105 -0
  65. package/dist/generator.d.ts +44 -0
  66. package/dist/generator.d.ts.map +1 -0
  67. package/dist/generator.js +1816 -0
  68. package/dist/generators/di.d.ts +21 -0
  69. package/dist/generators/di.d.ts.map +1 -0
  70. package/dist/generators/di.js +100 -0
  71. package/dist/index.d.ts +7 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +4 -0
  74. package/dist/protocol.d.ts +58 -0
  75. package/dist/protocol.d.ts.map +1 -0
  76. package/dist/protocol.js +5 -0
  77. package/dist/uploader.d.ts +59 -0
  78. package/dist/uploader.d.ts.map +1 -0
  79. package/dist/uploader.js +255 -0
  80. package/dist/watcher.d.ts +13 -0
  81. package/dist/watcher.d.ts.map +1 -0
  82. package/dist/watcher.js +38 -0
  83. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # Archlast CLI
2
+
3
+ CLI tool for Archlast development and deployment.
4
+
5
+ ## Installation
6
+
7
+ ### npm (recommended for users)
8
+
9
+ ```bash
10
+ npm install -g @archlast/cli
11
+ ```
12
+
13
+ ### Local development (repo)
14
+
15
+ ```bash
16
+ bun install
17
+ bun run build
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Development Mode
23
+
24
+ Watch for file changes and auto-generate types:
25
+
26
+ ```bash
27
+ bun run archlast:dev
28
+ # or
29
+ bun archlast dev
30
+ ```
31
+
32
+ Options:
33
+
34
+ - `--path <path>` - Path to archlast folder (default: `./archlast`)
35
+ - `--port <port>` - Server port (default: `3001`)
36
+ - `--server <url>` - Server URL for code upload (default: `http://localhost:3001`)
37
+
38
+ ### Deploy Mode
39
+
40
+ One-time deployment:
41
+
42
+ ```bash
43
+ bun run archlast:deploy
44
+ # or
45
+ bun archlast deploy
46
+ ```
47
+
48
+ Options:
49
+
50
+ - `--path <path>` - Path to archlast folder (default: `./archlast`)
51
+ - `--server <url>` - Server URL for code upload (default: `http://localhost:3001`)
52
+
53
+ ## Features
54
+
55
+ - 📁 **File Watching**: Monitors `archlast/src/` folder for changes
56
+ - 🔍 **Code Analysis**: Detects functions (query, mutation, action) and schema changes
57
+ - 🎯 **Type Generation**: Auto-generates client-side TypeScript types to `_generated/`
58
+ - 🔄 **Diff Detection**: Shows added, modified, and removed functions
59
+ - ⚡ **Fast Rebuilds**: Only regenerates on actual changes
60
+ - 🌐 **HTTP Upload**: Uploads code to server via `/_archlast/deploy` endpoint
61
+
62
+ ## How It Works
63
+
64
+ 1. Watches all `.ts` files in the `archlast/src/` folder
65
+ 2. Analyzes exports to find `query`, `mutation`, and `action` functions
66
+ 3. Detects schema changes in `src/schema.ts`
67
+ 4. Generates type definitions in `archlast/_generated/api.ts`
68
+ 5. **Uploads code to server via HTTP** (POST to `/_archlast/deploy`)
69
+ 6. Shows a diff of changes in the console
70
+
71
+ ## Project Structure
72
+
73
+ ```
74
+ archlast/
75
+ ├── src/ # Your function definitions
76
+ │ ├── schema.ts # Database schema
77
+ │ ├── tasks.ts # Function exports
78
+ │ └── ...
79
+ ├── _generated/ # Auto-generated by CLI
80
+ │ ├── api.ts # Type-safe API object
81
+ │ ├── index.ts # Barrel export
82
+ │ └── server.ts # Server types (manual)
83
+ ├── package.json
84
+ └── tsconfig.json
85
+ ```
86
+
87
+ Run `bun run dev` from the archlast folder to start development mode.
88
+
89
+ Similar to Convex's `npx convex dev` workflow.
90
+
91
+ ## Server Endpoint
92
+
93
+ The server receives deployments at `POST /_archlast/deploy` with payload:
94
+
95
+ ```typescript
96
+ {
97
+ functions: Array<{
98
+ name: string;
99
+ type: 'query' | 'mutation' | 'action';
100
+ filePath: string;
101
+ code: string;
102
+ }>;
103
+ schema: {
104
+ filePath: string;
105
+ code: string;
106
+ } | null;
107
+ timestamp: number;
108
+ }
109
+ ```
110
+
111
+ Server responds with:
112
+
113
+ ```typescript
114
+ {
115
+ success: boolean;
116
+ message: string;
117
+ functions: number;
118
+ schema: boolean;
119
+ }
120
+ ```
121
+
122
+ Similar to Convex's `npx convex dev` workflow.
123
+
124
+ ## Testing
125
+
126
+ Run the test suite:
127
+
128
+ ```bash
129
+ bun test
130
+ ```
131
+
132
+ Run with coverage:
133
+
134
+ ```bash
135
+ bun test --coverage
136
+ ```
137
+
138
+ The test suite includes:
139
+
140
+ - **Unit tests**: Individual module testing (analyzer, generator, uploader, watcher, DI generator)
141
+ - **Integration tests**: Command orchestration (dev, deploy)
142
+ - **CLI tests**: Entry point and command parsing
143
+ - **Fixtures**: Comprehensive test data for malformed exports, complex handlers, schema variations, and mock manifests
144
+
145
+ Coverage target: 90%+ lines/branches/functions
146
+
147
+ ## Publishing (maintainers)
148
+
149
+ See `docs/npm-publishing.md` for the release workflow and manual publish steps.
@@ -0,0 +1,96 @@
1
+ export interface FunctionDefinition {
2
+ name: string;
3
+ type: "query" | "mutation" | "action";
4
+ filePath: string;
5
+ hash: string;
6
+ }
7
+ export interface HttpRouteDefinition {
8
+ name: string;
9
+ type: "http" | "webhook";
10
+ method: string;
11
+ path: string;
12
+ filePath: string;
13
+ hash: string;
14
+ }
15
+ export interface RpcDefinition {
16
+ name: string;
17
+ procedureType: "query" | "mutation";
18
+ filePath: string;
19
+ moduleName: string;
20
+ hash: string;
21
+ }
22
+ export interface InjectableDefinition {
23
+ className: string;
24
+ provide: string;
25
+ implementation: string;
26
+ filePath: string;
27
+ hash: string;
28
+ }
29
+ export interface SchemaDefinition {
30
+ tables: Record<string, any>;
31
+ hash: string;
32
+ /** Path to the schema file (for merged schemas, indicates "schema/*.ts") */
33
+ filePath?: string;
34
+ /** For merged schemas, list of all schema files that were combined */
35
+ files?: string[];
36
+ }
37
+ export interface AnalysisResult {
38
+ functions: FunctionDefinition[];
39
+ httpRoutes: HttpRouteDefinition[];
40
+ rpcRoutes: RpcDefinition[];
41
+ injectables: InjectableDefinition[];
42
+ schema: SchemaDefinition | string | null;
43
+ timestamp: number;
44
+ }
45
+ export declare class CodeAnalyzer {
46
+ private archlastPath;
47
+ constructor(archlastPath: string);
48
+ analyze(): Promise<AnalysisResult>;
49
+ private analyzeFunctions;
50
+ private analyzeHttpRoutes;
51
+ private analyzeRpcRoutes;
52
+ private analyzeInjectables;
53
+ /**
54
+ * Analyze schema definition
55
+ * Supports both single-file (schema.ts) and folder-based (schema/*.ts) schemas
56
+ * Priority: Single file > Folder with index.ts > Folder (scanned and merged)
57
+ */
58
+ private analyzeSchema;
59
+ /**
60
+ * Parse a single schema file
61
+ */
62
+ private parseSchemaFile;
63
+ /**
64
+ * Scan schema folder for all .ts files
65
+ */
66
+ private scanSchemaFolder;
67
+ /**
68
+ * Merge multiple schema files into a single schema definition
69
+ * Later files override earlier ones for conflicting table names
70
+ */
71
+ private mergeSchemaFiles;
72
+ /**
73
+ * Extract table definitions from schema content
74
+ * Uses regex parsing to find defineTable calls
75
+ */
76
+ private extractTablesFromSchema;
77
+ private hashString;
78
+ }
79
+ export declare class CodeDiffer {
80
+ diff(prev: AnalysisResult, current: AnalysisResult): {
81
+ added: FunctionDefinition[];
82
+ removed: FunctionDefinition[];
83
+ modified: FunctionDefinition[];
84
+ addedRoutes: HttpRouteDefinition[];
85
+ removedRoutes: HttpRouteDefinition[];
86
+ modifiedRoutes: HttpRouteDefinition[];
87
+ addedRpcRoutes: RpcDefinition[];
88
+ removedRpcRoutes: RpcDefinition[];
89
+ modifiedRpcRoutes: RpcDefinition[];
90
+ addedInjectables: InjectableDefinition[];
91
+ removedInjectables: InjectableDefinition[];
92
+ modifiedInjectables: InjectableDefinition[];
93
+ schemaChanged: boolean;
94
+ };
95
+ }
96
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,EAAE,MAAM;IAI1B,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;YAiB1B,gBAAgB;YAuDhB,iBAAiB;YAmDjB,gBAAgB;YAgDhB,kBAAkB;IAkEhC;;;;OAIG;YACW,aAAa;IAgC3B;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;YACW,gBAAgB;IAuB9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAoB/B,OAAO,CAAC,UAAU;CASrB;AAED,qBAAa,UAAU;IACnB,IAAI,CACA,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,cAAc,GACxB;QACC,KAAK,EAAE,kBAAkB,EAAE,CAAC;QAC5B,OAAO,EAAE,kBAAkB,EAAE,CAAC;QAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;QAC/B,WAAW,EAAE,mBAAmB,EAAE,CAAC;QACnC,aAAa,EAAE,mBAAmB,EAAE,CAAC;QACrC,cAAc,EAAE,mBAAmB,EAAE,CAAC;QACtC,cAAc,EAAE,aAAa,EAAE,CAAC;QAChC,gBAAgB,EAAE,aAAa,EAAE,CAAC;QAClC,iBAAiB,EAAE,aAAa,EAAE,CAAC;QACnC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;QACzC,kBAAkB,EAAE,oBAAoB,EAAE,CAAC;QAC3C,mBAAmB,EAAE,oBAAoB,EAAE,CAAC;QAC5C,aAAa,EAAE,OAAO,CAAC;KAC1B;CAsHJ"}
@@ -0,0 +1,404 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { glob } from "glob";
4
+ export class CodeAnalyzer {
5
+ archlastPath;
6
+ constructor(archlastPath) {
7
+ this.archlastPath = path.resolve(archlastPath);
8
+ }
9
+ async analyze() {
10
+ const functions = await this.analyzeFunctions();
11
+ const httpRoutes = await this.analyzeHttpRoutes();
12
+ const rpcRoutes = await this.analyzeRpcRoutes();
13
+ const injectables = await this.analyzeInjectables();
14
+ const schema = await this.analyzeSchema();
15
+ return {
16
+ functions,
17
+ httpRoutes,
18
+ rpcRoutes,
19
+ injectables,
20
+ schema,
21
+ timestamp: Date.now(),
22
+ };
23
+ }
24
+ async analyzeFunctions() {
25
+ const pattern = path.join(this.archlastPath, "src", "**/*.ts").replace(/\\/g, "/");
26
+ const files = await glob(pattern, {
27
+ ignore: ["**/node_modules/**", "**/_generated/**", "**/schema.ts"],
28
+ });
29
+ const functions = [];
30
+ for (const file of files) {
31
+ const content = fs.readFileSync(file, "utf-8");
32
+ const srcPath = path.join(this.archlastPath, "src");
33
+ const relativePath = path.relative(srcPath, file);
34
+ // Simple regex to detect exported functions
35
+ const queryMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*query\s*\(/g);
36
+ const mutationMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*mutation\s*\(/g);
37
+ const actionMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*action\s*\(/g);
38
+ for (const match of queryMatches) {
39
+ functions.push({
40
+ name: match[1],
41
+ type: "query",
42
+ filePath: relativePath,
43
+ hash: this.hashString(content),
44
+ });
45
+ }
46
+ for (const match of mutationMatches) {
47
+ functions.push({
48
+ name: match[1],
49
+ type: "mutation",
50
+ filePath: relativePath,
51
+ hash: this.hashString(content),
52
+ });
53
+ }
54
+ for (const match of actionMatches) {
55
+ functions.push({
56
+ name: match[1],
57
+ type: "action",
58
+ filePath: relativePath,
59
+ hash: this.hashString(content),
60
+ });
61
+ }
62
+ }
63
+ return functions;
64
+ }
65
+ async analyzeHttpRoutes() {
66
+ const pattern = path.join(this.archlastPath, "src", "**/*.ts").replace(/\\/g, "/");
67
+ const files = await glob(pattern, {
68
+ ignore: ["**/node_modules/**", "**/_generated/**", "**/schema.ts"],
69
+ });
70
+ const routes = [];
71
+ for (const file of files) {
72
+ const content = fs.readFileSync(file, "utf-8");
73
+ const srcPath = path.join(this.archlastPath, "src");
74
+ const relativePath = path.relative(srcPath, file);
75
+ // Detect http.get, http.post, etc.
76
+ // Use [\s\S]*? to handle multiline object definitions
77
+ const httpMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*http\.(get|post|put|delete|patch)\s*\(\s*\{[\s\S]*?path:\s*["']([^"']+)["']/g);
78
+ for (const match of httpMatches) {
79
+ routes.push({
80
+ name: match[1],
81
+ type: "http",
82
+ method: match[2].toUpperCase(),
83
+ path: match[3],
84
+ filePath: relativePath,
85
+ hash: this.hashString(content),
86
+ });
87
+ }
88
+ // Detect webhook definitions
89
+ const webhookMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*webhook\s*\(\s*\{[^}]*path:\s*["']([^"']+)["']/g);
90
+ for (const match of webhookMatches) {
91
+ routes.push({
92
+ name: match[1],
93
+ type: "webhook",
94
+ method: "POST",
95
+ path: match[2],
96
+ filePath: relativePath,
97
+ hash: this.hashString(content),
98
+ });
99
+ }
100
+ }
101
+ return routes;
102
+ }
103
+ async analyzeRpcRoutes() {
104
+ const pattern = path.join(this.archlastPath, "src", "**/*.ts").replace(/\\/g, "/");
105
+ const files = await glob(pattern, {
106
+ ignore: ["**/node_modules/**", "**/_generated/**", "**/schema.ts"],
107
+ });
108
+ const routes = [];
109
+ for (const file of files) {
110
+ const content = fs.readFileSync(file, "utf-8");
111
+ const srcPath = path.join(this.archlastPath, "src");
112
+ const relativePath = path.relative(srcPath, file);
113
+ const moduleName = relativePath.replace(/\.ts$/, "");
114
+ // Detect rpc.query definitions
115
+ const queryMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*rpc\.query\s*\(/g);
116
+ for (const match of queryMatches) {
117
+ routes.push({
118
+ name: match[1],
119
+ procedureType: "query",
120
+ filePath: relativePath,
121
+ moduleName,
122
+ hash: this.hashString(content),
123
+ });
124
+ }
125
+ // Detect rpc.mutation definitions
126
+ const mutationMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*rpc\.mutation\s*\(/g);
127
+ for (const match of mutationMatches) {
128
+ routes.push({
129
+ name: match[1],
130
+ procedureType: "mutation",
131
+ filePath: relativePath,
132
+ moduleName,
133
+ hash: this.hashString(content),
134
+ });
135
+ }
136
+ }
137
+ return routes;
138
+ }
139
+ async analyzeInjectables() {
140
+ const pattern = path.join(this.archlastPath, "src", "**/*.ts").replace(/\\/g, "/");
141
+ const files = await glob(pattern, {
142
+ ignore: ["**/node_modules/**", "**/_generated/**", "**/schema.ts"],
143
+ });
144
+ const injectables = [];
145
+ for (const file of files) {
146
+ const content = fs.readFileSync(file, "utf-8");
147
+ const srcPath = path.join(this.archlastPath, "src");
148
+ const relativePath = path.relative(srcPath, file);
149
+ // Detect @Injectable() decorators
150
+ // Pattern: @Injectable({ provide: 'Token', useFactory: 'Implementation' })
151
+ // or @Injectable() for simple cases
152
+ const decoratorMatches = content.matchAll(/@Injectable\s*\(\s*(?:\{([^}]*)\})?\s*\)\s*(?:export\s+)?class\s+(\w+)/g);
153
+ for (const match of decoratorMatches) {
154
+ const optionsStr = match[1] || "";
155
+ const className = match[2];
156
+ // Parse options
157
+ let provide = className;
158
+ let implementation = "default";
159
+ const provideMatch = optionsStr.match(/provide:\s*["']([^"']+)["']/);
160
+ if (provideMatch) {
161
+ provide = provideMatch[1];
162
+ }
163
+ const factoryMatch = optionsStr.match(/useFactory:\s*["']([^"']+)["']/);
164
+ if (factoryMatch) {
165
+ implementation = factoryMatch[1];
166
+ }
167
+ injectables.push({
168
+ className,
169
+ provide,
170
+ implementation,
171
+ filePath: relativePath,
172
+ hash: this.hashString(content),
173
+ });
174
+ }
175
+ // Also detect @Factory decorator
176
+ const factoryMatches = content.matchAll(/@Factory\s*\(\s*["']([^"']+)["']\s*,\s*["']([^"']+)["']\s*\)\s*(?:export\s+)?class\s+(\w+)/g);
177
+ for (const match of factoryMatches) {
178
+ injectables.push({
179
+ className: match[3],
180
+ provide: match[1],
181
+ implementation: match[2],
182
+ filePath: relativePath,
183
+ hash: this.hashString(content),
184
+ });
185
+ }
186
+ }
187
+ return injectables;
188
+ }
189
+ /**
190
+ * Analyze schema definition
191
+ * Supports both single-file (schema.ts) and folder-based (schema/*.ts) schemas
192
+ * Priority: Single file > Folder with index.ts > Folder (scanned and merged)
193
+ */
194
+ async analyzeSchema() {
195
+ const srcPath = path.join(this.archlastPath, "src");
196
+ const singleSchemaPath = path.join(srcPath, "schema.ts");
197
+ const schemaFolderPath = path.join(srcPath, "schema");
198
+ const schemaIndexPath = path.join(schemaFolderPath, "index.ts");
199
+ // Check for single schema file first (highest priority)
200
+ if (fs.existsSync(singleSchemaPath)) {
201
+ const content = fs.readFileSync(singleSchemaPath, "utf-8");
202
+ return this.parseSchemaFile(singleSchemaPath, content);
203
+ }
204
+ // Check for schema folder
205
+ if (!fs.existsSync(schemaFolderPath)) {
206
+ return null;
207
+ }
208
+ // Check for schema/index.ts
209
+ if (fs.existsSync(schemaIndexPath)) {
210
+ const content = fs.readFileSync(schemaIndexPath, "utf-8");
211
+ return this.parseSchemaFile(schemaIndexPath, content);
212
+ }
213
+ // Scan and merge all schema files in folder
214
+ const schemaFiles = await this.scanSchemaFolder(schemaFolderPath);
215
+ if (schemaFiles.length === 0) {
216
+ return null;
217
+ }
218
+ return this.mergeSchemaFiles(schemaFiles);
219
+ }
220
+ /**
221
+ * Parse a single schema file
222
+ */
223
+ parseSchemaFile(filePath, content) {
224
+ return {
225
+ tables: this.extractTablesFromSchema(content),
226
+ filePath: path.relative(this.archlastPath, filePath),
227
+ hash: this.hashString(content),
228
+ };
229
+ }
230
+ /**
231
+ * Scan schema folder for all .ts files
232
+ */
233
+ async scanSchemaFolder(folderPath) {
234
+ const pattern = path.join(folderPath, "*.ts").replace(/\\/g, "/");
235
+ const files = await glob(pattern, {
236
+ ignore: ["**/node_modules/**", "**/_generated/**"],
237
+ });
238
+ const schemaFiles = [];
239
+ for (const file of files) {
240
+ // Skip index.ts as it's handled separately
241
+ if (path.basename(file) === "index.ts") {
242
+ continue;
243
+ }
244
+ const content = fs.readFileSync(file, "utf-8");
245
+ schemaFiles.push({
246
+ filePath: file,
247
+ content,
248
+ });
249
+ }
250
+ return schemaFiles;
251
+ }
252
+ /**
253
+ * Merge multiple schema files into a single schema definition
254
+ * Later files override earlier ones for conflicting table names
255
+ */
256
+ mergeSchemaFiles(schemaFiles) {
257
+ const tables = {};
258
+ const hashes = [];
259
+ for (const { filePath, content } of schemaFiles) {
260
+ const fileTables = this.extractTablesFromSchema(content);
261
+ // Merge tables (later files override)
262
+ Object.assign(tables, fileTables);
263
+ hashes.push(this.hashString(content));
264
+ }
265
+ // Combined hash from all files
266
+ const combinedHash = this.hashString(hashes.join("|"));
267
+ return {
268
+ tables,
269
+ filePath: "schema/*.ts", // Indicator that this is a merged schema
270
+ hash: combinedHash,
271
+ files: schemaFiles.map(f => path.relative(this.archlastPath, f.filePath)),
272
+ };
273
+ }
274
+ /**
275
+ * Extract table definitions from schema content
276
+ * Uses regex parsing to find defineTable calls
277
+ */
278
+ extractTablesFromSchema(content) {
279
+ const tables = {};
280
+ // Pattern to match defineTable calls
281
+ // export const <tableName> = defineTable({ ... }, options?)
282
+ const tableMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)\s*=\s*defineTable\s*\(\s*\{([^}]+)\}/g);
283
+ for (const match of tableMatches) {
284
+ const tableName = match[1];
285
+ tables[tableName] = {
286
+ name: tableName,
287
+ fields: match[2] || "",
288
+ };
289
+ }
290
+ return tables;
291
+ }
292
+ hashString(str) {
293
+ let hash = 0;
294
+ for (let i = 0; i < str.length; i++) {
295
+ const char = str.charCodeAt(i);
296
+ hash = (hash << 5) - hash + char;
297
+ hash = hash & hash;
298
+ }
299
+ return hash.toString(36);
300
+ }
301
+ }
302
+ export class CodeDiffer {
303
+ diff(prev, current) {
304
+ const prevFuncMap = new Map(prev.functions.map((f) => [f.name, f]));
305
+ const currFuncMap = new Map(current.functions.map((f) => [f.name, f]));
306
+ const added = [];
307
+ const removed = [];
308
+ const modified = [];
309
+ // Find added and modified functions
310
+ for (const [name, func] of currFuncMap) {
311
+ const prevFunc = prevFuncMap.get(name);
312
+ if (!prevFunc) {
313
+ added.push(func);
314
+ }
315
+ else if (prevFunc.hash !== func.hash) {
316
+ modified.push(func);
317
+ }
318
+ }
319
+ // Find removed functions
320
+ for (const [name, func] of prevFuncMap) {
321
+ if (!currFuncMap.has(name)) {
322
+ removed.push(func);
323
+ }
324
+ }
325
+ // Diff HTTP routes
326
+ const prevRouteMap = new Map((prev.httpRoutes || []).map((r) => [r.name, r]));
327
+ const currRouteMap = new Map((current.httpRoutes || []).map((r) => [r.name, r]));
328
+ const addedRoutes = [];
329
+ const removedRoutes = [];
330
+ const modifiedRoutes = [];
331
+ for (const [name, route] of currRouteMap) {
332
+ const prevRoute = prevRouteMap.get(name);
333
+ if (!prevRoute) {
334
+ addedRoutes.push(route);
335
+ }
336
+ else if (prevRoute.hash !== route.hash) {
337
+ modifiedRoutes.push(route);
338
+ }
339
+ }
340
+ for (const [name, route] of prevRouteMap) {
341
+ if (!currRouteMap.has(name)) {
342
+ removedRoutes.push(route);
343
+ }
344
+ }
345
+ // Diff RPC routes
346
+ const prevRpcMap = new Map((prev.rpcRoutes || []).map((r) => [`${r.moduleName}.${r.name}`, r]));
347
+ const currRpcMap = new Map((current.rpcRoutes || []).map((r) => [`${r.moduleName}.${r.name}`, r]));
348
+ const addedRpcRoutes = [];
349
+ const removedRpcRoutes = [];
350
+ const modifiedRpcRoutes = [];
351
+ for (const [fullName, route] of currRpcMap) {
352
+ const prevRoute = prevRpcMap.get(fullName);
353
+ if (!prevRoute) {
354
+ addedRpcRoutes.push(route);
355
+ }
356
+ else if (prevRoute.hash !== route.hash) {
357
+ modifiedRpcRoutes.push(route);
358
+ }
359
+ }
360
+ for (const [fullName, route] of prevRpcMap) {
361
+ if (!currRpcMap.has(fullName)) {
362
+ removedRpcRoutes.push(route);
363
+ }
364
+ }
365
+ // Diff injectables
366
+ const prevInjectableMap = new Map((prev.injectables || []).map((i) => [`${i.provide}:${i.implementation}`, i]));
367
+ const currInjectableMap = new Map((current.injectables || []).map((i) => [`${i.provide}:${i.implementation}`, i]));
368
+ const addedInjectables = [];
369
+ const removedInjectables = [];
370
+ const modifiedInjectables = [];
371
+ for (const [key, injectable] of currInjectableMap) {
372
+ const prevInjectable = prevInjectableMap.get(key);
373
+ if (!prevInjectable) {
374
+ addedInjectables.push(injectable);
375
+ }
376
+ else if (prevInjectable.hash !== injectable.hash) {
377
+ modifiedInjectables.push(injectable);
378
+ }
379
+ }
380
+ for (const [key, injectable] of prevInjectableMap) {
381
+ if (!currInjectableMap.has(key)) {
382
+ removedInjectables.push(injectable);
383
+ }
384
+ }
385
+ const prevHash = typeof prev.schema === "string" ? prev.schema : prev.schema?.hash;
386
+ const currHash = typeof current.schema === "string" ? current.schema : current.schema?.hash;
387
+ const schemaChanged = prevHash !== currHash;
388
+ return {
389
+ added,
390
+ removed,
391
+ modified,
392
+ addedRoutes,
393
+ removedRoutes,
394
+ modifiedRoutes,
395
+ addedRpcRoutes,
396
+ removedRpcRoutes,
397
+ modifiedRpcRoutes,
398
+ addedInjectables,
399
+ removedInjectables,
400
+ modifiedInjectables,
401
+ schemaChanged,
402
+ };
403
+ }
404
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare function validateApiKey(apiKey: string): {
2
+ valid: boolean;
3
+ error?: string;
4
+ };
5
+ export declare class AdminTokenNotFoundError extends Error {
6
+ constructor();
7
+ }
8
+ export declare function isBetterAuthApiKey(token: string): boolean;
9
+ export declare function extractFromEnv(content: string, varName: string): string | null;
10
+ export declare function readAdminToken(archlastPath?: string): string;
11
+ export declare function getAuthHeaders(archlastPath?: string): Record<string, string>;
12
+ export declare function hashString(str: string): string;
13
+ export declare function hashFile(filePath: string): string;
14
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAcjF;AAED,qBAAa,uBAAwB,SAAQ,KAAK;;CAUjD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY9E;AAED,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CA2C5D;AAED,wBAAgB,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK5E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ9C;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOjD"}