@archlast/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -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 +322875 -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 +9 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +59 -0
- package/dist/commands/dev.d.ts +10 -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/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -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 +12 -0
- package/dist/docker/config.d.ts.map +1 -0
- package/dist/docker/config.js +183 -0
- package/dist/docker/manager.d.ts +19 -0
- package/dist/docker/manager.d.ts.map +1 -0
- package/dist/docker/manager.js +239 -0
- package/dist/docker/ports.d.ts +6 -0
- package/dist/docker/ports.d.ts.map +1 -0
- package/dist/docker/restart-on-deploy.d.ts +6 -0
- package/dist/docker/restart-on-deploy.d.ts.map +1 -0
- package/dist/docker/types.d.ts +36 -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/project.d.ts +18 -0
- package/dist/project.d.ts.map +1 -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 +63 -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 +58 -0
- package/scripts/postinstall.cjs +65 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI Generator - Generates provider registration code
|
|
3
|
+
*/
|
|
4
|
+
import type { InjectableDefinition } from "../analyzer.js";
|
|
5
|
+
export interface DIGeneratorOptions {
|
|
6
|
+
outputDir: string;
|
|
7
|
+
injectables: InjectableDefinition[];
|
|
8
|
+
}
|
|
9
|
+
export declare class DIGenerator {
|
|
10
|
+
private outputDir;
|
|
11
|
+
private injectables;
|
|
12
|
+
constructor(options: DIGeneratorOptions);
|
|
13
|
+
generate(): Promise<void>;
|
|
14
|
+
private generateContent;
|
|
15
|
+
private getImportPath;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate DI registration code
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateDI(outputDir: string, injectables: InjectableDefinition[]): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=di.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"di.d.ts","sourceRoot":"","sources":["../../src/generators/di.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACvC;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAyB;gBAEhC,OAAO,EAAE,kBAAkB;IAKjC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/B,OAAO,CAAC,eAAe;IA2EvB,OAAO,CAAC,aAAa;CAQxB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC5B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,oBAAoB,EAAE,GACpC,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI Generator - Generates provider registration code
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
export class DIGenerator {
|
|
7
|
+
outputDir;
|
|
8
|
+
injectables;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.outputDir = options.outputDir;
|
|
11
|
+
this.injectables = options.injectables;
|
|
12
|
+
}
|
|
13
|
+
async generate() {
|
|
14
|
+
// Ensure output directory exists
|
|
15
|
+
if (!fs.existsSync(this.outputDir)) {
|
|
16
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const content = this.generateContent();
|
|
19
|
+
const outputPath = path.join(this.outputDir, "di.ts");
|
|
20
|
+
fs.writeFileSync(outputPath, content, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
generateContent() {
|
|
23
|
+
const lines = [];
|
|
24
|
+
// Header comment
|
|
25
|
+
lines.push("/**");
|
|
26
|
+
lines.push(" * AUTO-GENERATED FILE - DO NOT EDIT");
|
|
27
|
+
lines.push(" * Generated by @archlast/cli");
|
|
28
|
+
lines.push(" * Contains provider registrations for dependency injection");
|
|
29
|
+
lines.push(" */");
|
|
30
|
+
lines.push("");
|
|
31
|
+
// Import Container
|
|
32
|
+
lines.push('import { Container } from "@archlast/server/di/container";');
|
|
33
|
+
lines.push("");
|
|
34
|
+
// Generate imports for each injectable
|
|
35
|
+
const importsByFile = new Map();
|
|
36
|
+
for (const injectable of this.injectables) {
|
|
37
|
+
const importPath = this.getImportPath(injectable.filePath);
|
|
38
|
+
if (!importsByFile.has(importPath)) {
|
|
39
|
+
importsByFile.set(importPath, []);
|
|
40
|
+
}
|
|
41
|
+
importsByFile.get(importPath).push(injectable.className);
|
|
42
|
+
}
|
|
43
|
+
for (const [importPath, classNames] of importsByFile) {
|
|
44
|
+
lines.push(`import { ${classNames.join(", ")} } from "${importPath}";`);
|
|
45
|
+
}
|
|
46
|
+
if (importsByFile.size > 0) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
// Generate provider definitions
|
|
50
|
+
lines.push("/**");
|
|
51
|
+
lines.push(" * All detected injectable providers");
|
|
52
|
+
lines.push(" */");
|
|
53
|
+
lines.push("export const providers = [");
|
|
54
|
+
for (const injectable of this.injectables) {
|
|
55
|
+
lines.push(" {");
|
|
56
|
+
lines.push(` provide: "${injectable.provide}",`);
|
|
57
|
+
lines.push(` useClass: ${injectable.className},`);
|
|
58
|
+
if (injectable.implementation !== "default") {
|
|
59
|
+
lines.push(` implementation: "${injectable.implementation}",`);
|
|
60
|
+
}
|
|
61
|
+
lines.push(` scope: "transient",`);
|
|
62
|
+
lines.push(" },");
|
|
63
|
+
}
|
|
64
|
+
lines.push("];");
|
|
65
|
+
lines.push("");
|
|
66
|
+
// Generate registration function
|
|
67
|
+
lines.push("/**");
|
|
68
|
+
lines.push(" * Register all providers with the container");
|
|
69
|
+
lines.push(" */");
|
|
70
|
+
lines.push("export function registerProviders(container: Container): void {");
|
|
71
|
+
lines.push(" for (const provider of providers) {");
|
|
72
|
+
lines.push(' console.log(`[DI] Registering ${provider.provide} impl=${(provider as any).implementation ?? "default"} scope=${(provider as any).scope ?? "singleton"}`);');
|
|
73
|
+
lines.push(" container.register(provider);");
|
|
74
|
+
lines.push(" }");
|
|
75
|
+
lines.push("}");
|
|
76
|
+
lines.push("");
|
|
77
|
+
// Export provider count for debugging
|
|
78
|
+
lines.push(`export const providerCount = ${this.injectables.length}; `);
|
|
79
|
+
lines.push("");
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
getImportPath(filePath) {
|
|
83
|
+
// Convert file path to relative import path
|
|
84
|
+
// e.g., "services/email/resend.ts" -> "../src/services/email/resend"
|
|
85
|
+
const withoutExt = filePath.replace(/\.(ts|js)$/, "");
|
|
86
|
+
// Normalize path separators to forward slashes for imports
|
|
87
|
+
const normalized = withoutExt.replace(/\\/g, "/");
|
|
88
|
+
return `../src/${normalized}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generate DI registration code
|
|
93
|
+
*/
|
|
94
|
+
export async function generateDI(outputDir, injectables) {
|
|
95
|
+
const generator = new DIGenerator({
|
|
96
|
+
outputDir,
|
|
97
|
+
injectables,
|
|
98
|
+
});
|
|
99
|
+
await generator.generate();
|
|
100
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CodeAnalyzer, CodeDiffer } from "./analyzer";
|
|
2
|
+
export { FileWatcher } from "./watcher";
|
|
3
|
+
export { TypeGenerator } from "./generator";
|
|
4
|
+
export { CodeUploader } from "./uploader";
|
|
5
|
+
export type { AnalysisResult, FunctionDefinition } from "./analyzer";
|
|
6
|
+
export type { UploadPayload } from "./uploader";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ProjectMetadata {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
containerName: string;
|
|
5
|
+
dataVolumeName: string;
|
|
6
|
+
port: number;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getProjectDir(projectPath: string): string;
|
|
11
|
+
export declare function getProjectMetadataPath(projectPath: string): string;
|
|
12
|
+
export declare function readProjectMetadata(projectPath: string): ProjectMetadata | null;
|
|
13
|
+
export declare function writeProjectMetadata(projectPath: string, metadata: ProjectMetadata): void;
|
|
14
|
+
export declare function ensureProjectMetadata(projectPath: string, overrides?: Partial<ProjectMetadata>): ProjectMetadata;
|
|
15
|
+
export declare function updateProjectMetadata(projectPath: string, updates: Partial<ProjectMetadata>): ProjectMetadata;
|
|
16
|
+
export declare function deriveContainerName(projectSlug: string, projectId: string): string;
|
|
17
|
+
export declare function deriveVolumeName(projectSlug: string, projectId: string): string;
|
|
18
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAOD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAS/E;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAIzF;AAED,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GACzC,eAAe,CA4BjB;AAED,wBAAgB,qBAAqB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAClC,eAAe,CAMjB;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAI/E"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta Deployment Protocol
|
|
3
|
+
* Defines the contract between CLI and server for incremental deployments
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Represents a single file change
|
|
7
|
+
*/
|
|
8
|
+
export interface FileChange {
|
|
9
|
+
filePath: string;
|
|
10
|
+
code: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Full deployment payload (existing format)
|
|
14
|
+
*/
|
|
15
|
+
export interface FullUploadPayload {
|
|
16
|
+
files: Array<{
|
|
17
|
+
filePath: string;
|
|
18
|
+
code: string;
|
|
19
|
+
}>;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Delta deployment payload (new format)
|
|
24
|
+
*/
|
|
25
|
+
export interface DeltaUploadPayload {
|
|
26
|
+
timestamp: number;
|
|
27
|
+
changes: {
|
|
28
|
+
added: FileChange[];
|
|
29
|
+
modified: FileChange[];
|
|
30
|
+
removed: string[];
|
|
31
|
+
};
|
|
32
|
+
manifestHash?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Result of computing differences between local and server state
|
|
36
|
+
*/
|
|
37
|
+
export interface DeltaResult {
|
|
38
|
+
added: Array<{
|
|
39
|
+
filePath: string;
|
|
40
|
+
code: string;
|
|
41
|
+
}>;
|
|
42
|
+
modified: Array<{
|
|
43
|
+
filePath: string;
|
|
44
|
+
code: string;
|
|
45
|
+
}>;
|
|
46
|
+
removed: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Deployment response from server
|
|
50
|
+
*/
|
|
51
|
+
export interface DeploymentResponse {
|
|
52
|
+
success: boolean;
|
|
53
|
+
message: string;
|
|
54
|
+
functions: number;
|
|
55
|
+
schema: boolean;
|
|
56
|
+
deploymentType?: "full" | "delta";
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,KAAK,CAAC;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACL,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,QAAQ,EAAE,UAAU,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IAEF,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,QAAQ,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { DeltaResult } from "./protocol.js";
|
|
2
|
+
export interface UploadPayload {
|
|
3
|
+
files: Array<{
|
|
4
|
+
filePath: string;
|
|
5
|
+
code: string;
|
|
6
|
+
}>;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class CodeUploader {
|
|
10
|
+
private serverUrl;
|
|
11
|
+
private archlastPath;
|
|
12
|
+
private projectRoot;
|
|
13
|
+
private includeNodeModules;
|
|
14
|
+
constructor(serverUrl: string, archlastPath: string, options?: {
|
|
15
|
+
includeNodeModules?: boolean;
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Get auth headers for requests using Better-Auth API key
|
|
19
|
+
*/
|
|
20
|
+
private getAuthHeaders;
|
|
21
|
+
/**
|
|
22
|
+
* Poll server until it's reachable
|
|
23
|
+
* @param maxRetries Maximum number of retries
|
|
24
|
+
* @param interval Milliseconds between retries
|
|
25
|
+
* @returns true if server is reachable, false if max retries exceeded
|
|
26
|
+
*/
|
|
27
|
+
pollServer(maxRetries?: number, interval?: number): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if local files differ from server manifest
|
|
30
|
+
* @returns Object with hasChanges flag and optional reason
|
|
31
|
+
* @deprecated Use computeDelta() instead for better change detection
|
|
32
|
+
*/
|
|
33
|
+
checkHashDiff(): Promise<{
|
|
34
|
+
hasChanges: boolean;
|
|
35
|
+
reason?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Compute delta between local files and server manifest
|
|
39
|
+
* @returns DeltaResult with added, modified, and removed files
|
|
40
|
+
*/
|
|
41
|
+
computeDelta(): Promise<DeltaResult>;
|
|
42
|
+
upload(): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
message?: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Upload only the changed files using delta deployment
|
|
48
|
+
* @returns Deployment result with deployment type indicator
|
|
49
|
+
*/
|
|
50
|
+
uploadDelta(): Promise<{
|
|
51
|
+
success: boolean;
|
|
52
|
+
message?: string;
|
|
53
|
+
deploymentType?: "full" | "delta";
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Compute hash of the full manifest for integrity verification
|
|
57
|
+
*/
|
|
58
|
+
private computeManifestHash;
|
|
59
|
+
private preparePayload;
|
|
60
|
+
private collectAllFiles;
|
|
61
|
+
private hashString;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=uploader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../src/uploader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGR,WAAW,EAEd,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,KAAK,CAAC;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAU;gBAExB,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE;IAS/F;;OAEG;IACH,OAAO,CAAC,cAAc;IAMtB;;;;;OAKG;IACG,UAAU,CAAC,UAAU,GAAE,MAAW,EAAE,QAAQ,GAAE,MAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBpF;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAiBxE;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC;IAiEpC,MAAM,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA4B/D;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACrC,CAAC;IA0DF;;OAEG;IACH,OAAO,CAAC,mBAAmB;YAQb,cAAc;YASd,eAAe;IAwE7B,OAAO,CAAC,UAAU;CASrB"}
|
package/dist/uploader.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { readAdminToken } from "./auth.js";
|
|
5
|
+
export class CodeUploader {
|
|
6
|
+
serverUrl;
|
|
7
|
+
archlastPath;
|
|
8
|
+
constructor(serverUrl, archlastPath) {
|
|
9
|
+
this.serverUrl = serverUrl.replace(/\/$/, "");
|
|
10
|
+
this.archlastPath = path.resolve(archlastPath);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get auth headers for requests using Better-Auth API key
|
|
14
|
+
*/
|
|
15
|
+
getAuthHeaders() {
|
|
16
|
+
const apiKey = readAdminToken(this.archlastPath);
|
|
17
|
+
console.log(`[CLI] Using Better-Auth API key for deployment`);
|
|
18
|
+
return { "x-api-key": apiKey };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Poll server until it's reachable
|
|
22
|
+
* @param maxRetries Maximum number of retries
|
|
23
|
+
* @param interval Milliseconds between retries
|
|
24
|
+
* @returns true if server is reachable, false if max retries exceeded
|
|
25
|
+
*/
|
|
26
|
+
async pollServer(maxRetries = 30, interval = 1000) {
|
|
27
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${this.serverUrl}/`, {
|
|
30
|
+
method: "GET",
|
|
31
|
+
signal: AbortSignal.timeout(2000),
|
|
32
|
+
});
|
|
33
|
+
if (response.ok || response.status < 500) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// Server not ready, continue polling
|
|
39
|
+
}
|
|
40
|
+
if (i < maxRetries - 1) {
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if local files differ from server manifest
|
|
48
|
+
* @returns Object with hasChanges flag and optional reason
|
|
49
|
+
* @deprecated Use computeDelta() instead for better change detection
|
|
50
|
+
*/
|
|
51
|
+
async checkHashDiff() {
|
|
52
|
+
const delta = await this.computeDelta();
|
|
53
|
+
const hasChanges = delta.added.length > 0 || delta.modified.length > 0 || delta.removed.length > 0;
|
|
54
|
+
if (!hasChanges) {
|
|
55
|
+
return { hasChanges: false };
|
|
56
|
+
}
|
|
57
|
+
const reasons = [];
|
|
58
|
+
if (delta.added.length > 0)
|
|
59
|
+
reasons.push(`${delta.added.length} added`);
|
|
60
|
+
if (delta.modified.length > 0)
|
|
61
|
+
reasons.push(`${delta.modified.length} modified`);
|
|
62
|
+
if (delta.removed.length > 0)
|
|
63
|
+
reasons.push(`${delta.removed.length} removed`);
|
|
64
|
+
return { hasChanges: true, reason: reasons.join(", ") };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute delta between local files and server manifest
|
|
68
|
+
* @returns DeltaResult with added, modified, and removed files
|
|
69
|
+
*/
|
|
70
|
+
async computeDelta() {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(`${this.serverUrl}/_archlast/deploy/manifest`, {
|
|
73
|
+
method: "GET",
|
|
74
|
+
headers: this.getAuthHeaders(),
|
|
75
|
+
signal: AbortSignal.timeout(5000),
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
// Check for auth error
|
|
79
|
+
if (response.status === 401) {
|
|
80
|
+
const error = await response.text();
|
|
81
|
+
throw new Error(`Authentication failed: ${error || "Invalid admin token"}\n\nPlease create a token in the dashboard (Settings → API Tokens) and update ARCHLAST_ADMIN_TOKEN in your .env file.`);
|
|
82
|
+
}
|
|
83
|
+
// Server not ready or no manifest - treat all files as added
|
|
84
|
+
const localFiles = await this.collectAllFiles();
|
|
85
|
+
return {
|
|
86
|
+
added: localFiles,
|
|
87
|
+
modified: [],
|
|
88
|
+
removed: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const serverManifest = (await response.json());
|
|
92
|
+
const localFiles = await this.collectAllFiles();
|
|
93
|
+
const localManifest = {};
|
|
94
|
+
for (const file of localFiles) {
|
|
95
|
+
localManifest[file.filePath] = this.hashString(file.code.trim());
|
|
96
|
+
}
|
|
97
|
+
const added = [];
|
|
98
|
+
const modified = [];
|
|
99
|
+
const removed = [];
|
|
100
|
+
// Check for added or modified files
|
|
101
|
+
for (const [filePath, hash] of Object.entries(localManifest)) {
|
|
102
|
+
if (!serverManifest[filePath]) {
|
|
103
|
+
const file = localFiles.find((f) => f.filePath === filePath);
|
|
104
|
+
if (file)
|
|
105
|
+
added.push(file);
|
|
106
|
+
}
|
|
107
|
+
else if (serverManifest[filePath] !== hash) {
|
|
108
|
+
const file = localFiles.find((f) => f.filePath === filePath);
|
|
109
|
+
if (file)
|
|
110
|
+
modified.push(file);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Check for removed files
|
|
114
|
+
for (const filePath of Object.keys(serverManifest)) {
|
|
115
|
+
if (!localManifest[filePath]) {
|
|
116
|
+
removed.push(filePath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { added, modified, removed };
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// If we can't check, assume all files need to be uploaded
|
|
123
|
+
const localFiles = await this.collectAllFiles();
|
|
124
|
+
return {
|
|
125
|
+
added: localFiles,
|
|
126
|
+
modified: [],
|
|
127
|
+
removed: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async upload() {
|
|
132
|
+
const payload = await this.preparePayload();
|
|
133
|
+
try {
|
|
134
|
+
const response = await fetch(`${this.serverUrl}/_archlast/deploy`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
...this.getAuthHeaders(),
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(payload),
|
|
141
|
+
});
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const error = await response.text();
|
|
144
|
+
return { success: false, message: error };
|
|
145
|
+
}
|
|
146
|
+
const result = (await response.json());
|
|
147
|
+
return { success: true, message: result.message || "Deployed successfully" };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
message: error instanceof Error ? error.message : "Upload failed",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Upload only the changed files using delta deployment
|
|
158
|
+
* @returns Deployment result with deployment type indicator
|
|
159
|
+
*/
|
|
160
|
+
async uploadDelta() {
|
|
161
|
+
const delta = await this.computeDelta();
|
|
162
|
+
const hasChanges = delta.added.length > 0 || delta.modified.length > 0 || delta.removed.length > 0;
|
|
163
|
+
if (!hasChanges) {
|
|
164
|
+
return { success: true, message: "No changes detected", deploymentType: "delta" };
|
|
165
|
+
}
|
|
166
|
+
const payload = {
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
changes: delta,
|
|
169
|
+
};
|
|
170
|
+
// Calculate manifest hash for integrity verification
|
|
171
|
+
const localFiles = await this.collectAllFiles();
|
|
172
|
+
const manifestHash = this.computeManifestHash(localFiles);
|
|
173
|
+
payload.manifestHash = manifestHash;
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(`${this.serverUrl}/_archlast/deploy/delta`, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
"Content-Type": "application/json",
|
|
179
|
+
...this.getAuthHeaders(),
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(payload),
|
|
182
|
+
});
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
const errorText = await response.text();
|
|
185
|
+
// Check if server doesn't support delta deployment, fall back to full upload
|
|
186
|
+
if (response.status === 404) {
|
|
187
|
+
return this.upload();
|
|
188
|
+
}
|
|
189
|
+
// Check for auth error
|
|
190
|
+
if (response.status === 401) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
message: `Authentication failed: ${errorText || "Invalid admin token"}\n\nPlease create a token in the dashboard (Settings → API Tokens) and update ARCHLAST_ADMIN_TOKEN in your .env file.`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { success: false, message: errorText };
|
|
197
|
+
}
|
|
198
|
+
const result = (await response.json());
|
|
199
|
+
return {
|
|
200
|
+
success: result.success,
|
|
201
|
+
message: result.message || "Delta deployment successful",
|
|
202
|
+
deploymentType: result.deploymentType || "delta",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
// If delta deployment fails, fall back to full upload
|
|
207
|
+
const fallbackResult = await this.upload();
|
|
208
|
+
return { ...fallbackResult, deploymentType: "full" };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Compute hash of the full manifest for integrity verification
|
|
213
|
+
*/
|
|
214
|
+
computeManifestHash(files) {
|
|
215
|
+
const filesHashStr = files
|
|
216
|
+
.sort((a, b) => a.filePath.localeCompare(b.filePath))
|
|
217
|
+
.map((f) => `${f.filePath}:${this.hashString(f.code.trim())}`)
|
|
218
|
+
.join(";");
|
|
219
|
+
return this.hashString(filesHashStr);
|
|
220
|
+
}
|
|
221
|
+
async preparePayload() {
|
|
222
|
+
const files = await this.collectAllFiles();
|
|
223
|
+
return {
|
|
224
|
+
files,
|
|
225
|
+
timestamp: Date.now(),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async collectAllFiles() {
|
|
229
|
+
// Scan entire archlast directory, excluding node_modules
|
|
230
|
+
// Include _generated/server.ts for type definitions
|
|
231
|
+
const pattern = path.join(this.archlastPath, "**/*.ts").replace(/\\/g, "/");
|
|
232
|
+
const allFiles = await glob(pattern, {
|
|
233
|
+
ignore: ["**/node_modules/**", "**/_generated/api.ts", "**/_generated/index.ts"],
|
|
234
|
+
});
|
|
235
|
+
const files = [];
|
|
236
|
+
for (const file of allFiles) {
|
|
237
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
238
|
+
const relativePath = path.relative(this.archlastPath, file).replace(/\\/g, "/");
|
|
239
|
+
files.push({
|
|
240
|
+
filePath: relativePath,
|
|
241
|
+
code: content,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return files;
|
|
245
|
+
}
|
|
246
|
+
hashString(str) {
|
|
247
|
+
let hash = 0;
|
|
248
|
+
for (let i = 0; i < str.length; i++) {
|
|
249
|
+
const char = str.charCodeAt(i);
|
|
250
|
+
hash = (hash << 5) - hash + char;
|
|
251
|
+
hash = hash & hash;
|
|
252
|
+
}
|
|
253
|
+
return Math.abs(hash).toString(36);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
export interface WatchEvent {
|
|
3
|
+
type: "add" | "change" | "unlink" | "unlinkDir";
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class FileWatcher extends EventEmitter {
|
|
7
|
+
private watcher;
|
|
8
|
+
private archlastPath;
|
|
9
|
+
constructor(archlastPath: string);
|
|
10
|
+
start(): void;
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,WAAY,SAAQ,YAAY;IACzC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,EAAE,MAAM;IAOhC,KAAK,IAAI,IAAI;IAwBb,IAAI,IAAI,IAAI;CAMf"}
|
package/dist/watcher.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as chokidar from "chokidar";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
export class FileWatcher extends EventEmitter {
|
|
5
|
+
watcher = null;
|
|
6
|
+
archlastPath;
|
|
7
|
+
constructor(archlastPath) {
|
|
8
|
+
super();
|
|
9
|
+
this.archlastPath = path.resolve(archlastPath);
|
|
10
|
+
}
|
|
11
|
+
start() {
|
|
12
|
+
const pattern = path.join(this.archlastPath, "src", "**/*.ts");
|
|
13
|
+
this.watcher = chokidar.watch(pattern, {
|
|
14
|
+
ignored: ["**/node_modules/**", "**/_generated/**"],
|
|
15
|
+
persistent: true,
|
|
16
|
+
ignoreInitial: true,
|
|
17
|
+
});
|
|
18
|
+
this.watcher
|
|
19
|
+
.on("add", (filePath) => {
|
|
20
|
+
this.emit("change", { type: "add", path: filePath });
|
|
21
|
+
})
|
|
22
|
+
.on("change", (filePath) => {
|
|
23
|
+
this.emit("change", { type: "change", path: filePath });
|
|
24
|
+
})
|
|
25
|
+
.on("unlink", (filePath) => {
|
|
26
|
+
this.emit("change", { type: "unlink", path: filePath });
|
|
27
|
+
})
|
|
28
|
+
.on("unlinkDir", (dirPath) => {
|
|
29
|
+
this.emit("change", { type: "unlinkDir", path: dirPath });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
if (this.watcher) {
|
|
34
|
+
this.watcher.close();
|
|
35
|
+
this.watcher = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@archlast/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Archlast CLI for development and deployment",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"archlast": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"scripts/postinstall.cjs",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "bun build src/cli.ts --outdir dist --target node --format esm -e cpu-features && bun run build:types",
|
|
21
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
22
|
+
"dev": "bun --watch src/cli.ts",
|
|
23
|
+
"test": "bun test tests",
|
|
24
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
25
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
26
|
+
"prepublishOnly": "bun run build",
|
|
27
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
28
|
+
"clean": "rm -rf dist"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"archlast",
|
|
32
|
+
"cli",
|
|
33
|
+
"reactive",
|
|
34
|
+
"backend"
|
|
35
|
+
],
|
|
36
|
+
"author": "",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^5.6.2",
|
|
40
|
+
"chokidar": "^3.5.3",
|
|
41
|
+
"commander": "^11.1.0",
|
|
42
|
+
"diff": "^8.0.2",
|
|
43
|
+
"dockerode": "^4.0.9",
|
|
44
|
+
"glob": "^10.3.10",
|
|
45
|
+
"inquirer": "^13.1.0",
|
|
46
|
+
"ora": "^5.4.1",
|
|
47
|
+
"ts-morph": "^21.0.1",
|
|
48
|
+
"typescript": "^5.3.3",
|
|
49
|
+
"yaml": "^2.3.4"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/dockerode": "^3.3.25",
|
|
53
|
+
"@types/node": "^20.10.5",
|
|
54
|
+
"bun-types": "latest",
|
|
55
|
+
"memfs": "^4.51.1",
|
|
56
|
+
"vitest": "^4.0.16"
|
|
57
|
+
}
|
|
58
|
+
}
|