@deployfoundation/foundation-deploy 0.1.1 → 0.1.3

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.
@@ -70,10 +70,16 @@ async function unpackReleaseSkills(ctx, release) {
70
70
  const key = release.manifest?.skills.key ?? skillsKey(release.version);
71
71
  const dir = mkdtempSync(join(tmpdir(), "foundation-skills-"));
72
72
  const tarball = join(dir, "skills.tar.gz");
73
- const contents = join(dir, "skills");
74
- mkdirSync(contents, { recursive: true });
75
73
  await awsMutate(ctx, ["s3", "cp", `s3://${release.bucket}/${key}`, tarball]);
76
- await run(["tar", "-xzf", tarball, "-C", contents], { dryRun: ctx.dryRun });
74
+ return await extractSkillsTarball(tarball, dir, { dryRun: ctx.dryRun });
75
+ }
76
+ async function extractSkillsTarball(tarball, dir, opts = {}) {
77
+ const contents = join(dir, "skills");
78
+ await run(["tar", "-xzf", tarball, "-C", dir], { dryRun: opts.dryRun });
79
+ if (opts.dryRun !== true && !existsSync(contents))
80
+ throw new Error(`${tarball} has no top-level skills/ directory`);
81
+ if (opts.dryRun === true)
82
+ mkdirSync(contents, { recursive: true });
77
83
  return contents;
78
84
  }
79
85
 
@@ -416,6 +422,10 @@ async function ensureEndpoint(runner, opts) {
416
422
  return "created";
417
423
  }
