@tinycloud-ai/tinycloud-cli 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.
- package/dist/main.js +79 -55
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -5,6 +5,11 @@ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync
|
|
|
5
5
|
import { basename, join as join5, resolve } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
|
|
8
|
+
// ../../packages/domain/src/cli.ts
|
|
9
|
+
var CLI_PACKAGE = "@tinycloud-ai/tinycloud-cli";
|
|
10
|
+
var CLI_VERSION = "0.1.2";
|
|
11
|
+
var CLI_SPEC = `${CLI_PACKAGE}@${CLI_VERSION}`;
|
|
12
|
+
|
|
8
13
|
// ../../packages/domain/src/ids.ts
|
|
9
14
|
var lastRandom = new Uint8Array(10);
|
|
10
15
|
|
|
@@ -15,6 +20,7 @@ var ERROR_CODES = {
|
|
|
15
20
|
AUTH_PROVIDER_REQUIRED: 501,
|
|
16
21
|
FORBIDDEN: 403,
|
|
17
22
|
NOT_FOUND: 404,
|
|
23
|
+
METHOD_NOT_ALLOWED: 405,
|
|
18
24
|
CONFLICT: 409,
|
|
19
25
|
PRECONDITION_FAILED: 412,
|
|
20
26
|
VALIDATION_FAILED: 422,
|
|
@@ -28,7 +34,6 @@ var ERROR_CODES = {
|
|
|
28
34
|
MANIFEST_UNSUPPORTED_API_VERSION: 422,
|
|
29
35
|
// Planning / provider
|
|
30
36
|
TARGET_INCOMPATIBLE: 422,
|
|
31
|
-
APPROVAL_REQUIRED: 409,
|
|
32
37
|
PLAN_EXPIRED: 409,
|
|
33
38
|
PLAN_STALE: 409,
|
|
34
39
|
PROVIDER_UNAVAILABLE: 503,
|
|
@@ -89,9 +94,9 @@ var RETRYABLE = /* @__PURE__ */ new Set([
|
|
|
89
94
|
]);
|
|
90
95
|
var DEFAULT_REMEDIATION = {
|
|
91
96
|
UNAUTHENTICATED: "Run `tiny login` and retry.",
|
|
97
|
+
METHOD_NOT_ALLOWED: "Use one of the methods named in the Allow response header.",
|
|
92
98
|
AUTH_PROVIDER_REQUIRED: "Configure a trusted OIDC provider for this gateway.",
|
|
93
99
|
FORBIDDEN: "Ask an organization admin for the required role.",
|
|
94
|
-
APPROVAL_REQUIRED: "Ask an organization admin to approve the plan, then deploy again with the approved plan ID.",
|
|
95
100
|
PLAN_STALE: "Run `tiny plan` again; the manifest or policy changed since this plan was produced.",
|
|
96
101
|
MIGRATION_CHECKSUM_CHANGED: "Restore the original migration file and add a new migration instead.",
|
|
97
102
|
INTERACTION_REQUIRED: "Re-run interactively, or pass the flag named in details.missingInput.",
|
|
@@ -121,8 +126,6 @@ function parseDuration(value) {
|
|
|
121
126
|
var DEFAULT_ORGANIZATION_POLICY = {
|
|
122
127
|
allowPublicApps: false,
|
|
123
128
|
allowRawSecrets: true,
|
|
124
|
-
requireApprovalForCapabilities: true,
|
|
125
|
-
requireApprovalForRawSecrets: true,
|
|
126
129
|
maxAppsPerOrg: 500,
|
|
127
130
|
maxPreviewTtlHours: 168,
|
|
128
131
|
maxMemoryMiB: 2048,
|
|
@@ -973,8 +976,14 @@ var DEFAULT_LIMITS = {
|
|
|
973
976
|
maxTotalBytes: 50 * 1024 * 1024,
|
|
974
977
|
maxFileBytes: 10 * 1024 * 1024
|
|
975
978
|
};
|
|
976
|
-
|
|
977
|
-
|
|
979
|
+
var BUILD_OUTPUT_IGNORES = DEFAULT_IGNORES.filter((pattern) => pattern !== "node_modules");
|
|
980
|
+
var BUILD_OUTPUT_LIMITS = {
|
|
981
|
+
maxFiles: 6e4,
|
|
982
|
+
maxTotalBytes: 400 * 1024 * 1024,
|
|
983
|
+
maxFileBytes: 50 * 1024 * 1024
|
|
984
|
+
};
|
|
985
|
+
function loadIgnores(root, base = DEFAULT_IGNORES) {
|
|
986
|
+
const patterns = [...base];
|
|
978
987
|
const file = join2(root, ".tinyignore");
|
|
979
988
|
if (existsSync(file)) {
|
|
980
989
|
for (const line of readFileSync2(file, "utf8").split("\n")) {
|
|
@@ -994,8 +1003,8 @@ function matches(pattern, path) {
|
|
|
994
1003
|
function escapeRegExp(value) {
|
|
995
1004
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
996
1005
|
}
|
|
997
|
-
function packSource(root, limits = DEFAULT_LIMITS) {
|
|
998
|
-
const ignores = loadIgnores(root);
|
|
1006
|
+
function packSource(root, limits = DEFAULT_LIMITS, baseIgnores = DEFAULT_IGNORES) {
|
|
1007
|
+
const ignores = loadIgnores(root, baseIgnores);
|
|
999
1008
|
const files = [];
|
|
1000
1009
|
const skippedSecrets = [];
|
|
1001
1010
|
let sizeBytes = 0;
|
|
@@ -1092,6 +1101,9 @@ var STORAGE_LIMITS = {
|
|
|
1092
1101
|
maxConcurrentOperationsPerEnvironment: 16
|
|
1093
1102
|
};
|
|
1094
1103
|
|
|
1104
|
+
// ../../packages/orchestrator/src/lifecycle.ts
|
|
1105
|
+
var MAX_DELETE_GRACE_MS = 90 * 864e5;
|
|
1106
|
+
|
|
1095
1107
|
// ../../packages/db/src/store.ts
|
|
1096
1108
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
1097
1109
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -1165,7 +1177,11 @@ var ApiClient = class {
|
|
|
1165
1177
|
return this.request("DELETE", path, query ? { query } : {});
|
|
1166
1178
|
}
|
|
1167
1179
|
uploadDirectory(directory) {
|
|
1168
|
-
return this.
|
|
1180
|
+
return this.uploadArtifact(createArtifactUpload(directory));
|
|
1181
|
+
}
|
|
1182
|
+
/** Upload a source tree the caller has already packed, from wherever it read it. */
|
|
1183
|
+
uploadArtifact(upload) {
|
|
1184
|
+
return this.post("/v1/artifacts", upload);
|
|
1169
1185
|
}
|
|
1170
1186
|
};
|
|
1171
1187
|
|
|
@@ -1252,6 +1268,16 @@ function failureRemediation(record) {
|
|
|
1252
1268
|
const remediation = record?.errorDetail?.remediation;
|
|
1253
1269
|
return typeof remediation === "string" && remediation !== "" ? remediation : "Read `tiny logs` for the failure, fix it, and deploy again.";
|
|
1254
1270
|
}
|
|
1271
|
+
function failureCode(record) {
|
|
1272
|
+
const code = record?.errorCode;
|
|
1273
|
+
return typeof code === "string" && code in ERROR_CODES ? code : "PROVIDER_UNAVAILABLE";
|
|
1274
|
+
}
|
|
1275
|
+
function failureLogs(record) {
|
|
1276
|
+
const details = record?.errorDetail?.details;
|
|
1277
|
+
if (typeof details !== "object" || details === null) return null;
|
|
1278
|
+
const logs = details.logs;
|
|
1279
|
+
return typeof logs === "string" && logs.trim() !== "" ? logs.replace(/\s+$/, "") : null;
|
|
1280
|
+
}
|
|
1255
1281
|
|
|
1256
1282
|
// ../cli/src/output.ts
|
|
1257
1283
|
var CLI_API_VERSION = "cli.tinycloud.dev/v1";
|
|
@@ -1277,7 +1303,6 @@ var EXIT_BY_CODE = {
|
|
|
1277
1303
|
MANIFEST_SCHEMA_INVALID: EXIT.validation,
|
|
1278
1304
|
MANIFEST_POLICY_VIOLATION: EXIT.policy,
|
|
1279
1305
|
MANIFEST_UNSUPPORTED_API_VERSION: EXIT.validation,
|
|
1280
|
-
APPROVAL_REQUIRED: EXIT.policy,
|
|
1281
1306
|
PLAN_STALE: EXIT.conflict,
|
|
1282
1307
|
PLAN_EXPIRED: EXIT.conflict,
|
|
1283
1308
|
TARGET_INCOMPATIBLE: EXIT.validation,
|
|
@@ -1388,7 +1413,7 @@ var USAGE = `tiny \u2014 governed runtime for small internal apps
|
|
|
1388
1413
|
tiny init [name] Write a starter tiny.yaml
|
|
1389
1414
|
tiny validate Parse and check tiny.yaml
|
|
1390
1415
|
tiny plan [--env production] Show what a deploy would do (never mutates)
|
|
1391
|
-
tiny deploy [--env production]
|
|
1416
|
+
tiny deploy [--env production] Deploy and watch it finish
|
|
1392
1417
|
[--no-wait] [--wait <seconds>]
|
|
1393
1418
|
tiny status Show apps and their lifecycle status
|
|
1394
1419
|
tiny logs [app] [--since 1h] [--limit 100] Read bounded logs
|
|
@@ -1397,12 +1422,13 @@ var USAGE = `tiny \u2014 governed runtime for small internal apps
|
|
|
1397
1422
|
|
|
1398
1423
|
tiny access list|grant|revoke <subject> [--role user]
|
|
1399
1424
|
tiny secrets set <name>|list
|
|
1400
|
-
tiny capabilities list|
|
|
1425
|
+
tiny capabilities list|grant <operation> --connection <name>
|
|
1401
1426
|
tiny preview create [--ttl 72h]|delete <environment>
|
|
1402
1427
|
|
|
1403
1428
|
tiny archive [app] Snapshot and deactivate
|
|
1404
1429
|
tiny restore [app] Reactivate an archived app
|
|
1405
|
-
tiny delete [app] --confirm <slug>
|
|
1430
|
+
tiny delete [app] --confirm <slug> [--grace 7d]
|
|
1431
|
+
Schedule deletion (--grace 0 deletes now)
|
|
1406
1432
|
tiny doctor Check auth, manifest files, and target reachability
|
|
1407
1433
|
|
|
1408
1434
|
Global flags: --json --api <url> --token <token> --cwd <path> --idempotency-key <key>
|
|
@@ -1628,21 +1654,12 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1628
1654
|
environment: typeof args.flags.env === "string" ? args.flags.env : void 0
|
|
1629
1655
|
});
|
|
1630
1656
|
if (args.flags.json !== true) printPlan(output, planned.plan, planned.warnings);
|
|
1631
|
-
if (planned.plan.approvals.length > 0 && args.flags.yes !== true && !isInteractive()) {
|
|
1632
|
-
throw new TinyError("APPROVAL_REQUIRED", "This deployment needs approval and no terminal is attached.", {
|
|
1633
|
-
details: { approvals: planned.plan.approvals, planId: planned.plan.planId, nextActions: [
|
|
1634
|
-
"Ask an organization admin to approve the plan.",
|
|
1635
|
-
"Re-run `tiny deploy --yes` once approved."
|
|
1636
|
-
] }
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
1657
|
output.progress("Deploying\u2026");
|
|
1640
1658
|
const result = await client.post("/v1/apps:deploy", {
|
|
1641
1659
|
artifactUri: artifact.artifactUri,
|
|
1642
1660
|
manifest: source,
|
|
1643
1661
|
environment: typeof args.flags.env === "string" ? args.flags.env : void 0,
|
|
1644
|
-
planId: planned.plan.planId
|
|
1645
|
-
approve: args.flags.yes === true
|
|
1662
|
+
planId: planned.plan.planId
|
|
1646
1663
|
}, typeof args.flags["idempotency-key"] === "string" ? args.flags["idempotency-key"] : void 0);
|
|
1647
1664
|
const settled = args.flags["no-wait"] === true ? null : await awaitDeployment(
|
|
1648
1665
|
() => client.get(`/v1/deployments/${result.deploymentId}`),
|
|
@@ -1650,31 +1667,38 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1650
1667
|
);
|
|
1651
1668
|
const status = settled?.status ?? result.status;
|
|
1652
1669
|
const build = buildSummary(settled?.providerState);
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1670
|
+
const payload = {
|
|
1671
|
+
appId: result.appId,
|
|
1672
|
+
deploymentId: result.deploymentId,
|
|
1673
|
+
status,
|
|
1674
|
+
// Same reason the human output withholds it: nothing the caller
|
|
1675
|
+
// deployed is behind that URL.
|
|
1676
|
+
...status === "failed" ? {} : { url: result.url },
|
|
1677
|
+
warnings: result.warnings,
|
|
1678
|
+
...build ? { build } : {}
|
|
1679
|
+
};
|
|
1680
|
+
const report = status === "failed" ? (human) => {
|
|
1681
|
+
if (args.flags.json !== true) human();
|
|
1682
|
+
} : (human) => output.result(payload, human);
|
|
1683
|
+
report(() => {
|
|
1684
|
+
output.progress("");
|
|
1685
|
+
const built = describeBuild(build);
|
|
1686
|
+
if (built) output.step(true, built);
|
|
1687
|
+
if (settled === null) {
|
|
1688
|
+
output.progress(args.flags["no-wait"] === true ? `\u2192 Deployment ${result.deploymentId} is ${status}. Follow it with \`tiny logs\`.` : `\u2192 Deployment ${result.deploymentId} is still running after ${Math.round(waitTimeoutMs(args) / 1e3)}s. Follow it with \`tiny logs\`.`);
|
|
1689
|
+
} else {
|
|
1690
|
+
output.step(status === "ready", `Deployment ${result.deploymentId} is ${status}`);
|
|
1691
|
+
if (settled.errorCode) output.warn(`${settled.errorCode}: ${describeFailure(settled)}`);
|
|
1692
|
+
const logs = failureLogs(settled);
|
|
1693
|
+
if (logs) output.progress(`
|
|
1694
|
+
${logs}
|
|
1695
|
+
`);
|
|
1674
1696
|
}
|
|
1675
|
-
|
|
1697
|
+
if (status !== "failed") output.step(true, `URL: ${result.url}`);
|
|
1698
|
+
for (const warning of result.warnings) output.warn(warning);
|
|
1699
|
+
});
|
|
1676
1700
|
if (status === "failed") {
|
|
1677
|
-
throw new TinyError(
|
|
1701
|
+
throw new TinyError(failureCode(settled), `Deployment ${result.deploymentId} failed: ${describeFailure(settled)}`, {
|
|
1678
1702
|
// The deployer recorded why and what to do about it; repeating its
|
|
1679
1703
|
// own remediation beats a generic pointer at the logs.
|
|
1680
1704
|
remediation: failureRemediation(settled),
|
|
@@ -1812,10 +1836,10 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1812
1836
|
});
|
|
1813
1837
|
return;
|
|
1814
1838
|
}
|
|
1815
|
-
if (args.subcommand === "
|
|
1839
|
+
if (args.subcommand === "grant") {
|
|
1816
1840
|
const operation = args.positional[0];
|
|
1817
1841
|
const connection = typeof args.flags.connection === "string" ? args.flags.connection : null;
|
|
1818
|
-
if (!operation || !connection) throw usageError("tiny capabilities
|
|
1842
|
+
if (!operation || !connection) throw usageError("tiny capabilities grant <operation> --connection <name>");
|
|
1819
1843
|
const appRef = typeof args.flags.app === "string" ? args.flags.app : parseManifest(readManifest(cwd).source).normalized.metadata.name;
|
|
1820
1844
|
const app = await resolveApp(client, appRef);
|
|
1821
1845
|
const grant = await client.post(`/v1/apps/${app.id}/capability-grants`, {
|
|
@@ -1823,12 +1847,11 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1823
1847
|
operations: [operation]
|
|
1824
1848
|
});
|
|
1825
1849
|
output.result({ grantId: grant.id, status: grant.status, operation }, () => {
|
|
1826
|
-
output.step(
|
|
1827
|
-
if (grant.status === "pending") output.progress("An organization admin must approve it before the app can call the operation.");
|
|
1850
|
+
output.step(true, `${appRef} can now ${operation} on ${connection}.`);
|
|
1828
1851
|
});
|
|
1829
1852
|
return;
|
|
1830
1853
|
}
|
|
1831
|
-
throw usageError("tiny capabilities list|
|
|
1854
|
+
throw usageError("tiny capabilities list|grant");
|
|
1832
1855
|
}
|
|
1833
1856
|
// --------------------------------------------------------- preview
|
|
1834
1857
|
case "preview": {
|
|
@@ -1884,10 +1907,11 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1884
1907
|
details: { missingInput: "--confirm", expected: app.slug }
|
|
1885
1908
|
});
|
|
1886
1909
|
}
|
|
1887
|
-
const
|
|
1910
|
+
const grace = typeof args.flags.grace === "string" ? args.flags.grace : void 0;
|
|
1911
|
+
const scheduled = await client.del(`/v1/apps/${app.id}`, { confirm, grace });
|
|
1888
1912
|
output.result(
|
|
1889
|
-
{ appId: app.id, deleteAfter: scheduled.deleteAfter },
|
|
1890
|
-
() => output.step(true, `${app.slug} will be deleted after ${scheduled.deleteAfter}.`)
|
|
1913
|
+
{ appId: app.id, status: scheduled.status, deleteAfter: scheduled.deleteAfter },
|
|
1914
|
+
() => output.step(true, scheduled.status === "deleted" ? `${app.slug} deleted.` : `${app.slug} will be deleted after ${scheduled.deleteAfter}. Run "tiny restore ${app.slug}" to cancel.`)
|
|
1891
1915
|
);
|
|
1892
1916
|
return;
|
|
1893
1917
|
}
|
|
@@ -1922,7 +1946,7 @@ function printPlan(output, plan, warnings) {
|
|
|
1922
1946
|
output.progress(` risk: ${plan.riskTier}${plan.estimatedMonthlyCents === null ? "" : ` estimate: $${(plan.estimatedMonthlyCents / 100).toFixed(2)}/mo`}`);
|
|
1923
1947
|
for (const warning of [...warnings, ...plan.warnings]) output.warn(warning);
|
|
1924
1948
|
for (const problem of plan.incompatibilities) output.warn(`incompatible: ${problem}`);
|
|
1925
|
-
for (const
|
|
1949
|
+
for (const notice of plan.notices) output.warn(notice.detail);
|
|
1926
1950
|
output.progress("");
|
|
1927
1951
|
}
|
|
1928
1952
|
function usageError(usage) {
|
package/package.json
CHANGED