@ashlr/stack-core 0.3.6 → 0.3.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashlr/stack-core",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Ashlr Stack core — provider adapters, .stack.toml config, Phantom integration. Shared logic behind the Stack CLI and MCP server.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -2,9 +2,9 @@
2
2
  "schema_version": 1,
3
3
  "kind": "ashlr-stack-manifest",
4
4
  "components": [
5
- { "id": "core", "name": "@ashlr/stack-core", "version": "0.3.6", "distribution": "npm" },
6
- { "id": "cli", "name": "@ashlr/stack", "version": "0.3.6", "distribution": "npm" },
7
- { "id": "mcp", "name": "ashlr-stack-mcp", "version": "0.3.6", "distribution": "npm" },
5
+ { "id": "core", "name": "@ashlr/stack-core", "version": "0.3.7", "distribution": "npm" },
6
+ { "id": "cli", "name": "@ashlr/stack", "version": "0.3.7", "distribution": "npm" },
7
+ { "id": "mcp", "name": "ashlr-stack-mcp", "version": "0.3.7", "distribution": "npm" },
8
8
  { "id": "plugin", "name": "stack", "version": "0.1.0", "distribution": "claude-code-plugin" }
9
9
  ]
10
10
  }
@@ -6430,3 +6430,55 @@ describe("webhookStubFor — arbitrary replacement with leading and trailing spa
6430
6430
  ).toBe(plain);
6431
6431
  });
6432
6432
  });
6433
+
6434
+ describe("webhookStubFor — $1 in replacement text (original PR 1023)", () => {
6435
+ const count = (s: string, needle: string) => s.split(needle).length - 1;
6436
+
6437
+ it("preserves $1 literally at every expansion site when it appears in a provider name", () => {
6438
+ // $1 is a replacement directive for String.prototype.replace/replaceAll:
6439
+ // it splices the first capture group. Template-literal interpolation does
6440
+ // NOT interpret $1, so provider names carrying it must land in the stub
6441
+ // as inert characters — exactly the bytes the caller passed in.
6442
+ const name = "svc$1pay";
6443
+ const stub = webhookStubFor(name);
6444
+
6445
+ // The name appears literally at the two case-preserving sites.
6446
+ expect(stub).toContain(`webhook handler stub for ${name}.`);
6447
+ expect(stub).toContain(`forge a ${name} webhook.`);
6448
+
6449
+ // At the handler name site the first character is upper-cased; $ is
6450
+ // ASCII so toUpperCase is the identity, leaving $1 intact.
6451
+ expect(stub).toContain("handleSvc$1payWebhook(req: Request)");
6452
+
6453
+ // At the reveal sites the entire name is upper-cased; $1 is
6454
+ // toUpperCase-invariant, so it survives unchanged.
6455
+ expect(count(stub, "phantom reveal SVC$1PAY_WEBHOOK_SECRET")).toBe(2);
6456
+
6457
+ // Dollar census: the name carries exactly one $ (in $1), and it appears
6458
+ // at five sites in the stub. No $ was dropped by $$ collapse and none
6459
+ // was minted by capture-group substitution.
6460
+ expect(count(stub, "$")).toBe(5);
6461
+ expect(count(stub, "$1")).toBe(5);
6462
+
6463
+ // No ${ opener leaked in — that would indicate the template was parsed
6464
+ // as a replace pattern and $1 was interpreted as a capture reference.
6465
+ expect(stub).not.toContain("${");
6466
+ });
6467
+
6468
+ it("preserves a bare $1 provider name through the capitalize path", () => {
6469
+ // $1 as the entire name exercises the capitalize() function: the first
6470
+ // codepoint is $, which toUpperCase leaves as $. The remaining `1` is
6471
+ // never touched. The name stays $1 at every expansion site.
6472
+ const name = "$1";
6473
+ const stub = webhookStubFor(name);
6474
+
6475
+ expect(stub).toContain(`webhook handler stub for ${name}.`);
6476
+ expect(stub).toContain(`forge a ${name} webhook.`);
6477
+ // capitalize("$1") = "$1" ($ is already uppercase, 1 stays 1)
6478
+ expect(stub).toContain("handle$1Webhook(req: Request)");
6479
+ expect(count(stub, "phantom reveal $1_WEBHOOK_SECRET")).toBe(2);
6480
+
6481
+ // Each $1 is a standalone $ at its own site — five total.
6482
+ expect(count(stub, "$1")).toBe(5);
6483
+ });
6484
+ });
@@ -26,9 +26,9 @@ export const STACK_MANIFEST = Object.freeze({
26
26
  schema_version: 1,
27
27
  kind: "ashlr-stack-manifest",
28
28
  components: Object.freeze([
29
- Object.freeze({ id: "core", name: "@ashlr/stack-core", version: "0.3.6", distribution: "npm" }),
30
- Object.freeze({ id: "cli", name: "@ashlr/stack", version: "0.3.6", distribution: "npm" }),
31
- Object.freeze({ id: "mcp", name: "ashlr-stack-mcp", version: "0.3.6", distribution: "npm" }),
29
+ Object.freeze({ id: "core", name: "@ashlr/stack-core", version: "0.3.7", distribution: "npm" }),
30
+ Object.freeze({ id: "cli", name: "@ashlr/stack", version: "0.3.7", distribution: "npm" }),
31
+ Object.freeze({ id: "mcp", name: "ashlr-stack-mcp", version: "0.3.7", distribution: "npm" }),
32
32
  Object.freeze({
33
33
  id: "plugin",
34
34
  name: "stack",