alchemy 2.0.0-beta.20 → 2.0.0-beta.21

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 (104) hide show
  1. package/bin/alchemy.js +1 -1
  2. package/bin/alchemy.js.map +1 -1
  3. package/bin/commands/bootstrap.ts +59 -57
  4. package/bin/commands/deploy.ts +8 -2
  5. package/bin/commands/login.ts +56 -49
  6. package/bin/commands/logs.ts +92 -90
  7. package/bin/commands/profile.ts +74 -70
  8. package/bin/commands/state.ts +160 -135
  9. package/bin/commands/tail.ts +94 -92
  10. package/lib/AWS/CloudFront/CachePolicy.d.ts.map +1 -1
  11. package/lib/AWS/CloudFront/CachePolicy.js.map +1 -1
  12. package/lib/AWS/CloudFront/KeyGroup.d.ts.map +1 -1
  13. package/lib/AWS/CloudFront/KeyGroup.js.map +1 -1
  14. package/lib/AWS/CloudFront/OriginRequestPolicy.d.ts.map +1 -1
  15. package/lib/AWS/CloudFront/OriginRequestPolicy.js +3 -1
  16. package/lib/AWS/CloudFront/OriginRequestPolicy.js.map +1 -1
  17. package/lib/AWS/CloudFront/PublicKey.d.ts.map +1 -1
  18. package/lib/AWS/CloudFront/PublicKey.js.map +1 -1
  19. package/lib/AWS/ECS/CapacityProvider.d.ts.map +1 -1
  20. package/lib/AWS/ECS/CapacityProvider.js +5 -4
  21. package/lib/AWS/ECS/CapacityProvider.js.map +1 -1
  22. package/lib/Axiom/Annotation.d.ts +3 -3
  23. package/lib/Axiom/Annotation.d.ts.map +1 -1
  24. package/lib/Axiom/Annotation.js +5 -5
  25. package/lib/Axiom/Annotation.js.map +1 -1
  26. package/lib/Axiom/ApiToken.d.ts +3 -3
  27. package/lib/Axiom/ApiToken.d.ts.map +1 -1
  28. package/lib/Axiom/ApiToken.js +4 -4
  29. package/lib/Axiom/ApiToken.js.map +1 -1
  30. package/lib/Axiom/AuthProvider.js.map +1 -1
  31. package/lib/Axiom/Credentials.d.ts +1 -1
  32. package/lib/Axiom/Credentials.d.ts.map +1 -1
  33. package/lib/Axiom/Credentials.js +2 -3
  34. package/lib/Axiom/Credentials.js.map +1 -1
  35. package/lib/Axiom/Dashboard.d.ts +4 -4
  36. package/lib/Axiom/Dashboard.d.ts.map +1 -1
  37. package/lib/Axiom/Dashboard.js +5 -5
  38. package/lib/Axiom/Dashboard.js.map +1 -1
  39. package/lib/Axiom/Dataset.d.ts.map +1 -1
  40. package/lib/Axiom/Dataset.js +5 -5
  41. package/lib/Axiom/Dataset.js.map +1 -1
  42. package/lib/Axiom/Monitor.d.ts +3 -3
  43. package/lib/Axiom/Monitor.d.ts.map +1 -1
  44. package/lib/Axiom/Monitor.js +6 -6
  45. package/lib/Axiom/Monitor.js.map +1 -1
  46. package/lib/Axiom/Notifier.d.ts +3 -3
  47. package/lib/Axiom/Notifier.d.ts.map +1 -1
  48. package/lib/Axiom/Notifier.js +9 -6
  49. package/lib/Axiom/Notifier.js.map +1 -1
  50. package/lib/Axiom/View.d.ts +3 -3
  51. package/lib/Axiom/View.d.ts.map +1 -1
  52. package/lib/Axiom/View.js +5 -5
  53. package/lib/Axiom/View.js.map +1 -1
  54. package/lib/Axiom/VirtualField.d.ts +3 -3
  55. package/lib/Axiom/VirtualField.d.ts.map +1 -1
  56. package/lib/Axiom/VirtualField.js +5 -5
  57. package/lib/Axiom/VirtualField.js.map +1 -1
  58. package/lib/Bundle/PurePlugin.d.ts.map +1 -1
  59. package/lib/Bundle/PurePlugin.js +4 -4
  60. package/lib/Bundle/PurePlugin.js.map +1 -1
  61. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
  62. package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
  63. package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -1
  64. package/lib/Cloudflare/ApiToken/Common.js.map +1 -1
  65. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
  66. package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -1
  67. package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
  68. package/lib/Cloudflare/Fetcher.js +3 -1
  69. package/lib/Cloudflare/Fetcher.js.map +1 -1
  70. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  71. package/lib/Cloudflare/Workers/Worker.js +12 -3
  72. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  73. package/lib/Output.d.ts.map +1 -1
  74. package/lib/Output.js +1 -1
  75. package/lib/Output.js.map +1 -1
  76. package/lib/Telemetry/Layer.d.ts.map +1 -1
  77. package/lib/Telemetry/Layer.js.map +1 -1
  78. package/lib/Telemetry/Metrics.d.ts.map +1 -1
  79. package/lib/Telemetry/Metrics.js.map +1 -1
  80. package/package.json +5 -5
  81. package/src/AWS/CloudFront/CachePolicy.ts +5 -2
  82. package/src/AWS/CloudFront/KeyGroup.ts +2 -1
  83. package/src/AWS/CloudFront/OriginRequestPolicy.ts +8 -2
  84. package/src/AWS/CloudFront/PublicKey.ts +2 -1
  85. package/src/AWS/ECS/CapacityProvider.ts +12 -9
  86. package/src/Axiom/Annotation.ts +7 -7
  87. package/src/Axiom/ApiToken.ts +6 -6
  88. package/src/Axiom/AuthProvider.ts +2 -7
  89. package/src/Axiom/Credentials.ts +2 -6
  90. package/src/Axiom/Dashboard.ts +10 -13
  91. package/src/Axiom/Dataset.ts +8 -12
  92. package/src/Axiom/Monitor.ts +8 -8
  93. package/src/Axiom/Notifier.ts +11 -8
  94. package/src/Axiom/View.ts +7 -7
  95. package/src/Axiom/VirtualField.ts +7 -7
  96. package/src/Bundle/PurePlugin.ts +6 -5
  97. package/src/Cloudflare/ApiToken/AccountApiToken.ts +1 -3
  98. package/src/Cloudflare/ApiToken/Common.ts +1 -3
  99. package/src/Cloudflare/ApiToken/UserApiToken.ts +1 -3
  100. package/src/Cloudflare/Fetcher.ts +3 -1
  101. package/src/Cloudflare/Workers/Worker.ts +40 -27
  102. package/src/Output.ts +1 -2
  103. package/src/Telemetry/Layer.ts +9 -10
  104. package/src/Telemetry/Metrics.ts +1 -6
