@tinybirdco/sdk 0.0.3 → 0.0.6
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/README.md +87 -14
- package/dist/api/deploy.d.ts +41 -3
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +141 -19
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +77 -29
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/api/local.d.ts +92 -0
- package/dist/api/local.d.ts.map +1 -0
- package/dist/api/local.js +176 -0
- package/dist/api/local.js.map +1 -0
- package/dist/api/local.test.d.ts +2 -0
- package/dist/api/local.test.d.ts.map +1 -0
- package/dist/api/local.test.js +182 -0
- package/dist/api/local.test.js.map +1 -0
- package/dist/api/resources.d.ts +178 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +244 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/resources.test.d.ts +2 -0
- package/dist/api/resources.test.d.ts.map +1 -0
- package/dist/api/resources.test.js +255 -0
- package/dist/api/resources.test.js.map +1 -0
- package/dist/cli/commands/build.d.ts +6 -4
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +95 -47
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/deploy.d.ts +39 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +90 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +9 -2
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +60 -31
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +24 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +174 -23
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +190 -30
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/config.d.ts +14 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +7 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +29 -0
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/index.js +107 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/package-manager.d.ts +8 -0
- package/dist/cli/utils/package-manager.d.ts.map +1 -0
- package/dist/cli/utils/package-manager.js +45 -0
- package/dist/cli/utils/package-manager.js.map +1 -0
- package/dist/cli/utils/package-manager.test.d.ts +2 -0
- package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
- package/dist/cli/utils/package-manager.test.js +85 -0
- package/dist/cli/utils/package-manager.test.js.map +1 -0
- package/dist/codegen/index.d.ts +39 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +300 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/index.test.d.ts +2 -0
- package/dist/codegen/index.test.d.ts.map +1 -0
- package/dist/codegen/index.test.js +310 -0
- package/dist/codegen/index.test.js.map +1 -0
- package/dist/codegen/type-mapper.d.ts +20 -0
- package/dist/codegen/type-mapper.d.ts.map +1 -0
- package/dist/codegen/type-mapper.js +238 -0
- package/dist/codegen/type-mapper.js.map +1 -0
- package/dist/codegen/type-mapper.test.d.ts +2 -0
- package/dist/codegen/type-mapper.test.d.ts.map +1 -0
- package/dist/codegen/type-mapper.test.js +167 -0
- package/dist/codegen/type-mapper.test.js.map +1 -0
- package/dist/codegen/utils.d.ts +46 -0
- package/dist/codegen/utils.d.ts.map +1 -0
- package/dist/codegen/utils.js +141 -0
- package/dist/codegen/utils.js.map +1 -0
- package/dist/codegen/utils.test.d.ts +2 -0
- package/dist/codegen/utils.test.d.ts.map +1 -0
- package/dist/codegen/utils.test.js +178 -0
- package/dist/codegen/utils.test.js.map +1 -0
- package/dist/generator/index.d.ts +3 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +17 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/index.test.js +104 -1
- package/dist/generator/index.test.js.map +1 -1
- package/dist/generator/loader.d.ts +15 -0
- package/dist/generator/loader.d.ts.map +1 -1
- package/dist/generator/loader.js +24 -0
- package/dist/generator/loader.js.map +1 -1
- package/dist/test/handlers.d.ts +49 -0
- package/dist/test/handlers.d.ts.map +1 -1
- package/dist/test/handlers.js +45 -0
- package/dist/test/handlers.js.map +1 -1
- package/package.json +4 -2
- package/src/api/deploy.test.ts +135 -34
- package/src/api/deploy.ts +203 -23
- package/src/api/local.test.ts +250 -0
- package/src/api/local.ts +270 -0
- package/src/api/resources.test.ts +332 -0
- package/src/api/resources.ts +554 -0
- package/src/cli/commands/build.ts +115 -53
- package/src/cli/commands/deploy.ts +126 -0
- package/src/cli/commands/dev.ts +81 -36
- package/src/cli/commands/init.test.ts +239 -30
- package/src/cli/commands/init.ts +243 -26
- package/src/cli/config.test.ts +47 -0
- package/src/cli/config.ts +20 -0
- package/src/cli/index.ts +120 -11
- package/src/cli/utils/package-manager.test.ts +118 -0
- package/src/cli/utils/package-manager.ts +44 -0
- package/src/codegen/index.test.ts +367 -0
- package/src/codegen/index.ts +379 -0
- package/src/codegen/type-mapper.test.ts +224 -0
- package/src/codegen/type-mapper.ts +265 -0
- package/src/codegen/utils.test.ts +221 -0
- package/src/codegen/utils.ts +174 -0
- package/src/generator/index.test.ts +121 -1
- package/src/generator/index.ts +19 -1
- package/src/generator/loader.ts +43 -0
- package/src/test/handlers.ts +58 -0
package/dist/test/handlers.js
CHANGED
|
@@ -46,6 +46,47 @@ export function createNoChangesResponse() {
|
|
|
46
46
|
result: "no_changes",
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Create deploy success response (for /v1/deploy endpoint)
|
|
51
|
+
* This returns a deployment object, not a build object
|
|
52
|
+
*/
|
|
53
|
+
export function createDeploySuccessResponse(options) {
|
|
54
|
+
return {
|
|
55
|
+
result: "success",
|
|
56
|
+
deployment: {
|
|
57
|
+
id: options?.deploymentId ?? "deploy-123",
|
|
58
|
+
status: options?.status ?? "pending",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create deployment status response (for /v1/deployments/:id endpoint)
|
|
64
|
+
*/
|
|
65
|
+
export function createDeploymentStatusResponse(options) {
|
|
66
|
+
return {
|
|
67
|
+
result: "success",
|
|
68
|
+
deployment: {
|
|
69
|
+
id: options?.deploymentId ?? "deploy-123",
|
|
70
|
+
status: options?.status ?? "data_ready",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create set-live success response (for /v1/deployments/:id/set-live endpoint)
|
|
76
|
+
*/
|
|
77
|
+
export function createSetLiveSuccessResponse() {
|
|
78
|
+
return {
|
|
79
|
+
result: "success",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create deployments list response (for /v1/deployments endpoint)
|
|
84
|
+
*/
|
|
85
|
+
export function createDeploymentsListResponse(options) {
|
|
86
|
+
return {
|
|
87
|
+
deployments: options?.deployments ?? [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
49
90
|
/**
|
|
50
91
|
* Default handlers for build and deploy endpoints
|
|
51
92
|
*/
|
|
@@ -58,5 +99,9 @@ export const handlers = [
|
|
|
58
99
|
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
59
100
|
return HttpResponse.json(createBuildSuccessResponse());
|
|
60
101
|
}),
|
|
102
|
+
// Deployments list endpoint - empty list by default (no stale deployments)
|
|
103
|
+
http.get(`${BASE_URL}/v1/deployments`, () => {
|
|
104
|
+
return HttpResponse.json(createDeploymentsListResponse());
|
|
105
|
+
}),
|
|
61
106
|
];
|
|
62
107
|
//# sourceMappingURL=handlers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/test/handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAEzC,MAAM,CAAC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAQ1C;IACC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,KAAK,EAAE;YACL,EAAE,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW;YACnC,kBAAkB,EAAE,OAAO,EAAE,YAAY,IAAI,EAAE;YAC/C,cAAc,EAAE,OAAO,EAAE,QAAQ,IAAI,EAAE;YACvC,kBAAkB,EAAE,OAAO,EAAE,YAAY,IAAI,EAAE;YAC/C,wBAAwB,EAAE,OAAO,EAAE,kBAAkB,IAAI,EAAE;YAC3D,oBAAoB,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE;YACnD,wBAAwB,EAAE,OAAO,EAAE,kBAAkB,IAAI,EAAE;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAa;IACtD,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iCAAiC,CAC/C,MAAmD;IAEnD,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO;QACL,MAAM,EAAE,YAAY;KACrB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,sCAAsC;IACtC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,WAAW,EAAE,GAAG,EAAE;QACrC,OAAO,YAAY,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC;IAEF,uCAAuC;IACvC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,YAAY,EAAE,GAAG,EAAE;QACtC,OAAO,YAAY,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC;CACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/test/handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAEzC,MAAM,CAAC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAQ1C;IACC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,KAAK,EAAE;YACL,EAAE,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW;YACnC,kBAAkB,EAAE,OAAO,EAAE,YAAY,IAAI,EAAE;YAC/C,cAAc,EAAE,OAAO,EAAE,QAAQ,IAAI,EAAE;YACvC,kBAAkB,EAAE,OAAO,EAAE,YAAY,IAAI,EAAE;YAC/C,wBAAwB,EAAE,OAAO,EAAE,kBAAkB,IAAI,EAAE;YAC3D,oBAAoB,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE;YACnD,wBAAwB,EAAE,OAAO,EAAE,kBAAkB,IAAI,EAAE;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAa;IACtD,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iCAAiC,CAC/C,MAAmD;IAEnD,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO;QACL,MAAM,EAAE,YAAY;KACrB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAG3C;IACC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE;YACV,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,YAAY;YACzC,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,SAAS;SACrC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B,CAAC,OAG9C;IACC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE;YACV,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,YAAY;YACzC,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,YAAY;SACxC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,4BAA4B;IAC1C,OAAO;QACL,MAAM,EAAE,SAAS;KAClB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAAC,OAE7C;IACC,OAAO;QACL,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,EAAE;KACxC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,sCAAsC;IACtC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,WAAW,EAAE,GAAG,EAAE;QACrC,OAAO,YAAY,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC;IAEF,uCAAuC;IACvC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,YAAY,EAAE,GAAG,EAAE;QACtC,OAAO,YAAY,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC;IAEF,2EAA2E;IAC3E,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,iBAAiB,EAAE,GAAG,EAAE;QAC1C,OAAO,YAAY,CAAC,IAAI,CAAC,6BAA6B,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC;CACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinybirdco/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "TypeScript SDK for Tinybird Forward - define datasources and pipes as TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
"author": "Tinybird",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.0.0",
|
|
34
35
|
"chokidar": "^4.0.0",
|
|
35
36
|
"commander": "^12.0.0",
|
|
36
37
|
"dotenv": "^16.0.0",
|
|
37
|
-
"esbuild": "^0.24.0"
|
|
38
|
+
"esbuild": "^0.24.0",
|
|
39
|
+
"picocolors": "^1.1.1"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@types/node": "^20.0.0",
|
package/src/api/deploy.test.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterEach, afterAll } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
|
|
2
2
|
import { setupServer } from "msw/node";
|
|
3
3
|
import { http, HttpResponse } from "msw";
|
|
4
4
|
import { deployToMain } from "./deploy.js";
|
|
5
5
|
import type { BuildConfig } from "./build.js";
|
|
6
6
|
import {
|
|
7
7
|
BASE_URL,
|
|
8
|
-
|
|
8
|
+
createDeploySuccessResponse,
|
|
9
|
+
createDeploymentStatusResponse,
|
|
10
|
+
createSetLiveSuccessResponse,
|
|
9
11
|
createBuildFailureResponse,
|
|
10
12
|
createBuildMultipleErrorsResponse,
|
|
11
|
-
|
|
13
|
+
createDeploymentsListResponse,
|
|
12
14
|
} from "../test/handlers.js";
|
|
13
15
|
import type { GeneratedResources } from "../generator/index.js";
|
|
14
16
|
|
|
15
17
|
const server = setupServer();
|
|
16
18
|
|
|
17
19
|
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Set up default handler for deployments list (used by stale deployment cleanup)
|
|
22
|
+
server.use(
|
|
23
|
+
http.get(`${BASE_URL}/v1/deployments`, () => {
|
|
24
|
+
return HttpResponse.json(createDeploymentsListResponse());
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
});
|
|
18
28
|
afterEach(() => server.resetHandlers());
|
|
19
29
|
afterAll(() => server.close());
|
|
20
30
|
|
|
@@ -34,42 +44,64 @@ describe("Deploy API", () => {
|
|
|
34
44
|
connections: [],
|
|
35
45
|
};
|
|
36
46
|
|
|
47
|
+
// Helper to set up successful deploy flow
|
|
48
|
+
function setupSuccessfulDeployFlow(deploymentId = "deploy-abc") {
|
|
49
|
+
server.use(
|
|
50
|
+
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
51
|
+
return HttpResponse.json(
|
|
52
|
+
createDeploySuccessResponse({ deploymentId, status: "pending" })
|
|
53
|
+
);
|
|
54
|
+
}),
|
|
55
|
+
http.get(`${BASE_URL}/v1/deployments/${deploymentId}`, () => {
|
|
56
|
+
return HttpResponse.json(
|
|
57
|
+
createDeploymentStatusResponse({ deploymentId, status: "data_ready" })
|
|
58
|
+
);
|
|
59
|
+
}),
|
|
60
|
+
http.post(`${BASE_URL}/v1/deployments/${deploymentId}/set-live`, () => {
|
|
61
|
+
return HttpResponse.json(createSetLiveSuccessResponse());
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
describe("deployToMain", () => {
|
|
38
|
-
it("successfully deploys resources", async () => {
|
|
39
|
-
|
|
40
|
-
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
41
|
-
return HttpResponse.json(
|
|
42
|
-
createBuildSuccessResponse({
|
|
43
|
-
buildId: "deploy-abc",
|
|
44
|
-
newPipes: ["top_events"],
|
|
45
|
-
newDatasources: ["events"],
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
})
|
|
49
|
-
);
|
|
67
|
+
it("successfully deploys resources with full flow", async () => {
|
|
68
|
+
setupSuccessfulDeployFlow("deploy-abc");
|
|
50
69
|
|
|
51
|
-
const result = await deployToMain(config, resources);
|
|
70
|
+
const result = await deployToMain(config, resources, { pollIntervalMs: 1 });
|
|
52
71
|
|
|
53
72
|
expect(result.success).toBe(true);
|
|
54
73
|
expect(result.result).toBe("success");
|
|
55
74
|
expect(result.buildId).toBe("deploy-abc");
|
|
56
75
|
expect(result.datasourceCount).toBe(1);
|
|
57
76
|
expect(result.pipeCount).toBe(1);
|
|
58
|
-
expect(result.pipes?.created).toEqual(["top_events"]);
|
|
59
|
-
expect(result.datasources?.created).toEqual(["events"]);
|
|
60
77
|
});
|
|
61
78
|
|
|
62
|
-
it("
|
|
79
|
+
it("polls until deployment is ready", async () => {
|
|
80
|
+
let pollCount = 0;
|
|
81
|
+
|
|
63
82
|
server.use(
|
|
64
83
|
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
65
|
-
return HttpResponse.json(
|
|
84
|
+
return HttpResponse.json(
|
|
85
|
+
createDeploySuccessResponse({ deploymentId: "deploy-poll", status: "pending" })
|
|
86
|
+
);
|
|
87
|
+
}),
|
|
88
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-poll`, () => {
|
|
89
|
+
pollCount++;
|
|
90
|
+
// Return pending for first 2 polls, then data_ready
|
|
91
|
+
const status = pollCount < 3 ? "pending" : "data_ready";
|
|
92
|
+
return HttpResponse.json(
|
|
93
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-poll", status })
|
|
94
|
+
);
|
|
95
|
+
}),
|
|
96
|
+
http.post(`${BASE_URL}/v1/deployments/deploy-poll/set-live`, () => {
|
|
97
|
+
return HttpResponse.json(createSetLiveSuccessResponse());
|
|
66
98
|
})
|
|
67
99
|
);
|
|
68
100
|
|
|
69
|
-
const result = await deployToMain(config, resources);
|
|
101
|
+
const result = await deployToMain(config, resources, { pollIntervalMs: 1 });
|
|
70
102
|
|
|
71
103
|
expect(result.success).toBe(true);
|
|
72
|
-
expect(
|
|
104
|
+
expect(pollCount).toBe(3);
|
|
73
105
|
});
|
|
74
106
|
|
|
75
107
|
it("handles deploy failure with single error", async () => {
|
|
@@ -146,31 +178,66 @@ describe("Deploy API", () => {
|
|
|
146
178
|
server.use(
|
|
147
179
|
http.post(`${BASE_URL}/v1/deploy`, ({ request }) => {
|
|
148
180
|
capturedUrl = request.url;
|
|
149
|
-
return HttpResponse.json(
|
|
181
|
+
return HttpResponse.json(
|
|
182
|
+
createDeploySuccessResponse({ deploymentId: "deploy-url-test" })
|
|
183
|
+
);
|
|
184
|
+
}),
|
|
185
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-url-test`, () => {
|
|
186
|
+
return HttpResponse.json(
|
|
187
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-url-test", status: "data_ready" })
|
|
188
|
+
);
|
|
189
|
+
}),
|
|
190
|
+
http.post(`${BASE_URL}/v1/deployments/deploy-url-test/set-live`, () => {
|
|
191
|
+
return HttpResponse.json(createSetLiveSuccessResponse());
|
|
150
192
|
})
|
|
151
193
|
);
|
|
152
194
|
|
|
153
|
-
await deployToMain(config, resources);
|
|
195
|
+
await deployToMain(config, resources, { pollIntervalMs: 1 });
|
|
154
196
|
|
|
155
197
|
expect(capturedUrl).toBe(`${BASE_URL}/v1/deploy`);
|
|
156
198
|
});
|
|
157
199
|
|
|
158
|
-
it("
|
|
200
|
+
it("handles failed deployment status", async () => {
|
|
159
201
|
server.use(
|
|
160
202
|
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
161
203
|
return HttpResponse.json(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
204
|
+
createDeploySuccessResponse({ deploymentId: "deploy-fail", status: "pending" })
|
|
205
|
+
);
|
|
206
|
+
}),
|
|
207
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-fail`, () => {
|
|
208
|
+
return HttpResponse.json(
|
|
209
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-fail", status: "failed" })
|
|
166
210
|
);
|
|
167
211
|
})
|
|
168
212
|
);
|
|
169
213
|
|
|
170
|
-
const result = await deployToMain(config, resources);
|
|
214
|
+
const result = await deployToMain(config, resources, { pollIntervalMs: 1 });
|
|
171
215
|
|
|
172
|
-
expect(result.
|
|
173
|
-
expect(result.
|
|
216
|
+
expect(result.success).toBe(false);
|
|
217
|
+
expect(result.error).toContain("Deployment failed with status: failed");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("handles set-live failure", async () => {
|
|
221
|
+
server.use(
|
|
222
|
+
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
223
|
+
return HttpResponse.json(
|
|
224
|
+
createDeploySuccessResponse({ deploymentId: "deploy-setlive-fail" })
|
|
225
|
+
);
|
|
226
|
+
}),
|
|
227
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-setlive-fail`, () => {
|
|
228
|
+
return HttpResponse.json(
|
|
229
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-setlive-fail", status: "data_ready" })
|
|
230
|
+
);
|
|
231
|
+
}),
|
|
232
|
+
http.post(`${BASE_URL}/v1/deployments/deploy-setlive-fail/set-live`, () => {
|
|
233
|
+
return HttpResponse.json({ error: "Set live failed" }, { status: 500 });
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const result = await deployToMain(config, resources, { pollIntervalMs: 1 });
|
|
238
|
+
|
|
239
|
+
expect(result.success).toBe(false);
|
|
240
|
+
expect(result.error).toContain("Failed to set deployment as live");
|
|
174
241
|
});
|
|
175
242
|
|
|
176
243
|
it("normalizes baseUrl with trailing slash", async () => {
|
|
@@ -179,16 +246,50 @@ describe("Deploy API", () => {
|
|
|
179
246
|
server.use(
|
|
180
247
|
http.post(`${BASE_URL}/v1/deploy`, ({ request }) => {
|
|
181
248
|
capturedUrl = request.url;
|
|
182
|
-
return HttpResponse.json(
|
|
249
|
+
return HttpResponse.json(
|
|
250
|
+
createDeploySuccessResponse({ deploymentId: "deploy-slash" })
|
|
251
|
+
);
|
|
252
|
+
}),
|
|
253
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-slash`, () => {
|
|
254
|
+
return HttpResponse.json(
|
|
255
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-slash", status: "data_ready" })
|
|
256
|
+
);
|
|
257
|
+
}),
|
|
258
|
+
http.post(`${BASE_URL}/v1/deployments/deploy-slash/set-live`, () => {
|
|
259
|
+
return HttpResponse.json(createSetLiveSuccessResponse());
|
|
183
260
|
})
|
|
184
261
|
);
|
|
185
262
|
|
|
186
263
|
await deployToMain(
|
|
187
264
|
{ ...config, baseUrl: `${BASE_URL}/` },
|
|
188
|
-
resources
|
|
265
|
+
resources,
|
|
266
|
+
{ pollIntervalMs: 1 }
|
|
189
267
|
);
|
|
190
268
|
|
|
191
269
|
expect(capturedUrl).toBe(`${BASE_URL}/v1/deploy`);
|
|
192
270
|
});
|
|
271
|
+
|
|
272
|
+
it("times out when deployment never becomes ready", async () => {
|
|
273
|
+
server.use(
|
|
274
|
+
http.post(`${BASE_URL}/v1/deploy`, () => {
|
|
275
|
+
return HttpResponse.json(
|
|
276
|
+
createDeploySuccessResponse({ deploymentId: "deploy-timeout", status: "pending" })
|
|
277
|
+
);
|
|
278
|
+
}),
|
|
279
|
+
http.get(`${BASE_URL}/v1/deployments/deploy-timeout`, () => {
|
|
280
|
+
return HttpResponse.json(
|
|
281
|
+
createDeploymentStatusResponse({ deploymentId: "deploy-timeout", status: "pending" })
|
|
282
|
+
);
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const result = await deployToMain(config, resources, {
|
|
287
|
+
pollIntervalMs: 1,
|
|
288
|
+
maxPollAttempts: 3,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(result.success).toBe(false);
|
|
292
|
+
expect(result.error).toContain("Deployment timed out");
|
|
293
|
+
});
|
|
193
294
|
});
|
|
194
295
|
});
|
package/src/api/deploy.ts
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deploy resources to Tinybird main workspace
|
|
3
|
-
* Uses the /v1/deploy endpoint
|
|
3
|
+
* Uses the /v1/deploy endpoint to create a deployment, then sets it live
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { GeneratedResources } from "../generator/index.js";
|
|
7
|
-
import type { BuildConfig, BuildApiResult
|
|
7
|
+
import type { BuildConfig, BuildApiResult } from "./build.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Deployment object returned by the /v1/deploy endpoint
|
|
11
|
+
*/
|
|
12
|
+
export interface Deployment {
|
|
13
|
+
id: string;
|
|
14
|
+
status: string;
|
|
15
|
+
live?: boolean;
|
|
16
|
+
created_at?: string;
|
|
17
|
+
updated_at?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Response from /v1/deployments list endpoint
|
|
22
|
+
*/
|
|
23
|
+
export interface DeploymentsListResponse {
|
|
24
|
+
deployments: Deployment[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Response from /v1/deploy endpoint
|
|
29
|
+
*/
|
|
30
|
+
export interface DeployResponse {
|
|
31
|
+
result: "success" | "failed";
|
|
32
|
+
deployment?: Deployment;
|
|
33
|
+
error?: string;
|
|
34
|
+
errors?: Array<{ filename?: string; error: string }>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Response from /v1/deployments/{id} endpoint
|
|
39
|
+
*/
|
|
40
|
+
export interface DeploymentStatusResponse {
|
|
41
|
+
result: string;
|
|
42
|
+
deployment: Deployment;
|
|
43
|
+
}
|
|
8
44
|
|
|
9
45
|
/**
|
|
10
46
|
* Deploy generated resources to Tinybird main workspace
|
|
11
47
|
*
|
|
12
48
|
* Uses the /v1/deploy endpoint which accepts all resources in a single
|
|
13
|
-
* multipart form request.
|
|
14
|
-
* (
|
|
49
|
+
* multipart form request. After creating the deployment, this function:
|
|
50
|
+
* 1. Polls until the deployment is ready (status === 'data_ready')
|
|
51
|
+
* 2. Sets the deployment as live via /v1/deployments/{id}/set-live
|
|
15
52
|
*
|
|
16
53
|
* @param config - Build configuration with API URL and token
|
|
17
54
|
* @param resources - Generated resources to deploy
|
|
@@ -38,9 +75,13 @@ import type { BuildConfig, BuildApiResult, BuildResponse } from "./build.js";
|
|
|
38
75
|
export async function deployToMain(
|
|
39
76
|
config: BuildConfig,
|
|
40
77
|
resources: GeneratedResources,
|
|
41
|
-
options?: { debug?: boolean }
|
|
78
|
+
options?: { debug?: boolean; pollIntervalMs?: number; maxPollAttempts?: number }
|
|
42
79
|
): Promise<BuildApiResult> {
|
|
43
80
|
const debug = options?.debug ?? !!process.env.TINYBIRD_DEBUG;
|
|
81
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 1000;
|
|
82
|
+
const maxPollAttempts = options?.maxPollAttempts ?? 120; // 2 minutes max
|
|
83
|
+
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
84
|
+
|
|
44
85
|
const formData = new FormData();
|
|
45
86
|
|
|
46
87
|
// Add datasources
|
|
@@ -73,14 +114,48 @@ export async function deployToMain(
|
|
|
73
114
|
);
|
|
74
115
|
}
|
|
75
116
|
|
|
76
|
-
//
|
|
77
|
-
|
|
117
|
+
// Step 0: Clean up any stale non-live deployments that might block the new deployment
|
|
118
|
+
try {
|
|
119
|
+
const deploymentsUrl = `${baseUrl}/v1/deployments`;
|
|
120
|
+
const deploymentsResponse = await fetch(deploymentsUrl, {
|
|
121
|
+
headers: {
|
|
122
|
+
Authorization: `Bearer ${config.token}`,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (deploymentsResponse.ok) {
|
|
127
|
+
const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse;
|
|
128
|
+
const staleDeployments = deploymentsBody.deployments.filter(
|
|
129
|
+
(d) => !d.live && d.status !== "live"
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
for (const stale of staleDeployments) {
|
|
133
|
+
if (debug) {
|
|
134
|
+
console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`);
|
|
135
|
+
}
|
|
136
|
+
await fetch(`${baseUrl}/v1/deployments/${stale.id}`, {
|
|
137
|
+
method: "DELETE",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${config.token}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// Ignore errors during cleanup - we'll try to deploy anyway
|
|
146
|
+
if (debug) {
|
|
147
|
+
console.log(`[debug] Failed to clean up stale deployments: ${e}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Step 1: Create deployment via /v1/deploy
|
|
152
|
+
const deployUrl = `${baseUrl}/v1/deploy`;
|
|
78
153
|
|
|
79
154
|
if (debug) {
|
|
80
|
-
console.log(`[debug] POST ${
|
|
155
|
+
console.log(`[debug] POST ${deployUrl}`);
|
|
81
156
|
}
|
|
82
157
|
|
|
83
|
-
const response = await fetch(
|
|
158
|
+
const response = await fetch(deployUrl, {
|
|
84
159
|
method: "POST",
|
|
85
160
|
headers: {
|
|
86
161
|
Authorization: `Bearer ${config.token}`,
|
|
@@ -89,7 +164,7 @@ export async function deployToMain(
|
|
|
89
164
|
});
|
|
90
165
|
|
|
91
166
|
// Parse response
|
|
92
|
-
let body:
|
|
167
|
+
let body: DeployResponse;
|
|
93
168
|
const rawBody = await response.text();
|
|
94
169
|
|
|
95
170
|
if (debug) {
|
|
@@ -98,7 +173,7 @@ export async function deployToMain(
|
|
|
98
173
|
}
|
|
99
174
|
|
|
100
175
|
try {
|
|
101
|
-
body = JSON.parse(rawBody) as
|
|
176
|
+
body = JSON.parse(rawBody) as DeployResponse;
|
|
102
177
|
} catch {
|
|
103
178
|
throw new Error(
|
|
104
179
|
`Failed to parse response from Tinybird API: ${response.status} ${response.statusText}\nBody: ${rawBody}`
|
|
@@ -131,7 +206,7 @@ export async function deployToMain(
|
|
|
131
206
|
}
|
|
132
207
|
|
|
133
208
|
// Handle API result
|
|
134
|
-
if (body.result === "failed") {
|
|
209
|
+
if (body.result === "failed" || !body.deployment) {
|
|
135
210
|
return {
|
|
136
211
|
success: false,
|
|
137
212
|
result: "failed",
|
|
@@ -142,25 +217,130 @@ export async function deployToMain(
|
|
|
142
217
|
};
|
|
143
218
|
}
|
|
144
219
|
|
|
220
|
+
const deploymentId = body.deployment.id;
|
|
221
|
+
|
|
222
|
+
if (debug) {
|
|
223
|
+
console.log(`[debug] Deployment created with ID: ${deploymentId}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Step 2: Poll until deployment is ready
|
|
227
|
+
let deployment = body.deployment;
|
|
228
|
+
let attempts = 0;
|
|
229
|
+
|
|
230
|
+
while (deployment.status !== "data_ready" && attempts < maxPollAttempts) {
|
|
231
|
+
await sleep(pollIntervalMs);
|
|
232
|
+
attempts++;
|
|
233
|
+
|
|
234
|
+
if (debug) {
|
|
235
|
+
console.log(`[debug] Polling deployment status (attempt ${attempts})...`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const statusUrl = `${baseUrl}/v1/deployments/${deploymentId}`;
|
|
239
|
+
const statusResponse = await fetch(statusUrl, {
|
|
240
|
+
headers: {
|
|
241
|
+
Authorization: `Bearer ${config.token}`,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!statusResponse.ok) {
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
result: "failed",
|
|
249
|
+
error: `Failed to check deployment status: ${statusResponse.status} ${statusResponse.statusText}`,
|
|
250
|
+
datasourceCount: resources.datasources.length,
|
|
251
|
+
pipeCount: resources.pipes.length,
|
|
252
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
253
|
+
buildId: deploymentId,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const statusBody = (await statusResponse.json()) as DeploymentStatusResponse;
|
|
258
|
+
deployment = statusBody.deployment;
|
|
259
|
+
|
|
260
|
+
if (debug) {
|
|
261
|
+
console.log(`[debug] Deployment status: ${deployment.status}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check for failed status
|
|
265
|
+
if (deployment.status === "failed" || deployment.status === "error") {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
result: "failed",
|
|
269
|
+
error: `Deployment failed with status: ${deployment.status}`,
|
|
270
|
+
datasourceCount: resources.datasources.length,
|
|
271
|
+
pipeCount: resources.pipes.length,
|
|
272
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
273
|
+
buildId: deploymentId,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (deployment.status !== "data_ready") {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
result: "failed",
|
|
282
|
+
error: `Deployment timed out after ${maxPollAttempts} attempts. Last status: ${deployment.status}`,
|
|
283
|
+
datasourceCount: resources.datasources.length,
|
|
284
|
+
pipeCount: resources.pipes.length,
|
|
285
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
286
|
+
buildId: deploymentId,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Step 3: Set the deployment as live
|
|
291
|
+
const setLiveUrl = `${baseUrl}/v1/deployments/${deploymentId}/set-live`;
|
|
292
|
+
|
|
293
|
+
if (debug) {
|
|
294
|
+
console.log(`[debug] POST ${setLiveUrl}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const setLiveResponse = await fetch(setLiveUrl, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: {
|
|
300
|
+
Authorization: `Bearer ${config.token}`,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!setLiveResponse.ok) {
|
|
305
|
+
const setLiveBody = await setLiveResponse.text();
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
result: "failed",
|
|
309
|
+
error: `Failed to set deployment as live: ${setLiveResponse.status} ${setLiveResponse.statusText}\n${setLiveBody}`,
|
|
310
|
+
datasourceCount: resources.datasources.length,
|
|
311
|
+
pipeCount: resources.pipes.length,
|
|
312
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
313
|
+
buildId: deploymentId,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (debug) {
|
|
318
|
+
console.log(`[debug] Deployment ${deploymentId} is now live`);
|
|
319
|
+
}
|
|
320
|
+
|
|
145
321
|
return {
|
|
146
322
|
success: true,
|
|
147
|
-
result:
|
|
323
|
+
result: "success",
|
|
148
324
|
datasourceCount: resources.datasources.length,
|
|
149
325
|
pipeCount: resources.pipes.length,
|
|
150
326
|
connectionCount: resources.connections?.length ?? 0,
|
|
151
|
-
buildId:
|
|
327
|
+
buildId: deploymentId,
|
|
152
328
|
pipes: {
|
|
153
|
-
changed:
|
|
154
|
-
created:
|
|
155
|
-
deleted:
|
|
329
|
+
changed: [],
|
|
330
|
+
created: [],
|
|
331
|
+
deleted: [],
|
|
156
332
|
},
|
|
157
333
|
datasources: {
|
|
158
|
-
changed:
|
|
159
|
-
created:
|
|
160
|
-
deleted:
|
|
334
|
+
changed: [],
|
|
335
|
+
created: [],
|
|
336
|
+
deleted: [],
|
|
161
337
|
},
|
|
162
|
-
// Keep deprecated fields for backwards compatibility
|
|
163
|
-
changedPipeNames: body.build?.changed_pipe_names ?? [],
|
|
164
|
-
newPipeNames: body.build?.new_pipe_names ?? [],
|
|
165
338
|
};
|
|
166
339
|
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Helper function to sleep for a given number of milliseconds
|
|
343
|
+
*/
|
|
344
|
+
function sleep(ms: number): Promise<void> {
|
|
345
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
346
|
+
}
|