@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.
Files changed (92) hide show
  1. package/README.md +141 -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 +322875 -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 +9 -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 +10 -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/init.d.ts +7 -0
  30. package/dist/commands/init.d.ts.map +1 -0
  31. package/dist/commands/logs.d.ts +10 -0
  32. package/dist/commands/logs.d.ts.map +1 -0
  33. package/dist/commands/logs.js +38 -0
  34. package/dist/commands/pull.d.ts +16 -0
  35. package/dist/commands/pull.d.ts.map +1 -0
  36. package/dist/commands/pull.js +415 -0
  37. package/dist/commands/restart.d.ts +11 -0
  38. package/dist/commands/restart.d.ts.map +1 -0
  39. package/dist/commands/restart.js +63 -0
  40. package/dist/commands/start.d.ts +11 -0
  41. package/dist/commands/start.d.ts.map +1 -0
  42. package/dist/commands/start.js +74 -0
  43. package/dist/commands/status.d.ts +8 -0
  44. package/dist/commands/status.d.ts.map +1 -0
  45. package/dist/commands/status.js +69 -0
  46. package/dist/commands/stop.d.ts +8 -0
  47. package/dist/commands/stop.d.ts.map +1 -0
  48. package/dist/commands/stop.js +23 -0
  49. package/dist/commands/upgrade.d.ts +12 -0
  50. package/dist/commands/upgrade.d.ts.map +1 -0
  51. package/dist/commands/upgrade.js +77 -0
  52. package/dist/docker/compose.d.ts +3 -0
  53. package/dist/docker/compose.d.ts.map +1 -0
  54. package/dist/docker/compose.js +47 -0
  55. package/dist/docker/config.d.ts +12 -0
  56. package/dist/docker/config.d.ts.map +1 -0
  57. package/dist/docker/config.js +183 -0
  58. package/dist/docker/manager.d.ts +19 -0
  59. package/dist/docker/manager.d.ts.map +1 -0
  60. package/dist/docker/manager.js +239 -0
  61. package/dist/docker/ports.d.ts +6 -0
  62. package/dist/docker/ports.d.ts.map +1 -0
  63. package/dist/docker/restart-on-deploy.d.ts +6 -0
  64. package/dist/docker/restart-on-deploy.d.ts.map +1 -0
  65. package/dist/docker/types.d.ts +36 -0
  66. package/dist/docker/types.d.ts.map +1 -0
  67. package/dist/docker/types.js +1 -0
  68. package/dist/events-listener.d.ts +19 -0
  69. package/dist/events-listener.d.ts.map +1 -0
  70. package/dist/events-listener.js +105 -0
  71. package/dist/generator.d.ts +44 -0
  72. package/dist/generator.d.ts.map +1 -0
  73. package/dist/generator.js +1816 -0
  74. package/dist/generators/di.d.ts +21 -0
  75. package/dist/generators/di.d.ts.map +1 -0
  76. package/dist/generators/di.js +100 -0
  77. package/dist/index.d.ts +7 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +4 -0
  80. package/dist/project.d.ts +18 -0
  81. package/dist/project.d.ts.map +1 -0
  82. package/dist/protocol.d.ts +58 -0
  83. package/dist/protocol.d.ts.map +1 -0
  84. package/dist/protocol.js +5 -0
  85. package/dist/uploader.d.ts +63 -0
  86. package/dist/uploader.d.ts.map +1 -0
  87. package/dist/uploader.js +255 -0
  88. package/dist/watcher.d.ts +13 -0
  89. package/dist/watcher.d.ts.map +1 -0
  90. package/dist/watcher.js +38 -0
  91. package/package.json +58 -0
  92. package/scripts/postinstall.cjs +65 -0