@@ -57,68 +57,70 @@ export const bootstrapCommand = Command.make(
57
57
  "alchemy.region": a.region ?? "",
58
58
  "alchemy.destroy": a.destroy,
59
59
  }),
60
- )(Effect.fnUntraced(function* ({ envFile, profile, region, destroy }) {
61
- const logger = Logger.layer([fileLogger("bootstrap.txt")]);
60
+ )(
61
+ Effect.fnUntraced(function* ({ envFile, profile, region, destroy }) {
62
+ const logger = Logger.layer([fileLogger("bootstrap.txt")]);
62
63
 
63
- return yield* Effect.gen(function* () {
64
- const ssoProfile = yield* Auth.loadProfile(profile);
65
- if (!ssoProfile.sso_account_id) {
66
- return yield* Effect.die(
67
- `AWS SSO profile '${profile}' is missing sso_account_id`,
68
- );
69
- }
70
-
71
- // Build a single AWSEnvironment, then derive Region/Credentials from
72
- // it so resource providers downstream see a consistent view. The
73
- // credentials Effect captures FileSystem/Path/HttpClient via the
74
- // ambient context; AWSEnvironment expects R=never, so we provide it
75
- // here.
76
- const ambient = yield* Effect.context<FileSystem | Path | HttpClient>();
77
- const environment = AWSEnvironment.makeEnvironment({
78
- accountId: ssoProfile.sso_account_id,
79
- region: region ?? ssoProfile.region ?? "us-east-1",
80
- credentials: Auth.loadProfileCredentials(profile).pipe(
81
- Effect.provide(ambient),
82
- ),
83
- profile,
84
- });
64
+ return yield* Effect.gen(function* () {
65
+ const ssoProfile = yield* Auth.loadProfile(profile);
66
+ if (!ssoProfile.sso_account_id) {
67
+ return yield* Effect.die(
68
+ `AWS SSO profile '${profile}' is missing sso_account_id`,
69
+ );
70
+ }
85
71
 
86
- const awsLayers = Layer.provideMerge(
87
- Layer.mergeAll(
88
- AWSRegion.fromEnvironment,
89
- AWSCredentials.fromEnvironment,
90
- ),
91
- environment,
92
- );
72
+ // Build a single AWSEnvironment, then derive Region/Credentials from
73
+ // it so resource providers downstream see a consistent view. The
74
+ // credentials Effect captures FileSystem/Path/HttpClient via the
75
+ // ambient context; AWSEnvironment expects R=never, so we provide it
76
+ // here.
77
+ const ambient = yield* Effect.context<FileSystem | Path | HttpClient>();
78
+ const environment = AWSEnvironment.makeEnvironment({
79
+ accountId: ssoProfile.sso_account_id,
80
+ region: region ?? ssoProfile.region ?? "us-east-1",
81
+ credentials: Auth.loadProfileCredentials(profile).pipe(
82
+ Effect.provide(ambient),
83
+ ),
84
+ profile,
85
+ });
93
86
 
94
- return yield* Effect.gen(function* () {
95
- const provider = yield* loadConfigProvider(envFile);
96
- const bootstrapLayer = Layer.provide(
97
- awsLayers,
98
- Layer.succeed(ConfigProvider.ConfigProvider, provider),
87
+ const awsLayers = Layer.provideMerge(
88
+ Layer.mergeAll(
89
+ AWSRegion.fromEnvironment,
90
+ AWSCredentials.fromEnvironment,
91
+ ),
92
+ environment,
99
93
  );
100
- if (destroy) {
101
- yield* destroyBootstrapAws().pipe(
102
- Effect.tap((result) =>
103
- result.destroyed === 0
104
- ? Console.log("✓ No bootstrap buckets found to destroy")
105
- : Console.log(
106
- `✓ Destroyed ${result.destroyed} bootstrap bucket(s): ${result.bucketNames.join(", ")}`,
107
- ),
94
+
95
+ return yield* Effect.gen(function* () {
96
+ const provider = yield* loadConfigProvider(envFile);
97
+ const bootstrapLayer = Layer.provide(
98
+ awsLayers,
99
+ Layer.succeed(ConfigProvider.ConfigProvider, provider),
100
+ );
101
+ if (destroy) {
102
+ yield* destroyBootstrapAws().pipe(
103
+ Effect.tap((result) =>
104
+ result.destroyed === 0
105
+ ? Console.log("✓ No bootstrap buckets found to destroy")
106
+ : Console.log(
107
+ `✓ Destroyed ${result.destroyed} bootstrap bucket(s): ${result.bucketNames.join(", ")}`,
108
+ ),
109
+ ),
110
+ Effect.provide(bootstrapLayer),
111
+ );
112
+ return;
113
+ }
114
+ yield* bootstrapAws().pipe(
115
+ Effect.tap(({ bucketName, created }) =>
116
+ created
117
+ ? Console.log(`✓ Created assets bucket: ${bucketName}`)
118
+ : Console.log(`✓ Assets bucket already exists: ${bucketName}`),
108
119
  ),
109
120
  Effect.provide(bootstrapLayer),
110
121
  );
111
- return;
112
- }
113
- yield* bootstrapAws().pipe(
114
- Effect.tap(({ bucketName, created }) =>
115
- created
116
- ? Console.log(`✓ Created assets bucket: ${bucketName}`)
117
- : Console.log(`✓ Assets bucket already exists: ${bucketName}`),
118
- ),
119
- Effect.provide(bootstrapLayer),
120
- );
121
- });
122
- }).pipe(Effect.provide(logger));
123
- })),
122
+ });
123
+ }).pipe(Effect.provide(logger));
124
+ }),
125
+ ),
124
126
  );
@@ -149,7 +149,10 @@ export const destroyCommand = Command.make(
149
149
  yes,
150
150
  profile,
151
151
  },
152
- instrumentCommand("destroy", stackSpanAttrs)((args) =>
152
+ instrumentCommand(
153
+ "destroy",
154
+ stackSpanAttrs,
155
+ )((args) =>
153
156
  execStack({
154
157
  ...args,
155
158
  destroy: true,
@@ -165,7 +168,10 @@ export const planCommand = Command.make(
165
168
  stage,
166
169
  profile,
167
170
  },
168
- instrumentCommand("plan", stackSpanAttrs)((args) =>
171
+ instrumentCommand(
172
+ "plan",
173
+ stackSpanAttrs,
174
+ )((args) =>
169
175
  execStack({
170
176
  ...args,
171
177
  // plan is the same as deploy with dryRun always set to true
@@ -45,67 +45,74 @@ export const loginCommand = Command.make(
45
45
  },
46
46
  instrumentCommand(
47
47
  "login",
48
- (a: { main: string; stage: string; profile: string; configure: boolean }) => ({
48
+ (a: {
49
+ main: string;
50
+ stage: string;
51
+ profile: string;
52
+ configure: boolean;
53
+ }) => ({
49
54
  "alchemy.stage": a.stage,
50
55
  "alchemy.profile": a.profile,
51
56
  "alchemy.main": a.main,
52
57
  "alchemy.configure": a.configure,
53
58
  }),
54
- )(Effect.fnUntraced(function* ({ main, stage, envFile, profile, configure }) {
55
- const stackEffect = yield* importStack(main);
59
+ )(
60
+ Effect.fnUntraced(function* ({ main, stage, envFile, profile, configure }) {
61
+ const stackEffect = yield* importStack(main);
56
62
 
57
- const authProviders: AuthProviders["Service"] = {};
63
+ const authProviders: AuthProviders["Service"] = {};
58
64
 
59
- const services = Layer.mergeAll(
60
- Layer.succeed(AuthProviders, authProviders),
61
- ConfigProvider.layer(
62
- withProfileOverride(yield* loadConfigProvider(envFile), profile),
63
- ),
64
- Logger.layer([fileLogger("out")]),
65
- Layer.succeed(Stage, stage),
66
- State.localState(),
67
- );
68
-
69
- yield* Effect.gen(function* () {
70
- yield* Effect.catchCause(stackEffect, (cause) =>
71
- Console.warn(
72
- `Ignoring error while building stack for login (likely due to missing or broken credentials):\n${Cause.pretty(cause)}`,
65
+ const services = Layer.mergeAll(
66
+ Layer.succeed(AuthProviders, authProviders),
67
+ ConfigProvider.layer(
68
+ withProfileOverride(yield* loadConfigProvider(envFile), profile),
73
69
  ),
70
+ Logger.layer([fileLogger("out")]),
71
+ Layer.succeed(Stage, stage),
72
+ State.localState(),
74
73
  );
75
74
 
76
- const ci = yield* Config.boolean("CI").pipe(Config.withDefault(false));
77
- const providers = Object.values(authProviders);
78
-
79
- if (providers.length === 0) {
80
- yield* Console.log(
81
- "No AuthProviders registered. Make sure the stack's providers() layer includes AuthProviderLayer entries.",
75
+ yield* Effect.gen(function* () {
76
+ yield* Effect.catchCause(stackEffect, (cause) =>
77
+ Console.warn(
78
+ `Ignoring error while building stack for login (likely due to missing or broken credentials):\n${Cause.pretty(cause)}`,
79
+ ),
82
80
  );
83
- return;
84
- }
85
81
 
86
- yield* Effect.forEach(
87
- providers,
88
- (provider) =>
89
- Effect.gen(function* () {
90
- const existing = yield* getProfile(profile);
91
- const stored = existing?.[provider.name];
82
+ const ci = yield* Config.boolean("CI").pipe(Config.withDefault(false));
83
+ const providers = Object.values(authProviders);
92
84
 
93
- let cfg: { method: string };
94
- if (configure || stored == null) {
95
- cfg = yield* provider.configure(profile, { ci });
96
- yield* setProfile(profile, {
97
- ...existing,
98
- [provider.name]: cfg,
99
- });
100
- } else {
101
- cfg = stored;
102
- }
85
+ if (providers.length === 0) {
86
+ yield* Console.log(
87
+ "No AuthProviders registered. Make sure the stack's providers() layer includes AuthProviderLayer entries.",
88
+ );
89
+ return;
90
+ }
103
91
 
104
- yield* provider.login(profile, cfg);
105
- yield* provider.prettyPrint(profile, cfg);
106
- }),
107
- { discard: true },
108
- );
109
- }).pipe(Effect.provide(services));
110
- })),
92
+ yield* Effect.forEach(
93
+ providers,
94
+ (provider) =>
95
+ Effect.gen(function* () {
96
+ const existing = yield* getProfile(profile);
97
+ const stored = existing?.[provider.name];
98
+
99
+ let cfg: { method: string };
100
+ if (configure || stored == null) {
101
+ cfg = yield* provider.configure(profile, { ci });
102
+ yield* setProfile(profile, {
103
+ ...existing,
104
+ [provider.name]: cfg,
105
+ });
106
+ } else {
107
+ cfg = stored;
108
+ }
109
+
110
+ yield* provider.login(profile, cfg);
111
+ yield* provider.prettyPrint(profile, cfg);
112
+ }),
113
+ { discard: true },
114
+ );
115
+ }).pipe(Effect.provide(services));
116
+ }),
117
+ ),
111
118
  );
@@ -61,106 +61,108 @@ export const logsCommand = Command.make(
61
61
  "alchemy.main": a.main,
62
62
  "alchemy.limit": a.limit,
63
63
  }),
64
- )(Effect.fnUntraced(function* ({
65
- main,
66
- stage,
67
- envFile,
68
- profile,
69
- filter,
70
- limit,
71
- since,
72
- }) {
73
- const stackEffect = yield* importStack(main);
74
-
75
- const services = Layer.mergeAll(
76
- ConfigProvider.layer(
77
- withProfileOverride(yield* loadConfigProvider(envFile), profile),
78
- ),
79
- Layer.succeed(AuthProviders, {}),
80
- Layer.succeed(Stage, stage),
81
- Logger.layer([fileLogger("out")]),
82
- State.localState(),
83
- );
84
-
85
- const sinceDate = since ? parseSince(since) : undefined;
86
-
87
- yield* Effect.gen(function* () {
88
- const state = yield* State.State;
89
- const stack = yield* stackEffect;
64
+ )(
65
+ Effect.fnUntraced(function* ({
66
+ main,
67
+ stage,
68
+ envFile,
69
+ profile,
70
+ filter,
71
+ limit,
72
+ since,
73
+ }) {
74
+ const stackEffect = yield* importStack(main);
75
+
76
+ const services = Layer.mergeAll(
77
+ ConfigProvider.layer(
78
+ withProfileOverride(yield* loadConfigProvider(envFile), profile),
79
+ ),
80
+ Layer.succeed(AuthProviders, {}),
81
+ Layer.succeed(Stage, stage),
82
+ Logger.layer([fileLogger("out")]),
83
+ State.localState(),
84
+ );
85
+
86
+ const sinceDate = since ? parseSince(since) : undefined;
90
87
 
91
88
  yield* Effect.gen(function* () {
92
- const filterSet = parseResourceFilter(filter);
93
- const availableIds = [
94
- ...new Set(Object.values(stack.resources).map((r) => r.LogicalId)),
95
- ].sort();
96
-
97
- if (filterSet) {
98
- for (const id of filterSet) {
99
- if (!availableIds.includes(id)) {
100
- return yield* Effect.die(
101
- new Error(
102
- `Unknown resource '${id}' in --filter. Available: ${availableIds.join(", ") || "(none)"}`,
103
- ),
104
- );
89
+ const state = yield* State.State;
90
+ const stack = yield* stackEffect;
91
+
92
+ yield* Effect.gen(function* () {
93
+ const filterSet = parseResourceFilter(filter);
94
+ const availableIds = [
95
+ ...new Set(Object.values(stack.resources).map((r) => r.LogicalId)),
96
+ ].sort();
97
+
98
+ if (filterSet) {
99
+ for (const id of filterSet) {
100
+ if (!availableIds.includes(id)) {
101
+ return yield* Effect.die(
102
+ new Error(
103
+ `Unknown resource '${id}' in --filter. Available: ${availableIds.join(", ") || "(none)"}`,
104
+ ),
105
+ );
106
+ }
105
107
  }
106
108
  }
107
- }
108
109
 
109
- const fqns = Object.keys(stack.resources);
110
- const allLogs: { logicalId: string; lines: LogLine[] }[] = [];
110
+ const fqns = Object.keys(stack.resources);
111
+ const allLogs: { logicalId: string; lines: LogLine[] }[] = [];
111
112
 
112
- for (const fqn of fqns) {
113
- const resource = stack.resources[fqn]!;
114
- if (filterSet && !filterSet.has(resource.LogicalId)) continue;
113
+ for (const fqn of fqns) {
114
+ const resource = stack.resources[fqn]!;
115
+ if (filterSet && !filterSet.has(resource.LogicalId)) continue;
115
116
 
116
- const resourceState = yield* state.get({
117
- stack: stack.name,
118
- stage: stack.stage,
119
- fqn,
120
- });
121
- if (!resourceState?.attr) continue;
117
+ const resourceState = yield* state.get({
118
+ stack: stack.name,
119
+ stage: stack.stage,
120
+ fqn,
121
+ });
122
+ if (!resourceState?.attr) continue;
122
123
 
123
- const provider = yield* findProviderByType(resource.Type);
124
- if (!provider.logs) continue;
124
+ const provider = yield* findProviderByType(resource.Type);
125
+ if (!provider.logs) continue;
125
126
 
126
- const lines = yield* provider.logs({
127
- id: resource.LogicalId,
128
- instanceId: resourceState.instanceId,
129
- props: resourceState.props as any,
130
- output: resourceState.attr as any,
131
- options: { limit, since: sinceDate },
132
- });
127
+ const lines = yield* provider.logs({
128
+ id: resource.LogicalId,
129
+ instanceId: resourceState.instanceId,
130
+ props: resourceState.props as any,
131
+ output: resourceState.attr as any,
132
+ options: { limit, since: sinceDate },
133
+ });
133
134
 
134
- allLogs.push({ logicalId: resource.LogicalId, lines });
135
- }
135
+ allLogs.push({ logicalId: resource.LogicalId, lines });
136
+ }
136
137
 
137
- if (allLogs.length === 0) {
138
- if (filterSet) {
139
- yield* Console.log(
140
- "No resources with logs match --filter (deploy first, or selected resources may not expose logs).",
141
- );
142
- } else {
143
- yield* Console.log(
144
- "No resources with logs found. Deploy first, then run logs.",
145
- );
138
+ if (allLogs.length === 0) {
139
+ if (filterSet) {
140
+ yield* Console.log(
141
+ "No resources with logs match --filter (deploy first, or selected resources may not expose logs).",
142
+ );
143
+ } else {
144
+ yield* Console.log(
145
+ "No resources with logs found. Deploy first, then run logs.",
146
+ );
147
+ }
148
+ return;
146
149
  }
147
- return;
148
- }
149
-
150
- const merged = allLogs
151
- .flatMap(({ logicalId, lines }, i) => {
152
- const color = TAIL_COLORS[i % TAIL_COLORS.length]!;
153
- return lines.map((line) => ({
154
- ...line,
155
- formatted: `${color}${formatLocalTimestamp(line.timestamp)} [${logicalId}]${TAIL_RESET} ${line.message}`,
156
- }));
157
- })
158
- .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
159
-
160
- for (const entry of merged) {
161
- yield* Console.log(entry.formatted);
162
- }
163
- }).pipe(Effect.provide(stack.services));
164
- }).pipe(Effect.provide(services));
165
- })),
150
+
151
+ const merged = allLogs
152
+ .flatMap(({ logicalId, lines }, i) => {
153
+ const color = TAIL_COLORS[i % TAIL_COLORS.length]!;
154
+ return lines.map((line) => ({
155
+ ...line,
156
+ formatted: `${color}${formatLocalTimestamp(line.timestamp)} [${logicalId}]${TAIL_RESET} ${line.message}`,
157
+ }));
158
+ })
159
+ .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
160
+
161
+ for (const entry of merged) {
162
+ yield* Console.log(entry.formatted);
163
+ }
164
+ }).pipe(Effect.provide(stack.services));
165
+ }).pipe(Effect.provide(services));
166
+ }),
167
+ ),
166
168
  );
@@ -23,88 +23,92 @@ import { envFile, instrumentCommand, profile } from "./_shared.ts";
23
23
  const showCommand = Command.make(
24
24
  "show",
25
25
  { profile, envFile },
26
- instrumentCommand(
27
- "profile.show",
28
- (a: { profile: string }) => ({ "alchemy.profile": a.profile }),
29
- )(Effect.fnUntraced(function* ({ profile, envFile }) {
30
- const stored = yield* getProfile(profile);
31
- if (stored == null) {
32
- const config = yield* readConfig;
33
- const names = Object.keys(config.profiles);
34
- yield* Console.log(`Profile '${profile}' not found.`);
35
- if (names.length > 0) {
36
- yield* Console.log(`Available profiles: ${names.sort().join(", ")}`);
37
- } else {
38
- yield* Console.log("No profiles configured. Run `alchemy login`.");
26
+ instrumentCommand("profile.show", (a: { profile: string }) => ({
27
+ "alchemy.profile": a.profile,
28
+ }))(
29
+ Effect.fnUntraced(function* ({ profile, envFile }) {
30
+ const stored = yield* getProfile(profile);
31
+ if (stored == null) {
32
+ const config = yield* readConfig;
33
+ const names = Object.keys(config.profiles);
34
+ yield* Console.log(`Profile '${profile}' not found.`);
35
+ if (names.length > 0) {
36
+ yield* Console.log(`Available profiles: ${names.sort().join(", ")}`);
37
+ } else {
38
+ yield* Console.log("No profiles configured. Run `alchemy login`.");
39
+ }
40
+ return;
39
41
  }
40
- return;
41
- }
42
42
 
43
- yield* Console.log(`Profile: ${profile}`);
43
+ yield* Console.log(`Profile: ${profile}`);
44
44
 
45
- const providerNames = Object.keys(stored).sort();
46
- if (providerNames.length === 0) {
47
- yield* Console.log("(no providers configured)");
48
- return;
49
- }
45
+ const providerNames = Object.keys(stored).sort();
46
+ if (providerNames.length === 0) {
47
+ yield* Console.log("(no providers configured)");
48
+ return;
49
+ }
50
50
 
51
- const authProviders: AuthProviders["Service"] = {};
52
- const authRegistry = Layer.succeed(AuthProviders, authProviders);
53
- const services = Layer.mergeAll(
54
- authRegistry,
55
- ConfigProvider.layer(
56
- withProfileOverride(yield* loadConfigProvider(envFile), profile),
57
- ),
58
- Logger.layer([fileLogger("out")]),
59
- // Building these layers triggers their AuthProviderLayer effect, which
60
- // registers the provider into the shared `authProviders` registry.
61
- Layer.provide(Layer.mergeAll(CloudflareAuth, AwsAuth), authRegistry),
62
- );
51
+ const authProviders: AuthProviders["Service"] = {};
52
+ const authRegistry = Layer.succeed(AuthProviders, authProviders);
53
+ const services = Layer.mergeAll(
54
+ authRegistry,
55
+ ConfigProvider.layer(
56
+ withProfileOverride(yield* loadConfigProvider(envFile), profile),
57
+ ),
58
+ Logger.layer([fileLogger("out")]),
59
+ // Building these layers triggers their AuthProviderLayer effect, which
60
+ // registers the provider into the shared `authProviders` registry.
61
+ Layer.provide(Layer.mergeAll(CloudflareAuth, AwsAuth), authRegistry),
62
+ );
63
63
 
64
- yield* Effect.gen(function* () {
65
- for (const name of providerNames) {
66
- const cfg = stored[name]!;
67
- yield* Console.log("");
68
- yield* Console.log(`── ${name} ──`);
69
- const provider = authProviders[name];
70
- if (provider == null) {
71
- // Unknown provider — fall back to a JSON dump so the user can still
72
- // see what's stored even if its module isn't bundled into the CLI.
73
- yield* Console.log(`method: ${cfg.method}`);
74
- const { method: _method, ...rest } = cfg as Record<string, unknown> &
75
- { method: string };
76
- for (const [k, v] of Object.entries(rest)) {
77
- yield* Console.log(
78
- `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`,
79
- );
64
+ yield* Effect.gen(function* () {
65
+ for (const name of providerNames) {
66
+ const cfg = stored[name]!;
67
+ yield* Console.log("");
68
+ yield* Console.log(`── ${name} ──`);
69
+ const provider = authProviders[name];
70
+ if (provider == null) {
71
+ // Unknown provider — fall back to a JSON dump so the user can still
72
+ // see what's stored even if its module isn't bundled into the CLI.
73
+ yield* Console.log(`method: ${cfg.method}`);
74
+ const { method: _method, ...rest } = cfg as Record<
75
+ string,
76
+ unknown
77
+ > & { method: string };
78
+ for (const [k, v] of Object.entries(rest)) {
79
+ yield* Console.log(
80
+ `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`,
81
+ );
82
+ }
83
+ continue;
80
84
  }
81
- continue;
85
+ yield* provider.prettyPrint(profile, cfg);
82
86
  }
83
- yield* provider.prettyPrint(profile, cfg);
84
- }
85
- }).pipe(Effect.provide(services));
86
- })),
87
+ }).pipe(Effect.provide(services));
88
+ }),
89
+ ),
87
90
  );
88
91
 
89
92
  const clearCommand = Command.make(
90
93
  "clear",
91
94
  { profile },
92
- instrumentCommand(
93
- "profile.clear",
94
- (a: { profile: string }) => ({ "alchemy.profile": a.profile }),
95
- )(Effect.fnUntraced(function* ({ profile }) {
96
- const removed = yield* deleteProfile(profile);
97
- yield* deleteProfileCredentials(profile);
98
- if (removed) {
99
- yield* Console.log(
100
- `Cleared profile '${profile}' and all its credentials.`,
101
- );
102
- } else {
103
- yield* Console.log(
104
- `Profile '${profile}' not found in profiles.json; removed any stray credentials directory.`,
105
- );
106
- }
107
- })),
95
+ instrumentCommand("profile.clear", (a: { profile: string }) => ({
96
+ "alchemy.profile": a.profile,
97
+ }))(
98
+ Effect.fnUntraced(function* ({ profile }) {
99
+ const removed = yield* deleteProfile(profile);
100
+ yield* deleteProfileCredentials(profile);
101
+ if (removed) {
102
+ yield* Console.log(
103
+ `Cleared profile '${profile}' and all its credentials.`,
104
+ );
105
+ } else {
106
+ yield* Console.log(
107
+ `Profile '${profile}' not found in profiles.json; removed any stray credentials directory.`,
108
+ );
109
+ }
110
+ }),
111
+ ),
108
112
  );
109
113
 
110
114
  export const profileCommand = Command.make("profile", {}).pipe(