@saws/cli 2.0.0-beta.0 → 2.0.0-beta.11

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 (74) hide show
  1. package/README.md +189 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/bin/saws.d.ts +0 -1
  4. package/dist/bin/saws.js +32 -40
  5. package/dist/commands/app/deploy.d.ts +4 -0
  6. package/dist/commands/app/deploy.js +20 -0
  7. package/dist/commands/app/index.d.ts +2 -0
  8. package/dist/commands/app/index.js +16 -0
  9. package/dist/commands/app/install.d.ts +5 -0
  10. package/dist/commands/app/install.js +41 -0
  11. package/dist/commands/app/paths.d.ts +1 -0
  12. package/dist/commands/app/paths.js +8 -0
  13. package/dist/commands/app/process.d.ts +2 -0
  14. package/dist/commands/app/process.js +28 -0
  15. package/dist/commands/deploy/command.d.ts +2 -7
  16. package/dist/commands/deploy/command.js +13 -19
  17. package/dist/commands/deploy/index.d.ts +0 -1
  18. package/dist/commands/deploy/index.js +1 -4
  19. package/dist/commands/dev/command.d.ts +1 -7
  20. package/dist/commands/dev/command.js +46 -48
  21. package/dist/commands/dev/index.d.ts +0 -1
  22. package/dist/commands/dev/index.js +1 -6
  23. package/dist/commands/dev/tui/dev-tui.d.ts +18 -3
  24. package/dist/commands/dev/tui/dev-tui.js +263 -29
  25. package/dist/commands/host/command.d.ts +3 -2
  26. package/dist/commands/host/command.js +112 -7
  27. package/dist/commands/host/create.d.ts +9 -0
  28. package/dist/commands/host/create.js +30 -0
  29. package/dist/commands/host/deployment-key.d.ts +2 -0
  30. package/dist/commands/host/deployment-key.js +40 -0
  31. package/dist/commands/host/import-key.d.ts +5 -0
  32. package/dist/commands/host/import-key.js +40 -0
  33. package/dist/commands/host/index.d.ts +0 -1
  34. package/dist/commands/host/index.js +25 -3
  35. package/dist/commands/init/command.d.ts +1 -7
  36. package/dist/commands/init/command.js +24 -15
  37. package/dist/commands/init/index.d.ts +0 -1
  38. package/dist/commands/init/index.js +1 -5
  39. package/dist/commands/init/templates/gitignore.template.d.ts +1 -0
  40. package/dist/commands/init/templates/gitignore.template.js +4 -0
  41. package/dist/commands/init/templates/saws-ts.template.d.ts +3 -0
  42. package/dist/commands/init/templates/saws-ts.template.js +7 -0
  43. package/dist/commands/init/templates/tsconfig-json.template.d.ts +1 -0
  44. package/dist/commands/init/templates/tsconfig-json.template.js +4 -0
  45. package/dist/commands/logs/command.d.ts +5 -0
  46. package/dist/commands/logs/command.js +19 -0
  47. package/dist/commands/logs/index.d.ts +2 -0
  48. package/dist/commands/logs/index.js +7 -0
  49. package/dist/commands/secrets/command.d.ts +2 -2
  50. package/dist/commands/secrets/command.js +21 -9
  51. package/dist/commands/secrets/index.d.ts +0 -1
  52. package/dist/commands/secrets/index.js +7 -5
  53. package/dist/hosts.d.ts +1 -3
  54. package/dist/hosts.js +11 -10
  55. package/package.json +10 -20
  56. package/dist/bin/saws.d.ts.map +0 -1
  57. package/dist/commands/deploy/command.d.ts.map +0 -1
  58. package/dist/commands/deploy/index.d.ts.map +0 -1
  59. package/dist/commands/dev/command.d.ts.map +0 -1
  60. package/dist/commands/dev/index.d.ts.map +0 -1
  61. package/dist/commands/dev/tui/dev-tui.d.ts.map +0 -1
  62. package/dist/commands/host/command.d.ts.map +0 -1
  63. package/dist/commands/host/command.test.d.ts +0 -2
  64. package/dist/commands/host/command.test.d.ts.map +0 -1
  65. package/dist/commands/host/command.test.js +0 -13
  66. package/dist/commands/host/index.d.ts.map +0 -1
  67. package/dist/commands/init/command.d.ts.map +0 -1
  68. package/dist/commands/init/command.test.d.ts +0 -2
  69. package/dist/commands/init/command.test.d.ts.map +0 -1
  70. package/dist/commands/init/command.test.js +0 -40
  71. package/dist/commands/init/index.d.ts.map +0 -1
  72. package/dist/commands/secrets/command.d.ts.map +0 -1
  73. package/dist/commands/secrets/index.d.ts.map +0 -1
  74. package/dist/hosts.d.ts.map +0 -1
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { getAppDirectory } from "./paths.js";
4
+ import { runProcess } from "./process.js";
5
+ export async function installAppCommand(packageSpecifier, options) {
6
+ const packageName = getPackageName(packageSpecifier);
7
+ const appDirectory = getAppDirectory(options.name);
8
+ const inputPath = path.resolve(options.config);
9
+ const installedInputPath = path.join(appDirectory, "input.ts");
10
+ await fs.mkdir(appDirectory, { recursive: true });
11
+ if (inputPath !== installedInputPath) {
12
+ await fs.copyFile(inputPath, installedInputPath);
13
+ }
14
+ await fs.writeFile(path.join(appDirectory, "package.json"), `${JSON.stringify({
15
+ name: "saws-installed-application",
16
+ private: true,
17
+ type: "module",
18
+ }, null, 2)}\n`);
19
+ await fs.writeFile(path.join(appDirectory, "saws.ts"), applicationWrapper(packageName));
20
+ await runProcess("npm", ["install", "--save-exact", "--no-fund", "--no-audit", packageSpecifier], appDirectory);
21
+ console.log(`Installed ${packageSpecifier} as application "${options.name}"`);
22
+ }
23
+ function getPackageName(specifier) {
24
+ const trimmed = specifier.trim();
25
+ const match = /^(?<name>@[^/\s]+\/[^@/\s]+|[^@/\s]+)(?:@[^\s]+)?$/.exec(trimmed);
26
+ const name = match?.groups?.name;
27
+ if (name == null) {
28
+ throw new Error("Application package must be an npm package name with an optional version or tag");
29
+ }
30
+ return name;
31
+ }
32
+ function applicationWrapper(packageName) {
33
+ return `import { createSawsConfig } from "@saws/core";
34
+ import input from "./input.ts";
35
+ import * as application from ${JSON.stringify(packageName)};
36
+
37
+ export * from "./input.ts";
38
+
39
+ export default await createSawsConfig(application, input);
40
+ `;
41
+ }
@@ -0,0 +1 @@
1
+ export declare function getAppDirectory(name: string): string;
@@ -0,0 +1,8 @@
1
+ import path from "node:path";
2
+ import { getSawsHome } from "@saws/core";
3
+ export function getAppDirectory(name) {
4
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
5
+ throw new Error("Application name must start with a letter or number and contain only letters, numbers, dots, underscores, or hyphens");
6
+ }
7
+ return path.join(getSawsHome(), "apps", name);
8
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runProcess(command: string, args: string[], cwd: string): Promise<void>;
2
+ export declare function runSaws(args: string[], cwd: string): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { spawn } from "node:child_process";
2
+ export async function runProcess(command, args, cwd) {
3
+ await new Promise((resolve, reject) => {
4
+ const child = spawn(command, args, {
5
+ cwd,
6
+ stdio: "inherit",
7
+ });
8
+ child.once("error", reject);
9
+ child.once("exit", (code, signal) => {
10
+ if (signal != null) {
11
+ reject(new Error(`${command} exited from signal ${signal}`));
12
+ return;
13
+ }
14
+ if (code !== 0) {
15
+ reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
16
+ return;
17
+ }
18
+ resolve();
19
+ });
20
+ });
21
+ }
22
+ export async function runSaws(args, cwd) {
23
+ const cliPath = process.argv[1];
24
+ if (cliPath == null) {
25
+ throw new Error("Could not determine the SAWS CLI entry point");
26
+ }
27
+ await runProcess(process.execPath, [cliPath, ...args], cwd);
28
+ }
@@ -1,8 +1,3 @@
1
- export interface DeployCommandOptions {
1
+ export declare const deployCommand: (path: string, { stage }: {
2
2
  stage: string;
3
- dryRun?: boolean;
4
- rootDir?: string;
5
- config?: string;
6
- }
7
- export declare function deployCommand(path: string | undefined, options: DeployCommandOptions): Promise<void>;
8
- //# sourceMappingURL=command.d.ts.map
3
+ }) => Promise<void>;
@@ -1,21 +1,15 @@
1
- import { DeployContext, getSawsConfig } from "@saws/core";
2
- import { findConfiguredHosts } from "../../hosts.js";
3
- export async function deployCommand(path, options) {
4
- if (options.stage == null || options.stage.length === 0) {
5
- throw new Error("deploy requires --stage <stage>");
1
+ // import { createCacheDir } from "@saws/utils/create-directories";
2
+ import { getSawsConfig } from "@saws/core";
3
+ export const deployCommand = async (path, { stage }) => {
4
+ if (stage == null || stage.length === 0) {
5
+ throw new Error("deploy requires --stage <string>");
6
6
  }
7
- if (options.stage === "local") {
8
- throw new Error("Can not deploy to local stage");
7
+ if (stage === "local") {
8
+ console.warn("Can not deploy to local stage");
9
+ process.exit();
9
10
  }
10
- const serviceDefinition = await getSawsConfig(options.config ?? path);
11
- const dryRun = options.dryRun ?? false;
12
- for (const host of findConfiguredHosts(serviceDefinition)) {
13
- await host.assertReady({ dryRun });
14
- }
15
- await serviceDefinition.deploy(new DeployContext({
16
- stage: options.stage,
17
- rootDir: options.rootDir ?? process.cwd(),
18
- dryRun,
19
- env: process.env,
20
- }));
21
- }
11
+ process.env.STAGE = stage;
12
+ // await createCacheDir();
13
+ const serviceDefinition = await getSawsConfig(path);
14
+ await serviceDefinition.deploy(stage);
15
+ };
@@ -1,3 +1,2 @@
1
1
  import { Command } from "commander";
2
2
  export declare const createCommand: () => Command;
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,9 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { deployCommand } from "./command.js";
3
3
  export const createCommand = () => new Command("deploy")
4
+ .option("--stage <string>", "stage to deploy")
4
5
  .argument("[string]", "path to service definition")
5
- .requiredOption("--stage <string>", "stage to deploy")
6
- .option("--dry-run", "print remote commands instead of executing them")
7
- .option("--root-dir <string>", "project root used for generated local files")
8
- .option("--config <string>", "path to service definition")
9
6
  .action(deployCommand);
@@ -1,7 +1 @@
1
- export interface DevCommandOptions {
2
- rootDir?: string;
3
- config?: string;
4
- dryRun?: boolean;
5
- }
6
- export declare function devCommand(path: string | undefined, options: DevCommandOptions): Promise<void>;
7
- //# sourceMappingURL=command.d.ts.map
1
+ export declare const devCommand: (path: string) => Promise<void>;
@@ -1,63 +1,61 @@
1
- import { DevContext, ExitContext, getSawsConfig, } from "@saws/core";
1
+ import { createCacheDir } from "@saws/core/utils/create-directories";
2
+ import { onProcessExit } from "@saws/core/utils/on-exit";
3
+ import { getSawsConfig } from "@saws/core";
2
4
  import { DevTui } from "./tui/dev-tui.js";
3
- const LOCAL_STAGE = "local";
4
- export async function devCommand(path, options) {
5
+ export const devCommand = async (path) => {
5
6
  process.env.NODE_ENV = "development";
6
- process.env.STAGE = LOCAL_STAGE;
7
- const rootDir = options.rootDir ?? process.cwd();
8
- const serviceDefinition = await getSawsConfig(options.config ?? path);
9
- const tui = new DevTui(collectServices(serviceDefinition));
7
+ process.env.STAGE = "local";
8
+ process.env.AWS_REGION = "us-west-2";
9
+ await createCacheDir();
10
+ const serviceDefinition = await getSawsConfig(path);
11
+ const services = collectServices(serviceDefinition);
10
12
  const useTui = process.stdout.isTTY && process.stdin.isTTY;
11
- if (useTui)
12
- tui.start();
13
- const shutdown = async () => {
13
+ const tui = new DevTui(services.flatMap((service) => [service.name, ...service.getOnDevLogTabs()]));
14
+ const shutdown = () => {
14
15
  try {
15
- await serviceDefinition.exit(new ExitContext({ stage: LOCAL_STAGE, rootDir }));
16
+ serviceDefinition.exit();
16
17
  }
17
18
  finally {
18
19
  tui.stop();
19
- process.exit();
20
20
  }
21
21
  };
22
- process.once("SIGINT", shutdown);
23
- process.once("SIGTERM", shutdown);
24
- try {
25
- await serviceDefinition.dev(new DevContext({
26
- stage: LOCAL_STAGE,
27
- rootDir,
28
- dryRun: options.dryRun ?? false,
29
- env: process.env,
30
- logSink: useTui ? tui.logSink : undefined,
31
- }));
32
- // Service dev hooks start their processes without blocking so every
33
- // dependency can come up. Keep the command alive until a signal invokes
34
- // the shared shutdown path.
35
- if (!options.dryRun) {
36
- await new Promise(() => { });
37
- }
22
+ onProcessExit(shutdown);
23
+ process.once("SIGTERM", () => {
24
+ shutdown();
25
+ process.exit();
26
+ });
27
+ if (useTui) {
28
+ serviceDefinition.setRuntimeLogSink(tui.logSink);
29
+ tui.start();
38
30
  }
39
- catch (error) {
40
- if (useTui) {
41
- tui.writeSystemLog(error.stack ?? String(error), "stderr");
31
+ await serviceDefinition.dev();
32
+ if (useTui) {
33
+ for (const service of services) {
34
+ service.getStdOut()?.on("data", (chunk) => {
35
+ tui.logSink({
36
+ serviceName: service.name,
37
+ stream: "stdout",
38
+ chunk: chunk.toString("utf8"),
39
+ timestamp: new Date(),
40
+ });
41
+ });
42
+ service.getStdErr()?.on("data", (chunk) => {
43
+ tui.logSink({
44
+ serviceName: service.name,
45
+ stream: "stderr",
46
+ chunk: chunk.toString("utf8"),
47
+ timestamp: new Date(),
48
+ });
49
+ });
42
50
  }
43
- throw error;
44
51
  }
45
- finally {
46
- tui.stop();
52
+ else {
53
+ for (const service of services) {
54
+ service.getStdOut()?.pipe(process.stdout);
55
+ service.getStdErr()?.pipe(process.stderr);
56
+ }
47
57
  }
48
- }
58
+ };
49
59
  function collectServices(root) {
50
- const services = [];
51
- const seen = new Set();
52
- const visit = (service) => {
53
- if (seen.has(service))
54
- return;
55
- seen.add(service);
56
- for (const dependency of service.dependencies) {
57
- visit(dependency);
58
- }
59
- services.push(service.name);
60
- };
61
- visit(root);
62
- return services;
60
+ return [...new Set(root.getAllDependencies())];
63
61
  }
@@ -1,3 +1,2 @@
1
1
  import { Command } from "commander";
2
2
  export declare const createCommand: () => Command;
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,3 @@
1
1
  import { Command } from "commander";
2
2
  import { devCommand } from "./command.js";
3
- export const createCommand = () => new Command("dev")
4
- .argument("[string]", "path to service definition")
5
- .option("--dry-run", "print local Docker commands instead of executing them")
6
- .option("--root-dir <string>", "project root used for generated local files")
7
- .option("--config <string>", "path to service definition")
8
- .action(devCommand);
3
+ export const createCommand = () => new Command("dev").argument("[string]", "path to service definition").action(devCommand);
@@ -1,18 +1,33 @@
1
- import type { RuntimeLogSink } from "@saws/core";
1
+ export interface RuntimeLogEntry {
2
+ serviceName: string;
3
+ stream: "stdout" | "stderr";
4
+ chunk: string;
5
+ timestamp: Date;
6
+ }
7
+ export type RuntimeLogSink = (entry: RuntimeLogEntry) => void;
2
8
  export declare class DevTui {
3
9
  readonly logSink: RuntimeLogSink;
4
10
  private readonly services;
5
11
  private readonly logs;
12
+ private readonly logScrollOffsets;
6
13
  private selectedIndex;
7
14
  private isStarted;
15
+ private isSelectionMode;
8
16
  private previousRawMode;
9
17
  private readonly maxLines;
10
18
  constructor(services: string[]);
11
19
  start(): void;
12
20
  stop(): void;
13
- writeSystemLog(message: string, stream?: "stdout" | "stderr"): void;
14
21
  private handleKeypress;
22
+ private handleInput;
15
23
  private addLog;
16
24
  private render;
25
+ private enterSelectionMode;
26
+ private exitSelectionMode;
27
+ private getHelpText;
28
+ private getSelectedService;
29
+ private getBodyHeight;
30
+ private scrollSelectedLog;
31
+ private selectServiceAt;
32
+ private clampLogScrollOffset;
17
33
  }
18
- //# sourceMappingURL=dev-tui.d.ts.map