@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.
- package/README.md +149 -0
- package/dist/analyzer.d.ts +96 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +404 -0
- package/dist/auth.d.ts +14 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +106 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +322337 -0
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +36 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +23 -0
- package/dist/commands/data.d.ts +6 -0
- package/dist/commands/data.d.ts.map +1 -0
- package/dist/commands/data.js +300 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +59 -0
- package/dist/commands/dev.d.ts +9 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +132 -0
- package/dist/commands/generate.d.ts +6 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +100 -0
- package/dist/commands/logs.d.ts +10 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +38 -0
- package/dist/commands/pull.d.ts +16 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +415 -0
- package/dist/commands/restart.d.ts +11 -0
- package/dist/commands/restart.d.ts.map +1 -0
- package/dist/commands/restart.js +63 -0
- package/dist/commands/start.d.ts +11 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +74 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +69 -0
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +23 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +77 -0
- package/dist/docker/compose.d.ts +3 -0
- package/dist/docker/compose.d.ts.map +1 -0
- package/dist/docker/compose.js +47 -0
- package/dist/docker/config.d.ts +11 -0
- package/dist/docker/config.d.ts.map +1 -0
- package/dist/docker/config.js +183 -0
- package/dist/docker/manager.d.ts +18 -0
- package/dist/docker/manager.d.ts.map +1 -0
- package/dist/docker/manager.js +239 -0
- package/dist/docker/types.d.ts +35 -0
- package/dist/docker/types.d.ts.map +1 -0
- package/dist/docker/types.js +1 -0
- package/dist/events-listener.d.ts +19 -0
- package/dist/events-listener.d.ts.map +1 -0
- package/dist/events-listener.js +105 -0
- package/dist/generator.d.ts +44 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +1816 -0
- package/dist/generators/di.d.ts +21 -0
- package/dist/generators/di.d.ts.map +1 -0
- package/dist/generators/di.js +100 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/protocol.d.ts +58 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +5 -0
- package/dist/uploader.d.ts +59 -0
- package/dist/uploader.d.ts.map +1 -0
- package/dist/uploader.js +255 -0
- package/dist/watcher.d.ts +13 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +38 -0
- 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"}
|
package/dist/analyzer.js
ADDED
|
@@ -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"}
|