@@ -0,0 +1,100 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import chalk from "chalk";
4
+ import ora from "ora";
5
+ import { Command } from "commander";
6
+ import { TypeGenerator } from "../generator";
7
+ /**
8
+ * Generate CRUD handlers for a collection
9
+ * Creates a re-export wrapper file in src/functions/
10
+ */
11
+ async function generateCrudCommandAction(options) {
12
+ const archlastPath = options.path || ".";
13
+ const collection = options.collection;
14
+ const linked = options.linked !== false; // default to linked
15
+ console.log(chalk.blue.bold("🔧 Archlast Generate CRUD\n"));
16
+ const spinner = ora("Generating CRUD handler...").start();
17
+ try {
18
+ // Validate collection exists by checking schema
19
+ const schemaPath = path.join(archlastPath, "src", "schema.ts");
20
+ const schemaIndexPath = path.join(archlastPath, "src", "schema", "index.ts");
21
+ let schemaContent = "";
22
+ let schemaFilePath = "";
23
+ if (fs.existsSync(schemaPath)) {
24
+ schemaContent = fs.readFileSync(schemaPath, "utf-8");
25
+ schemaFilePath = schemaPath;
26
+ }
27
+ else if (fs.existsSync(schemaIndexPath)) {
28
+ schemaContent = fs.readFileSync(schemaIndexPath, "utf-8");
29
+ schemaFilePath = schemaIndexPath;
30
+ }
31
+ else {
32
+ spinner.fail(chalk.red("Schema file not found"));
33
+ console.error(chalk.red("Expected schema.ts at src/schema.ts or src/schema/index.ts"));
34
+ process.exit(1);
35
+ }
36
+ // Check if collection exists in schema
37
+ const collectionPattern = new RegExp(`(?:export\\s+(?:const|let)\\s+)?${collection}\\s*(?::\\s*\\w+)?\\s*[:=]\\s*defineTable`, "g");
38
+ if (!collectionPattern.test(schemaContent)) {
39
+ spinner.fail(chalk.red(`Collection "${collection}" not found in schema`));
40
+ console.error(chalk.red(`Please define the collection in ${schemaFilePath} first`));
41
+ process.exit(1);
42
+ }
43
+ const functionsDir = path.join(archlastPath, "src", "functions");
44
+ const targetFilePath = path.join(functionsDir, `${collection}.ts`);
45
+ // Ensure functions directory exists
46
+ if (!fs.existsSync(functionsDir)) {
47
+ fs.mkdirSync(functionsDir, { recursive: true });
48
+ }
49
+ // Check if file already exists
50
+ if (fs.existsSync(targetFilePath) && !options.force) {
51
+ spinner.fail(chalk.red(`File already exists: ${targetFilePath}`));
52
+ console.error(chalk.red("Use --force to overwrite"));
53
+ process.exit(1);
54
+ }
55
+ if (linked) {
56
+ // Generate linked (re-export) version
57
+ const pascalName = collection.charAt(0).toUpperCase() + collection.slice(1);
58
+ const content = `// Auto-generated CRUD re-export for "${collection}"
59
+ // This file links to the auto-generated handlers in _generated/crud
60
+ // Re-run "archlast generate crud ${collection}" to regenerate
61
+
62
+ export * from "../_generated/crud/${collection}";
63
+
64
+ // You can also import specific handlers:
65
+ // import { list${pascalName}, get${pascalName}, create${pascalName}, update${pascalName}, delete${pascalName} } from "../_generated/crud/${collection}";
66
+ `;
67
+ fs.writeFileSync(targetFilePath, content, "utf-8");
68
+ spinner.succeed(chalk.green(`✅ Created linked CRUD file: ${targetFilePath}`));
69
+ console.log(chalk.gray("\nLinked mode: This file re-exports auto-generated handlers."));
70
+ console.log(chalk.gray("When schema changes, run 'archlast dev' to regenerate _generated/crud.\n"));
71
+ }
72
+ else {
73
+ // Generate ejected (standalone) version
74
+ const generator = new TypeGenerator(archlastPath);
75
+ const handlerCode = generator.generateCrudHandlerForTable(collection);
76
+ fs.writeFileSync(targetFilePath, handlerCode, "utf-8");
77
+ spinner.succeed(chalk.green(`✅ Created ejected CRUD file: ${targetFilePath}`));
78
+ console.log(chalk.gray("\nEjected mode: This file contains standalone code."));
79
+ console.log(chalk.gray("You can modify it, but schema changes won't auto-update.\n"));
80
+ }
81
+ }
82
+ catch (error) {
83
+ spinner.fail(chalk.red("Generation failed"));
84
+ console.error(error);
85
+ process.exit(1);
86
+ }
87
+ }
88
+ /**
89
+ * Generate command with CRUD subcommand
90
+ */
91
+ export const generateCommand = new Command("generate")
92
+ .description("Generate code from schema")
93
+ .command("crud")
94
+ .description("Generate CRUD handlers for a collection")
95
+ .argument("<collection>", "Name of the collection")
96
+ .option("--path <path>", "Path to archlast folder", ".")
97
+ .option("--force", "Overwrite existing file")
98
+ .option("--linked", "Generate linked (re-export) file (default)", true)
99
+ .option("--ejected", "Generate ejected (standalone) file")
100
+ .action(generateCrudCommandAction);
@@ -0,0 +1,7 @@
1
+ interface InitOptions {
2
+ path?: string;
3
+ name?: string;
4
+ }
5
+ export declare function initCommand(options: InitOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAMA,UAAU,WAAW;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAqHD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDrE"}
@@ -0,0 +1,10 @@
1
+ interface LogsOptions {
2
+ path?: string;
3
+ container?: string;
4
+ tail?: string;
5
+ follow?: boolean;
6
+ config?: string;
7
+ }
8
+ export declare function logsCommand(options: LogsOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAIA,UAAU,WAAW;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCrE"}
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ import { DockerManager } from "../docker/manager";
3
+ import { loadDockerConfig } from "../docker/config";
4
+ export async function logsCommand(options) {
5
+ console.log(chalk.blue.bold("Archlast Docker Logs\n"));
6
+ const config = loadDockerConfig({
7
+ path: options.path,
8
+ containerName: options.container,
9
+ configFile: options.config,
10
+ });
11
+ const manager = new DockerManager();
12
+ const dockerAvailable = await manager.checkAvailable();
13
+ if (!dockerAvailable) {
14
+ console.error(chalk.red("Docker is not available."));
15
+ process.exit(1);
16
+ }
17
+ let stream;
18
+ try {
19
+ stream = await manager.logs(config.containerName, {
20
+ follow: options.follow ?? true,
21
+ tail: options.tail ?? "100",
22
+ });
23
+ }
24
+ catch (error) {
25
+ console.error(chalk.red("Failed to read container logs."));
26
+ if (error instanceof Error) {
27
+ console.error(chalk.red(error.message));
28
+ }
29
+ process.exit(1);
30
+ return;
31
+ }
32
+ manager.demuxStream(stream, process.stdout, process.stderr);
33
+ const shutdown = () => {
34
+ stream.destroy();
35
+ process.exit(0);
36
+ };
37
+ process.on("SIGINT", shutdown);
38
+ }
@@ -0,0 +1,16 @@
1
+ interface PullOptions {
2
+ path?: string;
3
+ server?: string;
4
+ files?: string[];
5
+ force?: boolean;
6
+ diff?: boolean;
7
+ merge?: boolean;
8
+ smart?: boolean;
9
+ tag?: string;
10
+ image?: string;
11
+ docker?: boolean;
12
+ version?: string;
13
+ }
14
+ export declare function pullCommand(options: PullOptions): Promise<void>;
15
+ export {};
16
+ //# sourceMappingURL=pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAcA,UAAU,WAAW;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AA6GD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoOrE"}
@@ -0,0 +1,415 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { createRequire } from "module";
4
+ import chalk from "chalk";
5
+ import inquirer from "inquirer";
6
+ import ora from "ora";
7
+ import { diffLines } from "diff";
8
+ import { glob } from "glob";
9
+ import { getAuthHeaders, hashFile } from "../auth.js";
10
+ import { DockerManager } from "../docker/manager";
11
+ import { loadDockerConfig } from "../docker/config";
12
+ const require = createRequire(import.meta.url);
13
+ /**
14
+ * Compute local file hashes for smart diff
15
+ */
16
+ function computeLocalHashes(archlastPath) {
17
+ const hashes = {};
18
+ // Common source files to check
19
+ const patterns = [
20
+ "src/**/*.ts",
21
+ "schema.ts",
22
+ ].map(p => path.join(archlastPath, p).replace(/\\/g, "/"));
23
+ for (const pattern of patterns) {
24
+ const files = glob.sync(pattern, {
25
+ ignore: ["**/node_modules/**", "**/_generated/**", "**/*.d.ts"],
26
+ windowsPathsNoEscape: true,
27
+ });
28
+ for (const file of files) {
29
+ const relativePath = path.relative(archlastPath, file).replace(/\\/g, "/");
30
+ hashes[relativePath] = hashFile(file);
31
+ }
32
+ }
33
+ return hashes;
34
+ }
35
+ /**
36
+ * Display changes summary
37
+ */
38
+ function displayChangesSummary(metadata) {
39
+ console.log(chalk.bold("\n📊 Changes Summary:\n"));
40
+ const fileChanges = metadata.files.filter(f => f.status !== "unchanged");
41
+ if (fileChanges.length === 0) {
42
+ console.log(chalk.gray(" No file changes detected"));
43
+ }
44
+ else {
45
+ const grouped = {
46
+ added: fileChanges.filter(f => f.status === "added"),
47
+ modified: fileChanges.filter(f => f.status === "modified"),
48
+ removed: fileChanges.filter(f => f.status === "removed"),
49
+ };
50
+ if (grouped.added.length > 0) {
51
+ console.log(chalk.green(` + ${grouped.added.length} added:`));
52
+ for (const f of grouped.added.slice(0, 5)) {
53
+ console.log(chalk.green(` ${f.filePath}`));
54
+ }
55
+ if (grouped.added.length > 5) {
56
+ console.log(chalk.green(` ... and ${grouped.added.length - 5} more`));
57
+ }
58
+ }
59
+ if (grouped.modified.length > 0) {
60
+ console.log(chalk.yellow(` ~ ${grouped.modified.length} modified:`));
61
+ for (const f of grouped.modified.slice(0, 5)) {
62
+ console.log(chalk.yellow(` ${f.filePath}`));
63
+ }
64
+ if (grouped.modified.length > 5) {
65
+ console.log(chalk.yellow(` ... and ${grouped.modified.length - 5} more`));
66
+ }
67
+ }
68
+ if (grouped.removed.length > 0) {
69
+ console.log(chalk.red(` - ${grouped.removed.length} removed:`));
70
+ for (const f of grouped.removed.slice(0, 5)) {
71
+ console.log(chalk.red(` ${f.filePath}`));
72
+ }
73
+ if (grouped.removed.length > 5) {
74
+ console.log(chalk.red(` ... and ${grouped.removed.length - 5} more`));
75
+ }
76
+ }
77
+ }
78
+ console.log();
79
+ }
80
+ export async function pullCommand(options) {
81
+ if (options.docker || options.tag || options.image || options.version) {
82
+ await pullDockerImage(options);
83
+ return;
84
+ }
85
+ const archlastPath = path.resolve(options.path || ".");
86
+ const serverUrl = (options.server || "http://localhost:4000").replace(/\/$/, "");
87
+ const force = options.force || false;
88
+ const showDiff = options.diff || false;
89
+ const attemptMerge = options.merge || false;
90
+ const smartPull = options.smart !== false; // Smart pull is default
91
+ console.log(chalk.blue.bold("📥 Archlast Pull\n"));
92
+ try {
93
+ // Compute local hashes for smart diff detection
94
+ const localHashes = smartPull ? computeLocalHashes(archlastPath) : {};
95
+ // Build query parameters
96
+ const queryParams = new URLSearchParams();
97
+ if (options.files) {
98
+ queryParams.set("files", options.files.join(","));
99
+ }
100
+ if (smartPull && Object.keys(localHashes).length > 0) {
101
+ queryParams.set("hashes", JSON.stringify(localHashes));
102
+ queryParams.set("changes", "true"); // Only fetch changed files
103
+ }
104
+ const url = `${serverUrl}/_archlast/pull?${queryParams.toString()}`;
105
+ console.log(chalk.gray(`Fetching from: ${serverUrl}`));
106
+ if (smartPull) {
107
+ console.log(chalk.gray(`Smart pull enabled with ${Object.keys(localHashes).length} local files`));
108
+ }
109
+ const headers = {
110
+ "Content-Type": "application/json",
111
+ ...getAuthHeaders(archlastPath),
112
+ };
113
+ const response = await fetch(url, {
114
+ method: "GET",
115
+ signal: AbortSignal.timeout(30000),
116
+ headers,
117
+ });
118
+ if (!response.ok) {
119
+ const errorText = await response.text();
120
+ console.error(chalk.red(`Pull failed: ${response.status} ${response.statusText}`));
121
+ console.error(chalk.gray(errorText));
122
+ console.log(chalk.yellow("\nCurl example:"));
123
+ console.log(chalk.gray(`curl -X GET "${url}"`));
124
+ process.exit(1);
125
+ }
126
+ const result = (await response.json());
127
+ // Check if we have metadata (smart pull response)
128
+ if (result.metadata) {
129
+ displayChangesSummary(result.metadata);
130
+ if (!result.metadata.hasChanges) {
131
+ console.log(chalk.green("✅ Everything is up to date!\n"));
132
+ return;
133
+ }
134
+ // Confirm pull if not forced
135
+ if (!force) {
136
+ const answer = await inquirer.prompt([
137
+ {
138
+ type: "confirm",
139
+ name: "proceed",
140
+ message: `Pull ${result.files.length} file(s)?`,
141
+ default: true,
142
+ },
143
+ ]);
144
+ if (!answer.proceed) {
145
+ console.log(chalk.yellow("\nPull cancelled"));
146
+ return;
147
+ }
148
+ }
149
+ }
150
+ if (!result.files || result.files.length === 0) {
151
+ console.log(chalk.green("✅ No files to pull"));
152
+ return;
153
+ }
154
+ console.log(chalk.gray(`\nReceived ${result.files.length} file(s)\n`));
155
+ let applyAll = force;
156
+ let quit = false;
157
+ for (const file of result.files) {
158
+ const localPath = path.join(archlastPath, file.filePath);
159
+ const localDir = path.dirname(localPath);
160
+ if (!fs.existsSync(localDir)) {
161
+ fs.mkdirSync(localDir, { recursive: true });
162
+ }
163
+ const localExists = fs.existsSync(localPath);
164
+ if (showDiff && localExists) {
165
+ const localCode = fs.readFileSync(localPath, "utf-8");
166
+ const diff = diffLines(localCode, file.code);
167
+ console.log(chalk.cyan(`\nDiff for ${file.filePath}:\n`));
168
+ diff.forEach((part) => {
169
+ if (part.added) {
170
+ process.stdout.write(chalk.green(part.value));
171
+ }
172
+ else if (part.removed) {
173
+ process.stdout.write(chalk.red(part.value));
174
+ }
175
+ else {
176
+ process.stdout.write(part.value);
177
+ }
178
+ });
179
+ console.log();
180
+ continue;
181
+ }
182
+ if (attemptMerge && file.filePath.endsWith("schema.ts") && localExists) {
183
+ const mergedCode = await mergeSchema(localPath, file.code);
184
+ if (mergedCode !== null) {
185
+ fs.writeFileSync(localPath, mergedCode, "utf-8");
186
+ console.log(chalk.green(`✅ Merged ${file.filePath}`));
187
+ continue;
188
+ }
189
+ }
190
+ if (localExists && !applyAll) {
191
+ const answer = await inquirer.prompt([
192
+ {
193
+ type: "list",
194
+ name: "action",
195
+ message: `Overwrite ${chalk.yellow(file.filePath)}?`,
196
+ choices: ["yes", "no", "yes to all", "quit", "diff"],
197
+ default: "yes",
198
+ },
199
+ ]);
200
+ if (answer.action === "quit") {
201
+ console.log(chalk.yellow("\nPull cancelled"));
202
+ quit = true;
203
+ break;
204
+ }
205
+ if (answer.action === "no") {
206
+ console.log(chalk.gray(`Skipped ${file.filePath}`));
207
+ continue;
208
+ }
209
+ if (answer.action === "diff") {
210
+ const localCode = fs.readFileSync(localPath, "utf-8");
211
+ const diff = diffLines(localCode, file.code);
212
+ console.log(chalk.cyan(`\nDiff for ${file.filePath}:\n`));
213
+ diff.forEach((part) => {
214
+ if (part.added) {
215
+ process.stdout.write(chalk.green(part.value));
216
+ }
217
+ else if (part.removed) {
218
+ process.stdout.write(chalk.red(part.value));
219
+ }
220
+ else {
221
+ process.stdout.write(part.value);
222
+ }
223
+ });
224
+ console.log();
225
+ // Ask again after showing diff
226
+ const diffAnswer = await inquirer.prompt([
227
+ {
228
+ type: "list",
229
+ name: "action",
230
+ message: `Overwrite ${chalk.yellow(file.filePath)}?`,
231
+ choices: ["yes", "no", "yes to all", "quit"],
232
+ default: "yes",
233
+ },
234
+ ]);
235
+ if (diffAnswer.action === "quit") {
236
+ quit = true;
237
+ break;
238
+ }
239
+ if (diffAnswer.action === "no") {
240
+ console.log(chalk.gray(`Skipped ${file.filePath}`));
241
+ continue;
242
+ }
243
+ if (diffAnswer.action === "yes to all") {
244
+ applyAll = true;
245
+ }
246
+ }
247
+ if (answer.action === "yes to all") {
248
+ applyAll = true;
249
+ }
250
+ fs.writeFileSync(localPath, file.code, "utf-8");
251
+ console.log(chalk.green(`✅ Pulled ${file.filePath}`));
252
+ }
253
+ else {
254
+ fs.writeFileSync(localPath, file.code, "utf-8");
255
+ console.log(chalk.green(`✅ Pulled ${file.filePath}`));
256
+ }
257
+ }
258
+ if (quit) {
259
+ process.exit(0);
260
+ }
261
+ console.log(chalk.green("\n✨ Pull complete!\n"));
262
+ // Show summary of what was pulled
263
+ if (result.metadata) {
264
+ const added = result.metadata.files.filter(f => f.status === "added").length;
265
+ const modified = result.metadata.files.filter(f => f.status === "modified").length;
266
+ if (added > 0 || modified > 0) {
267
+ console.log(chalk.gray(` Added: ${added} file(s)`));
268
+ console.log(chalk.gray(` Modified: ${modified} file(s)`));
269
+ }
270
+ }
271
+ }
272
+ catch (error) {
273
+ console.error(chalk.red("Pull failed"));
274
+ if (error instanceof Error) {
275
+ console.error(error.message);
276
+ }
277
+ else {
278
+ console.error(String(error));
279
+ }
280
+ process.exit(1);
281
+ }
282
+ }
283
+ async function pullDockerImage(options) {
284
+ console.log(chalk.blue.bold("Archlast Docker Pull\n"));
285
+ const config = loadDockerConfig({
286
+ path: options.path,
287
+ image: options.image,
288
+ tag: options.tag ?? options.version,
289
+ });
290
+ const manager = new DockerManager();
291
+ const dockerAvailable = await manager.checkAvailable();
292
+ if (!dockerAvailable) {
293
+ console.error(chalk.red("Docker is not available."));
294
+ process.exit(1);
295
+ }
296
+ const imageTag = `${config.image}:${config.tag}`;
297
+ const spinner = ora(`Pulling ${imageTag}...`).start();
298
+ try {
299
+ await manager.pullImage(imageTag, (event) => {
300
+ if (event?.status) {
301
+ const progress = event.progress ? ` ${event.progress}` : "";
302
+ spinner.text = `${event.status}${progress}`;
303
+ }
304
+ });
305
+ spinner.succeed(`Pulled ${imageTag}.`);
306
+ }
307
+ catch (error) {
308
+ spinner.fail("Failed to pull image.");
309
+ if (error instanceof Error) {
310
+ console.error(chalk.red(error.message));
311
+ }
312
+ process.exit(1);
313
+ }
314
+ }
315
+ async function mergeSchema(localPath, remoteCode) {
316
+ try {
317
+ const { Project } = await import("ts-morph");
318
+ const project = new Project();
319
+ const localSourceFile = project.addSourceFileAtPath(localPath);
320
+ const remoteSourceFile = project.createSourceFile("remote.ts", remoteCode);
321
+ const localTables = extractTableNames(localSourceFile);
322
+ const remoteTables = extractTableNames(remoteSourceFile);
323
+ const newTables = remoteTables.filter((t) => !localTables.includes(t));
324
+ if (newTables.length === 0) {
325
+ console.log(chalk.gray("No new tables to merge"));
326
+ return null;
327
+ }
328
+ console.log(chalk.gray(`Found ${newTables.length} new table(s): ${newTables.join(", ")}`));
329
+ const localLines = remoteCode.split("\n");
330
+ const mergedLines = [...localLines];
331
+ const schemaLineIndex = localLines.findIndex((line) => line.includes("defineSchema("));
332
+ if (schemaLineIndex === -1) {
333
+ console.log(chalk.yellow("Could not find defineSchema call"));
334
+ return null;
335
+ }
336
+ const closingBraceIndex = findClosingBraceIndex(localLines, schemaLineIndex);
337
+ for (const table of newTables) {
338
+ const tableDef = ` ${table}: defineTable({
339
+ id: v.id(),
340
+ }),`;
341
+ mergedLines.splice(closingBraceIndex, 0, tableDef);
342
+ }
343
+ return mergedLines.join("\n");
344
+ }
345
+ catch (error) {
346
+ console.error(chalk.red("Merge failed"));
347
+ if (error instanceof Error) {
348
+ console.error(error.message);
349
+ }
350
+ return null;
351
+ }
352
+ }
353
+ function extractTableNames(sourceFile) {
354
+ const tables = [];
355
+ try {
356
+ const { SyntaxKind } = require("ts-morph");
357
+ const variableStmts = sourceFile.getVariableStatements();
358
+ for (const stmt of variableStmts) {
359
+ const name = stmt.getName();
360
+ if (!name)
361
+ continue;
362
+ const initializer = stmt.getInitializer();
363
+ if (!initializer)
364
+ continue;
365
+ if (initializer.getKind() === SyntaxKind.CallExpression) {
366
+ const callExpr = initializer;
367
+ const expr = callExpr.getExpression();
368
+ if (expr && expr.getKind() === SyntaxKind.ObjectLiteralExpression) {
369
+ const objLit = expr;
370
+ for (const prop of objLit.getProperties()) {
371
+ if (prop.getKind() === SyntaxKind.PropertyAssignment) {
372
+ const propAssign = prop;
373
+ const propName = propAssign.getName();
374
+ if (propName === "tables") {
375
+ const propValue = propAssign.getInitializer();
376
+ if (propValue &&
377
+ propValue.getKind() === SyntaxKind.ObjectLiteralExpression) {
378
+ const tablesObj = propValue;
379
+ for (const tableProp of tablesObj.getProperties()) {
380
+ if (tableProp.getKind() === SyntaxKind.PropertyAssignment) {
381
+ const tablePropAssign = tableProp;
382
+ const tableName = tablePropAssign.getName();
383
+ if (tableName) {
384
+ tables.push(tableName);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+ catch (error) {
397
+ console.error(chalk.yellow(`Warning: Could not parse schema: ${error instanceof Error ? error.message : String(error)}`));
398
+ }
399
+ return tables;
400
+ }
401
+ function findClosingBraceIndex(lines, startIndex) {
402
+ let braceCount = 0;
403
+ for (let i = startIndex; i < lines.length; i++) {
404
+ const line = lines[i];
405
+ for (const char of line) {
406
+ if (char === "{")
407
+ braceCount++;
408
+ if (char === "}")
409
+ braceCount--;
410
+ if (braceCount === 0)
411
+ return i;
412
+ }
413
+ }
414
+ return lines.length - 1;
415
+ }
@@ -0,0 +1,11 @@
1
+ interface RestartOptions {
2
+ port?: string;
3
+ path?: string;
4
+ image?: string;
5
+ tag?: string;
6
+ container?: string;
7
+ config?: string;
8
+ }
9
+ export declare function restartCommand(options: RestartOptions): Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=restart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart.d.ts","sourceRoot":"","sources":["../../src/commands/restart.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAwBD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD3E"}
@@ -0,0 +1,63 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { DockerManager } from "../docker/manager";
4
+ import { loadDockerConfig } from "../docker/config";
5
+ const HEALTH_TIMEOUT_MS = 60000;
6
+ const HEALTH_INTERVAL_MS = 2000;
7
+ async function waitForHealth(port, timeoutMs) {
8
+ const deadline = Date.now() + timeoutMs;
9
+ const url = `http://localhost:${port}/health`;
10
+ while (Date.now() < deadline) {
11
+ try {
12
+ const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
13
+ if (response.ok) {
14
+ return true;
15
+ }
16
+ }
17
+ catch {
18
+ // retry
19
+ }
20
+ await new Promise((resolve) => setTimeout(resolve, HEALTH_INTERVAL_MS));
21
+ }
22
+ return false;
23
+ }
24
+ export async function restartCommand(options) {
25
+ console.log(chalk.blue.bold("Archlast Docker Restart\n"));
26
+ const config = loadDockerConfig({
27
+ path: options.path,
28
+ port: options.port,
29
+ image: options.image,
30
+ tag: options.tag,
31
+ containerName: options.container,
32
+ configFile: options.config,
33
+ });
34
+ const manager = new DockerManager();
35
+ const dockerAvailable = await manager.checkAvailable();
36
+ if (!dockerAvailable) {
37
+ console.error(chalk.red("Docker is not available."));
38
+ process.exit(1);
39
+ }
40
+ const spinner = ora(`Restarting ${config.containerName}...`).start();
41
+ try {
42
+ await manager.restart(config);
43
+ const healthy = await waitForHealth(config.port, HEALTH_TIMEOUT_MS);
44
+ if (!healthy) {
45
+ spinner.warn("Container restarted, but health check did not pass within 60 seconds.");
46
+ }
47
+ else {
48
+ spinner.succeed("Archlast container restarted.");
49
+ }
50
+ }
51
+ catch (error) {
52
+ spinner.fail("Failed to restart container.");
53
+ if (error instanceof Error) {
54
+ console.error(chalk.red(error.message));
55
+ }
56
+ else {
57
+ console.error(chalk.red(String(error)));
58
+ }
59
+ process.exit(1);
60
+ }
61
+ console.log(chalk.green(`API: http://localhost:${config.port}`));
62
+ console.log(chalk.green(`Dashboard: http://localhost:${config.port}/_admin`));
63
+ }