@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.
Files changed (2) hide show
  1. package/dist/main.js +79 -55
  2. 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
- function loadIgnores(root) {
977
- const patterns = [...DEFAULT_IGNORES];
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.post("/v1/artifacts", createArtifactUpload(directory));
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] [--yes] Deploy and watch it finish
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|request <operation> --connection <name>
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> Schedule deletion
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
- output.result(
1654
- {
1655
- appId: result.appId,
1656
- deploymentId: result.deploymentId,
1657
- status,
1658
- url: result.url,
1659
- warnings: result.warnings,
1660
- ...build ? { build } : {}
1661
- },
1662
- () => {
1663
- output.progress("");
1664
- const built = describeBuild(build);
1665
- if (built) output.step(true, built);
1666
- if (settled === null) {
1667
- 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\`.`);
1668
- } else {
1669
- output.step(status === "ready", `Deployment ${result.deploymentId} is ${status}`);
1670
- if (settled.errorCode) output.warn(`${settled.errorCode}: ${describeFailure(settled)}`);
1671
- }
1672
- output.step(true, `URL: ${result.url}`);
1673
- for (const warning of result.warnings) output.warn(warning);
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("PROVIDER_UNAVAILABLE", `Deployment ${result.deploymentId} failed: ${describeFailure(settled)}`, {
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 === "request") {
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 request <operation> --connection <name>");
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(grant.status === "approved", `Grant ${grant.id} is ${grant.status}`);
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|request");
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 scheduled = await client.del(`/v1/apps/${app.id}`, { confirm });
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 approval of plan.approvals) output.warn(`approval required \u2014 ${approval.code}: ${approval.detail}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinycloud-ai/tinycloud-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line client for deploying and managing governed, isolated internal apps on Tinycloud.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",