@notation/cli 0.11.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +187 -42
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -6,17 +6,23 @@ import { program } from "commander";
6
6
  // src/compile.ts
7
7
  import chokidar from "chokidar";
8
8
  import { glob } from "glob";
9
- import { log } from "console";
10
9
  import esbuild from "esbuild";
11
10
  import {
12
11
  functionInfraPlugin,
13
12
  functionRuntimePlugin
14
13
  } from "@notation/esbuild-plugins";
15
14
  import { filePaths } from "@notation/core";
16
- async function compile(entryPoint, watch2 = false) {
17
- log(`${watch2 ? "Watching" : "Compiling"} infrastructure`, entryPoint);
15
+
16
+ // src/logger.ts
17
+ var defaultLogger = console;
18
+
19
+ // src/compile.ts
20
+ async function compile(entryPoint, opts = {}) {
21
+ const watch2 = opts.watch ?? false;
22
+ const logger = opts.logger ?? defaultLogger;
23
+ logger.info(`${watch2 ? "Watching" : "Compiling"} infrastructure`, entryPoint);
18
24
  await compileInfra(entryPoint, watch2);
19
- log(`${watch2 ? "Watching" : "Compiling"} functions`);
25
+ logger.info(`${watch2 ? "Watching" : "Compiling"} functions`);
20
26
  const fnEntryPoints = await glob("runtime/**/*.fn.ts");
21
27
  let disposeFnCompiler = await compileFns(fnEntryPoints, watch2);
22
28
  if (!watch2) return;
@@ -72,59 +78,175 @@ async function compileFns(entryPoints, watch2 = false) {
72
78
  }
73
79
 
74
80
  // src/deploy.ts
75
- import { deployApp } from "@notation/core";
76
- async function deploy(entryPoint) {
77
- await compile(entryPoint);
78
- console.log(`Deploying ${entryPoint}`);
81
+ import {
82
+ createLoggerReconcilerSubscriber,
83
+ createNdjsonEventEmitter,
84
+ deployApp
85
+ } from "@notation/core";
86
+ import { randomUUID } from "crypto";
87
+
88
+ // src/stdio.ts
89
+ function redirectStdoutToStderr() {
90
+ const originalWrite = process.stdout.write.bind(process.stdout);
91
+ const write = (line) => {
92
+ originalWrite(line);
93
+ };
94
+ process.stdout.write = ((chunk, ...args) => process.stderr.write(
95
+ chunk,
96
+ ...args
97
+ ));
98
+ return {
99
+ write,
100
+ restore: () => {
101
+ process.stdout.write = originalWrite;
102
+ }
103
+ };
104
+ }
105
+
106
+ // src/deploy.ts
107
+ async function deploy(entryPoint, opts = {}) {
108
+ const logger = opts.logger ?? defaultLogger;
109
+ const emit = opts.json ? createNdjsonEventEmitter(redirectStdoutToStderr().write) : createLoggerReconcilerSubscriber({ logger });
110
+ await compile(entryPoint, { logger });
111
+ logger.info(`Deploying ${entryPoint}`);
112
+ const executionId = opts.executionId ?? randomUUID();
113
+ logger.info(`Execution ID ${executionId}`);
114
+ await deployApp({ entryPoint, emit, executionId });
115
+ }
116
+
117
+ // src/destroy.ts
118
+ import {
119
+ createLoggerReconcilerSubscriber as createLoggerReconcilerSubscriber2,
120
+ createNdjsonEventEmitter as createNdjsonEventEmitter2,
121
+ destroyApp
122
+ } from "@notation/core";
123
+ import { randomUUID as randomUUID2 } from "crypto";
124
+ async function destroy(entryPoint, opts = {}) {
125
+ const logger = opts.logger ?? defaultLogger;
126
+ const emit = opts.json ? createNdjsonEventEmitter2(redirectStdoutToStderr().write) : createLoggerReconcilerSubscriber2({ logger });
127
+ await compile(entryPoint, { logger });
128
+ logger.info(`Destroying ${entryPoint}
129
+ `);
130
+ const executionId = opts.executionId ?? randomUUID2();
131
+ logger.info(`Execution ID ${executionId}`);
132
+ await destroyApp({ entryPoint, emit, executionId });
133
+ }
134
+
135
+ // src/plan.ts
136
+ import {
137
+ createLoggerReconcilerSubscriber as createLoggerReconcilerSubscriber3,
138
+ planApp
139
+ } from "@notation/core";
140
+ var decisionSymbols = {
141
+ create: "+",
142
+ update: "~",
143
+ replace: "-/+",
144
+ "drift-update": "~",
145
+ "drift-replace": "-/+",
146
+ "drift-recreate": "\xB1",
147
+ "delete-orphan": "-",
148
+ noop: " "
149
+ };
150
+ async function plan(entryPoint, opts = {}) {
151
+ const logger = opts.logger ?? defaultLogger;
152
+ const emit = createLoggerReconcilerSubscriber3({ logger });
153
+ if (opts.json) {
154
+ let result2;
155
+ const { restore } = redirectStdoutToStderr();
156
+ try {
157
+ await compile(entryPoint, { logger });
158
+ result2 = await planApp({
159
+ entryPoint,
160
+ emit
161
+ });
162
+ } finally {
163
+ restore();
164
+ }
165
+ process.stdout.write(`${JSON.stringify(result2, null, 2)}
166
+ `);
167
+ return;
168
+ }
169
+ await compile(entryPoint, { logger });
170
+ logger.info(`Planning ${entryPoint}
171
+ `);
172
+ const result = await planApp({
173
+ entryPoint,
174
+ emit
175
+ });
176
+ printPlanSummary(result, logger);
177
+ }
178
+ function printPlanSummary(result, logger) {
179
+ const changedNodes = result.nodes.filter((node) => node.decision !== "noop");
180
+ for (const node of changedNodes) {
181
+ logger.info(
182
+ `${decisionSymbols[node.decision]} ${node.decision} ${node.type} ${node.id}`
183
+ );
184
+ for (const field of node.replaceFields ?? []) {
185
+ logger.info(
186
+ field.known ? ` ${field.name} forces replacement` : ` ${field.name} unknown, may force replacement`
187
+ );
188
+ }
189
+ }
190
+ const count = (decision) => result.nodes.filter((node) => node.decision === decision).length;
191
+ const summary = [
192
+ `${count("create")} to create`,
193
+ `${count("update") + count("drift-update")} to update`,
194
+ `${count("replace") + count("drift-replace")} to replace`,
195
+ `${count("drift-recreate")} to recreate`,
196
+ `${count("delete-orphan")} to delete`,
197
+ `${count("noop")} unchanged`
198
+ ].join(", ");
199
+ logger.info(`${changedNodes.length > 0 ? "\n" : ""}Plan: ${summary}.`);
200
+ }
201
+
202
+ // src/run-with-error-handling.ts
203
+ async function runWithErrorHandling(fn, opts) {
79
204
  try {
80
- await deployApp(entryPoint);
81
- } catch (err) {
82
- if (err.name === "CredentialsProviderError") {
83
- console.log(
205
+ await fn();
206
+ return 0;
207
+ } catch (error) {
208
+ if (error instanceof Error && error.name === "CredentialsProviderError") {
209
+ opts.logger.error(
84
210
  "\nAWS credentials not found.",
85
211
  "\n\nEnsure you have a default profile set up in ~/.aws/credentials.",
86
- "\n\nIf using another profile run AWS_PROFILE=otherProfile notation deploy.\n"
212
+ `
213
+
214
+ If using another profile run AWS_PROFILE=otherProfile notation ${opts.command}.
215
+ `
87
216
  );
88
- process.exit(1);
217
+ return 1;
89
218
  }
90
- console.log(err);
91
- process.exit(1);
219
+ opts.logger.error(error);
220
+ return 1;
92
221
  }
93
222
  }
94
223
 
95
- // src/destroy.ts
96
- import { destroyApp } from "@notation/core";
97
- async function destroy(entryPoint) {
98
- await compile(entryPoint);
99
- await destroyApp(entryPoint);
100
- }
101
-
102
224
  // src/visualise.ts
103
225
  import {
104
226
  createMermaidFlowChart,
105
227
  createMermaidLiveUrl,
106
228
  getResourceGraph
107
229
  } from "@notation/core";
108
- import { log as log2 } from "console";
109
- async function visualise(entryPoint) {
230
+ async function visualise(entryPoint, logger = defaultLogger) {
110
231
  await compileInfra(entryPoint);
111
- await generateGraph(entryPoint);
232
+ await generateGraph(entryPoint, logger);
112
233
  }
113
- async function generateGraph(entryPoint) {
114
- log2(`Generating graph for ${entryPoint}`);
234
+ async function generateGraph(entryPoint, logger = defaultLogger) {
235
+ logger.info(`Generating graph for ${entryPoint}`);
115
236
  const graph = await getResourceGraph(entryPoint);
116
237
  const chart = createMermaidFlowChart(graph.resourceGroups, graph.resources);
117
238
  const chartUrl = createMermaidLiveUrl(chart);
118
- log2("\nGenerated infrastructure chart:\n");
119
- log2(chartUrl);
239
+ logger.info("\nGenerated infrastructure chart:\n");
240
+ logger.info(chartUrl);
120
241
  }
121
242
 
122
243
  // src/watch.ts
123
244
  import chokidar2 from "chokidar";
124
- import { deployApp as deployApp2 } from "@notation/core";
245
+ import { randomUUID as randomUUID3 } from "crypto";
246
+ import { createLoggerReconcilerSubscriber as createLoggerReconcilerSubscriber4, deployApp as deployApp2 } from "@notation/core";
125
247
  var dotFilesRe = /(^|[\/\\])\../;
126
- async function watch(entryPoint) {
127
- await compile(entryPoint, true);
248
+ async function watch(entryPoint, logger = defaultLogger) {
249
+ await compile(entryPoint, { watch: true, logger });
128
250
  const watcher = chokidar2.watch("dist", {
129
251
  ignored: dotFilesRe,
130
252
  persistent: true
@@ -148,14 +270,21 @@ async function watch(entryPoint) {
148
270
  return;
149
271
  }
150
272
  isDeploying = true;
151
- deployApp2(entryPoint, false).then(() => {
273
+ const executionId = randomUUID3();
274
+ logger.info(`Execution ID ${executionId}`);
275
+ deployApp2({
276
+ entryPoint,
277
+ executionId,
278
+ driftDetection: false,
279
+ emit: createLoggerReconcilerSubscriber4({ logger })
280
+ }).then(() => {
152
281
  isDeploying = false;
153
282
  if (deployQueued) {
154
283
  deployQueued = false;
155
284
  triggerDeploy();
156
285
  }
157
286
  }).catch((err) => {
158
- console.error(err);
287
+ logger.error(err);
159
288
  isDeploying = false;
160
289
  });
161
290
  }
@@ -163,17 +292,30 @@ async function watch(entryPoint) {
163
292
 
164
293
  // src/index.ts
165
294
  import { startDashboardServer } from "@notation/dashboard";
295
+ import { NodeDurableRuntime, resolveDeploymentId } from "@notation/core";
166
296
  program.command("compile").argument("<entryPoint>", "entryPoint").description("Compile Notation App").action(async (entryPoint) => {
167
297
  await compile(entryPoint);
168
298
  });
169
- program.command("dashboard").description("Start Notation Dashboard").action(async () => {
170
- await startDashboardServer();
299
+ program.command("dashboard").argument("<entryPoint>", "entryPoint").description("Start Notation Dashboard").action(async (entryPoint) => {
300
+ const runtime = new NodeDurableRuntime({
301
+ deploymentId: resolveDeploymentId(entryPoint)
302
+ });
303
+ await startDashboardServer({ state: runtime.state });
304
+ });
305
+ program.command("deploy").argument("<entryPoint>", "entryPoint").description("Deploy Notation App").option("--json", "stream reconciler events as NDJSON").option("--execution-id <id>", "resume a durable execution").action(async (entryPoint, options) => {
306
+ await deploy(entryPoint, {
307
+ json: options.json,
308
+ executionId: options.executionId
309
+ });
171
310
  });
172
- program.command("deploy").argument("<entryPoint>", "entryPoint").description("Deploy Notation App").action(async (entryPoint) => {
173
- await deploy(entryPoint);
311
+ program.command("destroy").argument("<entryPoint>", "entryPoint").description("Destroy Notation App").option("--json", "stream reconciler events as NDJSON").option("--execution-id <id>", "resume a durable execution").action(async (entryPoint, options) => {
312
+ await destroy(entryPoint, {
313
+ json: options.json,
314
+ executionId: options.executionId
315
+ });
174
316
  });
175
- program.command("destroy").argument("<entryPoint>", "entryPoint").description("Destroy Notation App").action(async (entryPoint) => {
176
- await destroy(entryPoint);
317
+ program.command("plan").argument("<entryPoint>", "entryPoint").description("Plan Notation App").option("--json", "print the plan as JSON").action(async (entryPoint, options) => {
318
+ await plan(entryPoint, { json: options.json });
177
319
  });
178
320
  program.command("viz").argument("<entryPoint>", "entryPoint").description("Visualise Notation App").action(async (entryPoint) => {
179
321
  await visualise(entryPoint);
@@ -181,4 +323,7 @@ program.command("viz").argument("<entryPoint>", "entryPoint").description("Visua
181
323
  program.command("watch").argument("<entryPoint>", "entryPoint").description("Watch Notation App").action(async (entryPoint) => {
182
324
  await watch(entryPoint);
183
325
  });
184
- program.parse(process.argv);
326
+ process.exitCode = await runWithErrorHandling(
327
+ () => program.parseAsync(process.argv),
328
+ { logger: defaultLogger, command: process.argv[2] ?? program.name() }
329
+ );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@notation/cli",
4
- "version": "0.11.1",
4
+ "version": "0.14.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "templates"
@@ -13,10 +13,10 @@
13
13
  "chokidar": "^4.0.3",
14
14
  "commander": "^13.1.0",
15
15
  "esbuild": "^0.25.0",
16
- "glob": "^11.0.1",
17
- "@notation/core": "0.11.1",
18
- "@notation/dashboard": "0.11.1",
19
- "@notation/esbuild-plugins": "0.11.1"
16
+ "glob": "^13.0.6",
17
+ "@notation/core": "0.14.0",
18
+ "@notation/dashboard": "0.14.0",
19
+ "@notation/esbuild-plugins": "0.14.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/which-pm-runs": "^1.0.2"