418
424
  async function promoteEndpoint(runner, opts) {
425
+ if (opts.dryRun !== true && await endpointState(runner, opts.id, opts.name) === undefined) {
426
+ await ensureEndpoint(runner, opts);
427
+ return;
428
+ }
419
429
  await runner.mutate([
420
430
  "bedrock-agentcore-control",
421
431
  "update-agent-runtime-endpoint",
@@ -1831,6 +1841,15 @@ ${USAGE}`);
1831
1841
  }
1832
1842
  if (command === "slack-manifest") {
1833
1843
  const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
1844
+ if (process.env.EVENTS_REQUEST_URL === undefined)
1845
+ console.error([
1846
+ "warning: EVENTS_REQUEST_URL is not set, so this manifest has NO event",
1847
+ "subscriptions, slash commands or interactivity. Applying it to an existing",
1848
+ "app removes them. Set EVENTS_REQUEST_URL to the <Prefix>Api stack's EventsUrl",
1849
+ "output (commands and interactivity are derived from it) unless you are",
1850
+ "creating the app for the first time."
1851
+ ].join(`
1852
+ `));
1834
1853
  console.log(JSON.stringify(slackManifestFor(paths.instance, {
1835
1854
  ...process.env.EVENTS_REQUEST_URL === undefined ? {} : { EVENTS_REQUEST_URL: process.env.EVENTS_REQUEST_URL },
1836
1855
  ...process.env.COMMANDS_REQUEST_URL === undefined ? {} : { COMMANDS_REQUEST_URL: process.env.COMMANDS_REQUEST_URL },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deployfoundation/foundation-deploy",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Deploys one Foundation instance from a published, signed release: the CDK app and the deploy tool.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
package/src/deploy/cli.ts CHANGED
@@ -116,6 +116,20 @@ export async function main(argv: readonly string[] = process.argv.slice(2)): Pro
116
116
  // print a banner into it; every other command announces its target first.
117
117
  if (command === "slack-manifest") {
118
118
  const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
119
+ // Without the request URLs the manifest omits event subscriptions, slash
120
+ // commands and interactivity. That is right for creating a brand-new app,
121
+ // and destructive for an existing one: applying it strips the app's
122
+ // wiring. Say so on stderr, so stdout stays a clean manifest.
123
+ if (process.env.EVENTS_REQUEST_URL === undefined)
124
+ console.error(
125
+ [
126
+ "warning: EVENTS_REQUEST_URL is not set, so this manifest has NO event",
127
+ "subscriptions, slash commands or interactivity. Applying it to an existing",
128
+ "app removes them. Set EVENTS_REQUEST_URL to the <Prefix>Api stack's EventsUrl",
129
+ "output (commands and interactivity are derived from it) unless you are",
130
+ "creating the app for the first time.",
131
+ ].join("\n"),
132
+ );
119
133
  console.log(
120
134
  JSON.stringify(
121
135
  slackManifestFor(paths.instance, {
@@ -83,11 +83,32 @@ export async function unpackReleaseSkills(
83
83
  const key = release.manifest?.skills.key ?? skillsKey(release.version);
84
84
  const dir = mkdtempSync(join(tmpdir(), "foundation-skills-"));
85
85
  const tarball = join(dir, "skills.tar.gz");
86
- const contents = join(dir, "skills");
87
- mkdirSync(contents, { recursive: true });
88
86
  // `awsMutate` rather than a plain read, only so a dry run prints the copy
89
87
  // instead of performing it.
90
88
  await awsMutate(ctx, ["s3", "cp", `s3://${release.bucket}/${key}`, tarball]);
91
- await run(["tar", "-xzf", tarball, "-C", contents], { dryRun: ctx.dryRun });
89
+ return await extractSkillsTarball(tarball, dir, { dryRun: ctx.dryRun });
90
+ }
91
+
92
+ /**
93
+ * Unpack a release's `skills.tar.gz` and return the directory holding the
94
+ * skills themselves.
95
+ *
96
+ * `release-build` runs `tar -czf skills.tar.gz skills`, so the archive's top
97
+ * level is a `skills/` directory. It is extracted INTO `dir`, landing at
98
+ * `dir/skills`. v0.1.0 and v0.1.1 extracted into `dir/skills` instead, which
99
+ * produced `dir/skills/skills/…` and synced every product skill to
100
+ * `s3://…/skills/skills/`, one level below where the agent looks — so a release
101
+ * deploy silently shipped no product skills at all.
102
+ */
103
+ export async function extractSkillsTarball(
104
+ tarball: string,
105
+ dir: string,
106
+ opts: { dryRun?: boolean } = {},
107
+ ): Promise<string> {
108
+ const contents = join(dir, "skills");
109
+ await run(["tar", "-xzf", tarball, "-C", dir], { dryRun: opts.dryRun });
110
+ if (opts.dryRun !== true && !existsSync(contents))
111
+ throw new Error(`${tarball} has no top-level skills/ directory`);
112
+ if (opts.dryRun === true) mkdirSync(contents, { recursive: true });
92
113
  return contents;
93
114
  }
@@ -140,11 +140,25 @@ export async function ensureEndpoint(
140
140
  return "created";
141
141
  }
142
142
 
143
- /** Point an existing endpoint at `version` and wait for it to be READY there. */
143
+ /**
144
+ * Point the endpoint at `version` and wait for it to be READY there, creating
145
+ * it at that version when it does not exist yet.
146
+ *
147
+ * Only `setup` used to create `live`; `deploy` and `post-deploy` could only
148
+ * update it. An instance brought up the documented way, `foundation-deploy
149
+ * deploy` from npm, therefore reached promotion with no `live` to update, and
150
+ * the gateway's invoker, which calls `live`, had nothing to call: the first
151
+ * Slack message after a fresh install would have failed. Found by migrating
152
+ * Olivia.
153
+ */
144
154
  export async function promoteEndpoint(
145
155
  runner: EndpointRunner,
146
156
  opts: EndpointOptions,
147
157
  ): Promise<void> {
158
+ if (opts.dryRun !== true && (await endpointState(runner, opts.id, opts.name)) === undefined) {
159
+ await ensureEndpoint(runner, opts);
160
+ return;
161
+ }
148
162
  await runner.mutate([
149
163
  "bedrock-agentcore-control",
150
164
  "update-agent-runtime-endpoint",