oneclient 0.1.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.
- package/README.md +44 -0
- package/dist/artifact-create.d.ts +15 -0
- package/dist/artifact-create.d.ts.map +1 -0
- package/dist/artifact-create.js +130 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1 -0
- package/dist/credential-routing.d.ts +5 -0
- package/dist/credential-routing.d.ts.map +1 -0
- package/dist/credential-routing.js +32 -0
- package/dist/credentials.d.ts +10 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +88 -0
- package/dist/deployment-wait.d.ts +16 -0
- package/dist/deployment-wait.d.ts.map +1 -0
- package/dist/deployment-wait.js +31 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +624 -0
- package/dist/platform-auth.d.ts +4 -0
- package/dist/platform-auth.d.ts.map +1 -0
- package/dist/platform-auth.js +61 -0
- package/dist/presenter.d.ts +9 -0
- package/dist/presenter.d.ts.map +1 -0
- package/dist/presenter.js +106 -0
- package/dist/secret-input.d.ts +8 -0
- package/dist/secret-input.d.ts.map +1 -0
- package/dist/secret-input.js +33 -0
- package/dist/slug.d.ts +2 -0
- package/dist/slug.d.ts.map +1 -0
- package/dist/slug.js +10 -0
- package/llms.txt +17 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# oneclient
|
|
2
|
+
|
|
3
|
+
One package for the complete OneClient TypeScript SDK and its developer CLI.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install oneclient
|
|
7
|
+
npx oneclient login --email you@company.com
|
|
8
|
+
npx oneclient init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The same package works in code:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createClient } from "oneclient";
|
|
15
|
+
|
|
16
|
+
const one = createClient({
|
|
17
|
+
baseUrl: process.env.ONECLIENT_API_URL!,
|
|
18
|
+
serverKey: process.env.ONECLIENT_SERVER_KEY!,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const notes = await one.from("notes").select("id", "title").limit(20).execute();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Useful commands:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
oneclient status
|
|
28
|
+
oneclient projects
|
|
29
|
+
oneclient usage
|
|
30
|
+
oneclient deployments --environment env_...
|
|
31
|
+
oneclient models
|
|
32
|
+
oneclient billing:topup --usd 25
|
|
33
|
+
oneclient doctor
|
|
34
|
+
oneclient docs
|
|
35
|
+
oneclient mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Interactive terminals receive a compact, colored interface. Pipes and CI receive stable JSON by
|
|
39
|
+
default; `--json` makes that behavior explicit. Developer login credentials are stored in an
|
|
40
|
+
owner-only local file. `sk_*` and `dp_*` credentials stay in environment variables and are never
|
|
41
|
+
persisted by the CLI.
|
|
42
|
+
|
|
43
|
+
Documentation: [one-client.com/docs](https://one-client.com/docs) · AI context:
|
|
44
|
+
[one-client.com/llms.txt](https://one-client.com/llms.txt)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ArtifactManifest } from "@oneclient/contracts";
|
|
2
|
+
export interface CreateArtifactOptions {
|
|
3
|
+
directory: string;
|
|
4
|
+
archivePath: string;
|
|
5
|
+
manifestPath: string;
|
|
6
|
+
framework: ArtifactManifest["framework"];
|
|
7
|
+
rootDirectory?: string;
|
|
8
|
+
entrypoint?: string;
|
|
9
|
+
buildCommand?: string;
|
|
10
|
+
outputDirectory?: string;
|
|
11
|
+
lockfile?: ArtifactManifest["lockfile"];
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createArtifact(options: CreateArtifactOptions): Promise<ArtifactManifest>;
|
|
15
|
+
//# sourceMappingURL=artifact-create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-create.d.ts","sourceRoot":"","sources":["../src/artifact-create.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAOrF,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkDD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmF9F"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { lstat, mkdir, readdir, realpath, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { artifactManifestSchema } from "@oneclient/contracts";
|
|
6
|
+
import { create as createTar } from "tar";
|
|
7
|
+
const maximumFiles = 20_000;
|
|
8
|
+
const maximumFileBytes = 100 * 1024 * 1024;
|
|
9
|
+
const maximumTotalBytes = 500 * 1024 * 1024;
|
|
10
|
+
function artifactPath(root, absolute) {
|
|
11
|
+
const path = relative(root, absolute).split(sep).join("/");
|
|
12
|
+
if (!path ||
|
|
13
|
+
path.startsWith("/") ||
|
|
14
|
+
path.includes("\\") ||
|
|
15
|
+
path.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
16
|
+
path.length > 1024)
|
|
17
|
+
throw new Error(`Unsafe artifact path: ${path || absolute}`);
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
function validatedManifestPath(path, label) {
|
|
21
|
+
if (path === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
const normalized = path.split(sep).join("/");
|
|
24
|
+
if (normalized.startsWith("/") ||
|
|
25
|
+
normalized.includes("\\") ||
|
|
26
|
+
normalized.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
27
|
+
normalized.length > 1024)
|
|
28
|
+
throw new Error(`${label} must be a safe relative path`);
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
function ignoredPath(path) {
|
|
32
|
+
const parts = path.split("/");
|
|
33
|
+
if (parts.some((part) => [".git", ".next", ".open-next", ".turbo", ".wrangler", "node_modules"].includes(part)))
|
|
34
|
+
return true;
|
|
35
|
+
const name = parts.at(-1) ?? "";
|
|
36
|
+
return (((name === ".env" || name.startsWith(".env.")) && name !== ".env.example") ||
|
|
37
|
+
name === ".dev.vars" ||
|
|
38
|
+
name.startsWith(".dev.vars."));
|
|
39
|
+
}
|
|
40
|
+
async function sha256File(path) {
|
|
41
|
+
const hash = createHash("sha256");
|
|
42
|
+
for await (const chunk of createReadStream(path))
|
|
43
|
+
hash.update(chunk);
|
|
44
|
+
return hash.digest("hex");
|
|
45
|
+
}
|
|
46
|
+
export async function createArtifact(options) {
|
|
47
|
+
const rootDirectory = validatedManifestPath(options.rootDirectory, "rootDirectory");
|
|
48
|
+
const entrypoint = validatedManifestPath(options.entrypoint, "entrypoint");
|
|
49
|
+
const outputDirectory = validatedManifestPath(options.outputDirectory, "outputDirectory");
|
|
50
|
+
if (options.framework === "worker" && !entrypoint)
|
|
51
|
+
throw new Error("Worker artifacts require an entrypoint");
|
|
52
|
+
if (options.framework === "next-opennext" && !entrypoint)
|
|
53
|
+
throw new Error("OpenNext artifacts require a Worker entrypoint");
|
|
54
|
+
if ((options.framework === "vite" || options.framework === "next-opennext") && !outputDirectory)
|
|
55
|
+
throw new Error("Vite and OpenNext artifacts require an outputDirectory");
|
|
56
|
+
const root = await realpath(resolve(options.directory));
|
|
57
|
+
const archivePath = resolve(options.archivePath);
|
|
58
|
+
const manifestPath = resolve(options.manifestPath);
|
|
59
|
+
const excluded = new Set([archivePath, manifestPath]);
|
|
60
|
+
const files = [];
|
|
61
|
+
let totalBytes = 0;
|
|
62
|
+
async function walk(directory) {
|
|
63
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
64
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const absolute = resolve(directory, entry.name);
|
|
67
|
+
if (excluded.has(absolute))
|
|
68
|
+
continue;
|
|
69
|
+
const path = artifactPath(root, absolute);
|
|
70
|
+
if (ignoredPath(path))
|
|
71
|
+
continue;
|
|
72
|
+
const metadata = await lstat(absolute);
|
|
73
|
+
if (metadata.isSymbolicLink())
|
|
74
|
+
throw new Error(`Artifact contains a symbolic link: ${path}`);
|
|
75
|
+
if (metadata.isDirectory()) {
|
|
76
|
+
await walk(absolute);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!metadata.isFile())
|
|
80
|
+
throw new Error(`Artifact contains a special file: ${path}`);
|
|
81
|
+
if (metadata.size > maximumFileBytes)
|
|
82
|
+
throw new Error(`Artifact file exceeds 100 MiB: ${path}`);
|
|
83
|
+
if (files.length >= maximumFiles)
|
|
84
|
+
throw new Error("Artifact contains more than 20,000 files");
|
|
85
|
+
totalBytes += metadata.size;
|
|
86
|
+
if (totalBytes > maximumTotalBytes)
|
|
87
|
+
throw new Error("Artifact exceeds 500 MiB");
|
|
88
|
+
files.push({
|
|
89
|
+
path,
|
|
90
|
+
size: metadata.size,
|
|
91
|
+
sha256: await sha256File(absolute),
|
|
92
|
+
mode: (metadata.mode & 0o111) === 0 ? "file" : "executable",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await walk(root);
|
|
97
|
+
if (!files.length)
|
|
98
|
+
throw new Error("Artifact directory contains no deployable files");
|
|
99
|
+
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
100
|
+
await mkdir(dirname(archivePath), { recursive: true });
|
|
101
|
+
await createTar({
|
|
102
|
+
cwd: root,
|
|
103
|
+
file: archivePath,
|
|
104
|
+
follow: false,
|
|
105
|
+
gzip: false,
|
|
106
|
+
noMtime: true,
|
|
107
|
+
portable: true,
|
|
108
|
+
strict: true,
|
|
109
|
+
}, files.map((file) => file.path));
|
|
110
|
+
const manifest = artifactManifestSchema.parse({
|
|
111
|
+
version: 1,
|
|
112
|
+
framework: options.framework,
|
|
113
|
+
...(rootDirectory ? { rootDirectory } : {}),
|
|
114
|
+
...(entrypoint ? { entrypoint } : {}),
|
|
115
|
+
...(options.buildCommand ? { buildCommand: options.buildCommand } : {}),
|
|
116
|
+
...(outputDirectory ? { outputDirectory } : {}),
|
|
117
|
+
...(options.lockfile ? { lockfile: options.lockfile } : {}),
|
|
118
|
+
files,
|
|
119
|
+
totalBytes,
|
|
120
|
+
source: { kind: "cli" },
|
|
121
|
+
createdAt: options.createdAt ?? new Date().toISOString(),
|
|
122
|
+
signature: "0".repeat(64),
|
|
123
|
+
});
|
|
124
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
125
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, {
|
|
126
|
+
encoding: "utf8",
|
|
127
|
+
mode: 0o600,
|
|
128
|
+
});
|
|
129
|
+
return manifest;
|
|
130
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@oneclient/sdk";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ServerKeyScope } from "@oneclient/sdk";
|
|
2
|
+
export declare function deployTokenFromEnvironment(environment?: NodeJS.ProcessEnv): string | undefined;
|
|
3
|
+
export declare function serverKeyFromEnvironment(environment?: NodeJS.ProcessEnv): string | undefined;
|
|
4
|
+
export declare function parseServerKeyScopes(prefix: "pk" | "sk", value: string | undefined): ServerKeyScope[] | undefined;
|
|
5
|
+
//# sourceMappingURL=credential-routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-routing.d.ts","sourceRoot":"","sources":["../src/credential-routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,wBAAgB,0BAA0B,CACxC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,MAAM,GAAG,SAAS,CAGpB;AAYD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,GAAG,IAAI,EACnB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,cAAc,EAAE,GAAG,SAAS,CAc9B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function deployTokenFromEnvironment(environment = process.env) {
|
|
2
|
+
const token = environment.ONECLIENT_DEPLOY_TOKEN ?? environment.ONECLIENT_TOKEN;
|
|
3
|
+
return token?.startsWith("dp_") ? token : undefined;
|
|
4
|
+
}
|
|
5
|
+
export function serverKeyFromEnvironment(environment = process.env) {
|
|
6
|
+
const token = environment.ONECLIENT_SERVER_KEY ?? environment.ONECLIENT_TOKEN;
|
|
7
|
+
return token?.startsWith("sk_") ? token : undefined;
|
|
8
|
+
}
|
|
9
|
+
const serverKeyScopes = new Set([
|
|
10
|
+
"data:*",
|
|
11
|
+
"storage:*",
|
|
12
|
+
"kv:*",
|
|
13
|
+
"jobs:*",
|
|
14
|
+
"email:*",
|
|
15
|
+
"ai:*",
|
|
16
|
+
"analytics:*",
|
|
17
|
+
]);
|
|
18
|
+
export function parseServerKeyScopes(prefix, value) {
|
|
19
|
+
if (prefix === "pk") {
|
|
20
|
+
if (value)
|
|
21
|
+
throw new Error("pk keys do not accept server scopes");
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const scopes = [...new Set((value ?? "").split(",").map((scope) => scope.trim()))].filter(Boolean);
|
|
25
|
+
if (!scopes.length)
|
|
26
|
+
throw new Error("sk keys require --scopes with at least one scope");
|
|
27
|
+
for (const scope of scopes) {
|
|
28
|
+
if (!serverKeyScopes.has(scope))
|
|
29
|
+
throw new Error(`Unsupported server-key scope: ${scope}`);
|
|
30
|
+
}
|
|
31
|
+
return scopes;
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DeveloperCredentials {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
sessionToken: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function credentialsPath(environment?: NodeJS.ProcessEnv): string;
|
|
6
|
+
export declare function readDeveloperCredentials(path?: string): Promise<DeveloperCredentials | null>;
|
|
7
|
+
export declare function writeDeveloperCredentials(credentials: DeveloperCredentials, path?: string): Promise<void>;
|
|
8
|
+
export declare function clearDeveloperCredentials(path?: string): Promise<boolean>;
|
|
9
|
+
export declare function validateApiUrl(value: string): string;
|
|
10
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAIpF;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,SAAoB,GACvB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAwBtC;AAED,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,oBAAoB,EACjC,IAAI,SAAoB,GACvB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,yBAAyB,CAAC,IAAI,SAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ1F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWpD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
export function credentialsPath(environment = process.env) {
|
|
6
|
+
const configDirectory = environment.ONECLIENT_CONFIG_DIR ?? environment.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
7
|
+
return join(configDirectory, "oneclient", "credentials.json");
|
|
8
|
+
}
|
|
9
|
+
export async function readDeveloperCredentials(path = credentialsPath()) {
|
|
10
|
+
let contents;
|
|
11
|
+
try {
|
|
12
|
+
contents = await readFile(path, "utf8");
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (isMissingFile(error))
|
|
16
|
+
return null;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
if (process.platform !== "win32") {
|
|
20
|
+
const metadata = await stat(path);
|
|
21
|
+
if ((metadata.mode & 0o077) !== 0) {
|
|
22
|
+
throw new Error(`Refusing to read ${path}: credentials must not be accessible by group or other users`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const parsed = JSON.parse(contents);
|
|
26
|
+
if (typeof parsed.apiUrl !== "string" || typeof parsed.sessionToken !== "string") {
|
|
27
|
+
throw new Error(`Invalid OneClient credentials file: ${path}`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
apiUrl: validateApiUrl(parsed.apiUrl),
|
|
31
|
+
sessionToken: validateSessionToken(parsed.sessionToken),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function writeDeveloperCredentials(credentials, path = credentialsPath()) {
|
|
35
|
+
const normalized = {
|
|
36
|
+
apiUrl: validateApiUrl(credentials.apiUrl),
|
|
37
|
+
sessionToken: validateSessionToken(credentials.sessionToken),
|
|
38
|
+
};
|
|
39
|
+
const directory = dirname(path);
|
|
40
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
41
|
+
const temporaryPath = `${path}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
|
|
42
|
+
try {
|
|
43
|
+
await writeFile(temporaryPath, `${JSON.stringify(normalized, null, 2)}\n`, {
|
|
44
|
+
encoding: "utf8",
|
|
45
|
+
mode: 0o600,
|
|
46
|
+
flag: "wx",
|
|
47
|
+
});
|
|
48
|
+
await rename(temporaryPath, path);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function clearDeveloperCredentials(path = credentialsPath()) {
|
|
56
|
+
try {
|
|
57
|
+
await unlink(path);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (isMissingFile(error))
|
|
62
|
+
return false;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function validateApiUrl(value) {
|
|
67
|
+
const url = new URL(value);
|
|
68
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
69
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:")) {
|
|
70
|
+
throw new Error("The OneClient API URL must use HTTPS (HTTP is allowed only for localhost)");
|
|
71
|
+
}
|
|
72
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
73
|
+
throw new Error("The OneClient API URL cannot contain credentials, a query, or a fragment");
|
|
74
|
+
}
|
|
75
|
+
return url.toString().replace(/\/$/, "");
|
|
76
|
+
}
|
|
77
|
+
function validateSessionToken(value) {
|
|
78
|
+
const token = value.trim();
|
|
79
|
+
if (token.length < 16 || /\s/.test(token))
|
|
80
|
+
throw new Error("Invalid developer session token");
|
|
81
|
+
return token;
|
|
82
|
+
}
|
|
83
|
+
function isMissingFile(error) {
|
|
84
|
+
return (typeof error === "object" &&
|
|
85
|
+
error !== null &&
|
|
86
|
+
"code" in error &&
|
|
87
|
+
error.code === "ENOENT");
|
|
88
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type DeploymentBuildStatus = "uploading" | "queued" | "building" | "ready" | "failed" | "promoted" | "superseded";
|
|
2
|
+
export interface DeploymentStatusResult {
|
|
3
|
+
id: string;
|
|
4
|
+
status: DeploymentBuildStatus;
|
|
5
|
+
}
|
|
6
|
+
interface WaitForDeploymentOptions {
|
|
7
|
+
getStatus: () => Promise<DeploymentStatusResult>;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
pollIntervalMs?: number;
|
|
10
|
+
now?: () => number;
|
|
11
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function waitForDeployment(deploymentId: string, options: WaitForDeploymentOptions): Promise<DeploymentStatusResult>;
|
|
14
|
+
export declare function deploymentWaitTimeout(value: string): number;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=deployment-wait.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-wait.d.ts","sourceRoot":"","sources":["../src/deployment-wait.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAC/B,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAEvF,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,UAAU,wBAAwB;IAChC,SAAS,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAID,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,sBAAsB,CAAC,CA8BjC;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK3D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const pendingStatuses = new Set(["uploading", "queued", "building"]);
|
|
2
|
+
export async function waitForDeployment(deploymentId, options) {
|
|
3
|
+
if (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)
|
|
4
|
+
throw new TypeError("Deployment wait timeout must be a positive integer");
|
|
5
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
|
|
6
|
+
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs <= 0)
|
|
7
|
+
throw new TypeError("Deployment poll interval must be a positive integer");
|
|
8
|
+
const now = options.now ?? Date.now;
|
|
9
|
+
const sleep = options.sleep ??
|
|
10
|
+
((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
11
|
+
const deadline = now() + options.timeoutMs;
|
|
12
|
+
while (true) {
|
|
13
|
+
const deployment = await options.getStatus();
|
|
14
|
+
if (deployment.id !== deploymentId)
|
|
15
|
+
throw new Error(`Deployment status response did not match ${deploymentId}`);
|
|
16
|
+
if (deployment.status === "failed")
|
|
17
|
+
throw new Error(`Deployment ${deploymentId} failed; inspect it with oneclient deployment:logs`);
|
|
18
|
+
if (!pendingStatuses.has(deployment.status))
|
|
19
|
+
return deployment;
|
|
20
|
+
const remaining = deadline - now();
|
|
21
|
+
if (remaining <= 0)
|
|
22
|
+
throw new Error(`Timed out waiting for deployment ${deploymentId}; it may still finish in the background`);
|
|
23
|
+
await sleep(Math.min(pollIntervalMs, remaining));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function deploymentWaitTimeout(value) {
|
|
27
|
+
const seconds = Number(value);
|
|
28
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > 3_600)
|
|
29
|
+
throw new TypeError("--wait-timeout must be an integer from 1 to 3600 seconds");
|
|
30
|
+
return seconds * 1_000;
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|