@pushto/cli 0.2.1 → 0.4.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.
Files changed (184) hide show
  1. package/dist/commands/__tests__/deploy-domain.test.d.ts +2 -0
  2. package/dist/commands/__tests__/deploy-domain.test.d.ts.map +1 -0
  3. package/dist/commands/__tests__/deploy-domain.test.js +41 -0
  4. package/dist/commands/__tests__/deploy-domain.test.js.map +1 -0
  5. package/dist/commands/__tests__/headless-smoke.test.d.ts +2 -0
  6. package/dist/commands/__tests__/headless-smoke.test.d.ts.map +1 -0
  7. package/dist/commands/__tests__/headless-smoke.test.js +110 -0
  8. package/dist/commands/__tests__/headless-smoke.test.js.map +1 -0
  9. package/dist/commands/auth.d.ts +2 -0
  10. package/dist/commands/auth.d.ts.map +1 -0
  11. package/dist/commands/auth.js +77 -0
  12. package/dist/commands/auth.js.map +1 -0
  13. package/dist/commands/cron.d.ts +2 -0
  14. package/dist/commands/cron.d.ts.map +1 -0
  15. package/dist/commands/cron.js +157 -0
  16. package/dist/commands/cron.js.map +1 -0
  17. package/dist/commands/db.d.ts +4 -0
  18. package/dist/commands/db.d.ts.map +1 -0
  19. package/dist/commands/db.js +63 -0
  20. package/dist/commands/db.js.map +1 -0
  21. package/dist/commands/deploy.d.ts +16 -1
  22. package/dist/commands/deploy.d.ts.map +1 -1
  23. package/dist/commands/deploy.js +230 -150
  24. package/dist/commands/deploy.js.map +1 -1
  25. package/dist/commands/doctor.d.ts.map +1 -1
  26. package/dist/commands/doctor.js +289 -54
  27. package/dist/commands/doctor.js.map +1 -1
  28. package/dist/commands/domain.d.ts.map +1 -1
  29. package/dist/commands/domain.js +52 -7
  30. package/dist/commands/domain.js.map +1 -1
  31. package/dist/commands/echo.d.ts +2 -0
  32. package/dist/commands/echo.d.ts.map +1 -0
  33. package/dist/commands/echo.js +227 -0
  34. package/dist/commands/echo.js.map +1 -0
  35. package/dist/commands/env.d.ts +3 -1
  36. package/dist/commands/env.d.ts.map +1 -1
  37. package/dist/commands/env.js +76 -50
  38. package/dist/commands/env.js.map +1 -1
  39. package/dist/commands/init.d.ts +3 -1
  40. package/dist/commands/init.d.ts.map +1 -1
  41. package/dist/commands/init.js +58 -11
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/commands/login.d.ts.map +1 -1
  44. package/dist/commands/login.js +50 -23
  45. package/dist/commands/login.js.map +1 -1
  46. package/dist/commands/logs.d.ts +3 -1
  47. package/dist/commands/logs.d.ts.map +1 -1
  48. package/dist/commands/logs.js +77 -21
  49. package/dist/commands/logs.js.map +1 -1
  50. package/dist/commands/mail.d.ts +2 -0
  51. package/dist/commands/mail.d.ts.map +1 -0
  52. package/dist/commands/mail.js +105 -0
  53. package/dist/commands/mail.js.map +1 -0
  54. package/dist/commands/open.d.ts.map +1 -1
  55. package/dist/commands/open.js +21 -1
  56. package/dist/commands/open.js.map +1 -1
  57. package/dist/commands/preview.d.ts +22 -0
  58. package/dist/commands/preview.d.ts.map +1 -0
  59. package/dist/commands/preview.js +223 -0
  60. package/dist/commands/preview.js.map +1 -0
  61. package/dist/commands/status.d.ts +3 -1
  62. package/dist/commands/status.d.ts.map +1 -1
  63. package/dist/commands/status.js +62 -12
  64. package/dist/commands/status.js.map +1 -1
  65. package/dist/commands/upgrade.d.ts.map +1 -1
  66. package/dist/commands/upgrade.js +73 -10
  67. package/dist/commands/upgrade.js.map +1 -1
  68. package/dist/commands/use.d.ts +2 -0
  69. package/dist/commands/use.d.ts.map +1 -0
  70. package/dist/commands/use.js +108 -0
  71. package/dist/commands/use.js.map +1 -0
  72. package/dist/commands/vault.d.ts +2 -0
  73. package/dist/commands/vault.d.ts.map +1 -0
  74. package/dist/commands/vault.js +119 -0
  75. package/dist/commands/vault.js.map +1 -0
  76. package/dist/commands/whoami.d.ts.map +1 -1
  77. package/dist/commands/whoami.js +12 -1
  78. package/dist/commands/whoami.js.map +1 -1
  79. package/dist/index.js +117 -14
  80. package/dist/index.js.map +1 -1
  81. package/dist/lib/__tests__/build-runner.test.d.ts +2 -0
  82. package/dist/lib/__tests__/build-runner.test.d.ts.map +1 -0
  83. package/dist/lib/__tests__/build-runner.test.js +119 -0
  84. package/dist/lib/__tests__/build-runner.test.js.map +1 -0
  85. package/dist/lib/__tests__/cron-tail.test.d.ts +2 -0
  86. package/dist/lib/__tests__/cron-tail.test.d.ts.map +1 -0
  87. package/dist/lib/__tests__/cron-tail.test.js +27 -0
  88. package/dist/lib/__tests__/cron-tail.test.js.map +1 -0
  89. package/dist/lib/__tests__/detect-static-framework.test.d.ts +2 -0
  90. package/dist/lib/__tests__/detect-static-framework.test.d.ts.map +1 -0
  91. package/dist/lib/__tests__/detect-static-framework.test.js +146 -0
  92. package/dist/lib/__tests__/detect-static-framework.test.js.map +1 -0
  93. package/dist/lib/__tests__/errors.test.d.ts +2 -0
  94. package/dist/lib/__tests__/errors.test.d.ts.map +1 -0
  95. package/dist/lib/__tests__/errors.test.js +56 -0
  96. package/dist/lib/__tests__/errors.test.js.map +1 -0
  97. package/dist/lib/__tests__/interactive.test.d.ts +2 -0
  98. package/dist/lib/__tests__/interactive.test.d.ts.map +1 -0
  99. package/dist/lib/__tests__/interactive.test.js +79 -0
  100. package/dist/lib/__tests__/interactive.test.js.map +1 -0
  101. package/dist/lib/__tests__/output.test.d.ts +2 -0
  102. package/dist/lib/__tests__/output.test.d.ts.map +1 -0
  103. package/dist/lib/__tests__/output.test.js +144 -0
  104. package/dist/lib/__tests__/output.test.js.map +1 -0
  105. package/dist/lib/__tests__/prepare-artifact.test.d.ts +2 -0
  106. package/dist/lib/__tests__/prepare-artifact.test.d.ts.map +1 -0
  107. package/dist/lib/__tests__/prepare-artifact.test.js +55 -0
  108. package/dist/lib/__tests__/prepare-artifact.test.js.map +1 -0
  109. package/dist/lib/__tests__/preview-args.test.d.ts +2 -0
  110. package/dist/lib/__tests__/preview-args.test.d.ts.map +1 -0
  111. package/dist/lib/__tests__/preview-args.test.js +37 -0
  112. package/dist/lib/__tests__/preview-args.test.js.map +1 -0
  113. package/dist/lib/api.d.ts.map +1 -1
  114. package/dist/lib/api.js +22 -4
  115. package/dist/lib/api.js.map +1 -1
  116. package/dist/lib/ask.d.ts +15 -0
  117. package/dist/lib/ask.d.ts.map +1 -0
  118. package/dist/lib/ask.js +24 -0
  119. package/dist/lib/ask.js.map +1 -0
  120. package/dist/lib/build-runner.d.ts +25 -0
  121. package/dist/lib/build-runner.d.ts.map +1 -0
  122. package/dist/lib/build-runner.js +141 -0
  123. package/dist/lib/build-runner.js.map +1 -0
  124. package/dist/lib/config.d.ts +6 -0
  125. package/dist/lib/config.d.ts.map +1 -1
  126. package/dist/lib/config.js +7 -1
  127. package/dist/lib/config.js.map +1 -1
  128. package/dist/lib/cron-tail.d.ts +10 -0
  129. package/dist/lib/cron-tail.d.ts.map +1 -0
  130. package/dist/lib/cron-tail.js +5 -0
  131. package/dist/lib/cron-tail.js.map +1 -0
  132. package/dist/lib/detect-static-framework.d.ts +31 -0
  133. package/dist/lib/detect-static-framework.d.ts.map +1 -0
  134. package/dist/lib/detect-static-framework.js +96 -0
  135. package/dist/lib/detect-static-framework.js.map +1 -0
  136. package/dist/lib/env-parse.d.ts +48 -0
  137. package/dist/lib/env-parse.d.ts.map +1 -0
  138. package/dist/lib/env-parse.js +91 -0
  139. package/dist/lib/env-parse.js.map +1 -0
  140. package/dist/lib/errors.d.ts +111 -0
  141. package/dist/lib/errors.d.ts.map +1 -0
  142. package/dist/lib/errors.js +64 -0
  143. package/dist/lib/errors.js.map +1 -0
  144. package/dist/lib/file-walker.d.ts +23 -0
  145. package/dist/lib/file-walker.d.ts.map +1 -0
  146. package/dist/lib/file-walker.js +114 -0
  147. package/dist/lib/file-walker.js.map +1 -0
  148. package/dist/lib/interactive.d.ts +13 -0
  149. package/dist/lib/interactive.d.ts.map +1 -0
  150. package/dist/lib/interactive.js +24 -0
  151. package/dist/lib/interactive.js.map +1 -0
  152. package/dist/lib/output.d.ts +24 -0
  153. package/dist/lib/output.d.ts.map +1 -0
  154. package/dist/lib/output.js +71 -0
  155. package/dist/lib/output.js.map +1 -0
  156. package/dist/lib/prepare-artifact.d.ts +22 -0
  157. package/dist/lib/prepare-artifact.d.ts.map +1 -0
  158. package/dist/lib/prepare-artifact.js +26 -0
  159. package/dist/lib/prepare-artifact.js.map +1 -0
  160. package/dist/lib/preview-args.d.ts +20 -0
  161. package/dist/lib/preview-args.d.ts.map +1 -0
  162. package/dist/lib/preview-args.js +19 -0
  163. package/dist/lib/preview-args.js.map +1 -0
  164. package/dist/lib/safe-json.d.ts +7 -2
  165. package/dist/lib/safe-json.d.ts.map +1 -1
  166. package/dist/lib/safe-json.js +23 -4
  167. package/dist/lib/safe-json.js.map +1 -1
  168. package/dist/lib/update-check.d.ts +17 -0
  169. package/dist/lib/update-check.d.ts.map +1 -0
  170. package/dist/lib/update-check.js +81 -0
  171. package/dist/lib/update-check.js.map +1 -0
  172. package/dist/lib/upload-limits.d.ts +17 -0
  173. package/dist/lib/upload-limits.d.ts.map +1 -0
  174. package/dist/lib/upload-limits.js +17 -0
  175. package/dist/lib/upload-limits.js.map +1 -0
  176. package/dist/lib/zip-project.d.ts +19 -0
  177. package/dist/lib/zip-project.d.ts.map +1 -0
  178. package/dist/lib/zip-project.js +39 -0
  179. package/dist/lib/zip-project.js.map +1 -0
  180. package/dist/version.d.ts +2 -0
  181. package/dist/version.d.ts.map +1 -0
  182. package/dist/version.js +16 -0
  183. package/dist/version.js.map +1 -0
  184. package/package.json +5 -8
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Parse a .env file's content into a key/value record.
3
+ *
4
+ * Every surface that accepts a .env file speaks through this parser:
5
+ * `pushto deploy` (auto-import on every deploy), `pushto env push`, and the
6
+ * dashboard's import box + raw editor. Pre-fix the dashboard hand-rolled its
7
+ * own parse and the two drifted — `export KEY=value` lines (copied from a
8
+ * .bashrc) were rejected in the dashboard, and inline comments were stored
9
+ * INTO the value (`PORT=3000 # default port` saved a value of
10
+ * `3000 # default port`).
11
+ *
12
+ * This file exists twice, byte-identical: apps/cli/src/lib/env-parse.ts and
13
+ * apps/web/lib/env-parse.ts. The web app can't import from apps/cli, and the
14
+ * published CLI deliberately carries no workspace deps — a vitest pin
15
+ * (apps/web/lib/__tests__/env-parse.test.ts) keeps the copies in sync
16
+ * instead. Edit one, copy to the other, or the pin fails.
17
+ *
18
+ * Supports:
19
+ * - bare assignments: KEY=value
20
+ * - `export` prefix: export KEY=value ← copied from .bashrc
21
+ * - quoted values: KEY="value with spaces" KEY='single quoted'
22
+ * - inline trailing comment: PORT=3000 # default port (only when value is unquoted)
23
+ * - blank lines + #-comments
24
+ *
25
+ * Does NOT support (and pushto's server-side validator would reject):
26
+ * - multi-line quoted values
27
+ * - escape sequences inside double quotes (treated literally)
28
+ */
29
+ /**
30
+ * Full parse: valid pairs plus a per-line skip list with reasons, so
31
+ * consumers can tell the user WHICH lines didn't make it and why instead
32
+ * of importing silently-fewer vars than the file holds.
33
+ */
34
+ export const parseEnvContent = (content) => {
35
+ const vars = {};
36
+ const skipped = [];
37
+ const lines = content.split('\n');
38
+ for (let i = 0; i < lines.length; i++) {
39
+ let trimmed = lines[i].trim();
40
+ if (!trimmed || trimmed.startsWith('#'))
41
+ continue;
42
+ // Strip the bash-export prefix that people copy from their shell or
43
+ // .bashrc (`export DATABASE_URL=postgres://...`). Pre-fix this parsed
44
+ // as key=`export DATABASE_URL` which the server rejects on the
45
+ // [A-Z_] validator — DATABASE_URL silently never made it remote
46
+ // despite the success message claiming X vars imported.
47
+ if (trimmed.startsWith('export ')) {
48
+ trimmed = trimmed.slice(7).trim();
49
+ }
50
+ const eqIdx = trimmed.indexOf('=');
51
+ if (eqIdx === -1) {
52
+ skipped.push({ key: `line ${i + 1}`, reason: 'no `=` separator' });
53
+ continue;
54
+ }
55
+ const key = trimmed.slice(0, eqIdx).trim();
56
+ let value = trimmed.slice(eqIdx + 1).trim();
57
+ // Strip inline comments AFTER the value (only when not inside quotes).
58
+ // Common pattern: `PORT=3000 # default port`. The space-before-hash
59
+ // requirement avoids eating fragments of values that legitimately
60
+ // contain # (e.g. URLs with fragments, color codes like #ff0000 in
61
+ // some configs).
62
+ if (!value.startsWith('"') && !value.startsWith("'")) {
63
+ const hashIdx = value.indexOf('#');
64
+ if (hashIdx > 0 && /\s/.test(value[hashIdx - 1] ?? '')) {
65
+ value = value.slice(0, hashIdx).trim();
66
+ }
67
+ }
68
+ // Strip surrounding quotes (matched pair only; a lone quote stays).
69
+ if (value.length >= 2 &&
70
+ ((value.startsWith('"') && value.endsWith('"')) ||
71
+ (value.startsWith("'") && value.endsWith("'")))) {
72
+ value = value.slice(1, -1);
73
+ }
74
+ if (!key) {
75
+ skipped.push({ key: `line ${i + 1}`, reason: 'missing key before `=`' });
76
+ continue;
77
+ }
78
+ if (!value) {
79
+ skipped.push({ key, reason: 'empty value' });
80
+ continue;
81
+ }
82
+ vars[key] = value;
83
+ }
84
+ return { vars, skipped };
85
+ };
86
+ /**
87
+ * Record-only view for callers that don't surface skip reasons.
88
+ * Same parse, skips dropped.
89
+ */
90
+ export const parseEnvFile = (content) => parseEnvContent(content).vars;
91
+ //# sourceMappingURL=env-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-parse.js","sourceRoot":"","sources":["../../src/lib/env-parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import type { PushtoError, PushtoErrorFix, PushtoErrorStage } from '@pushto/shared/errors';
2
+ /**
3
+ * Exit bands. A caller that reads only $? still behaves correctly, because
4
+ * the band alone says what to do next.
5
+ */
6
+ export declare const EXIT: {
7
+ readonly OK: 0;
8
+ /** reserved for genuinely unclassified failures — never reachable from a known code */
9
+ readonly UNCLASSIFIED: 1;
10
+ readonly USAGE: 2;
11
+ readonly AUTH: 10;
12
+ readonly NOT_FOUND: 20;
13
+ readonly INVALID_INPUT: 30;
14
+ readonly BUILD_FAILED: 40;
15
+ readonly QUOTA: 50;
16
+ readonly UPSTREAM: 60;
17
+ };
18
+ /**
19
+ * The closed registry. Adding a code here is the ONLY way to add one, which
20
+ * is what stops a call site from inventing an exit number under time
21
+ * pressure — the failure mode that produced 112 identical exit(1)s.
22
+ */
23
+ export declare const CODES: {
24
+ readonly NOT_AUTHENTICATED: {
25
+ readonly band: 10;
26
+ readonly retryable: false;
27
+ };
28
+ readonly SESSION_EXPIRED: {
29
+ readonly band: 10;
30
+ readonly retryable: false;
31
+ };
32
+ readonly NO_PROJECT: {
33
+ readonly band: 20;
34
+ readonly retryable: false;
35
+ };
36
+ readonly PROJECT_NOT_FOUND: {
37
+ readonly band: 20;
38
+ readonly retryable: false;
39
+ };
40
+ readonly NO_DEPLOYMENT: {
41
+ readonly band: 20;
42
+ readonly retryable: false;
43
+ };
44
+ readonly BAD_USAGE: {
45
+ readonly band: 2;
46
+ readonly retryable: false;
47
+ };
48
+ readonly MISSING_ENTRYPOINT: {
49
+ readonly band: 30;
50
+ readonly retryable: false;
51
+ };
52
+ readonly NO_FILES: {
53
+ readonly band: 30;
54
+ readonly retryable: false;
55
+ };
56
+ readonly ARTIFACT_TOO_LARGE: {
57
+ readonly band: 30;
58
+ readonly retryable: false;
59
+ };
60
+ readonly INVALID_ENV_KEY: {
61
+ readonly band: 30;
62
+ readonly retryable: false;
63
+ };
64
+ readonly DB_ALREADY_EXISTS: {
65
+ readonly band: 30;
66
+ readonly retryable: false;
67
+ };
68
+ readonly BUILD_FAILED: {
69
+ readonly band: 40;
70
+ readonly retryable: false;
71
+ };
72
+ readonly CONTAINER_BOOT_FAILED: {
73
+ readonly band: 40;
74
+ readonly retryable: false;
75
+ };
76
+ readonly PAID_FEATURE: {
77
+ readonly band: 50;
78
+ readonly retryable: false;
79
+ };
80
+ readonly QUOTA_EXCEEDED: {
81
+ readonly band: 50;
82
+ readonly retryable: false;
83
+ };
84
+ readonly NETWORK: {
85
+ readonly band: 60;
86
+ readonly retryable: true;
87
+ };
88
+ readonly SERVER_ERROR: {
89
+ readonly band: 60;
90
+ readonly retryable: true;
91
+ };
92
+ readonly DB_PROVISION_FAILED: {
93
+ readonly band: 60;
94
+ readonly retryable: true;
95
+ };
96
+ };
97
+ export type PushtoErrorCode = keyof typeof CODES;
98
+ export declare const exitCodeFor: (code: PushtoErrorCode) => number;
99
+ export declare const isRetryable: (code: PushtoErrorCode) => boolean;
100
+ /**
101
+ * Build an error carrying the retryable bit derived from its code, so a call
102
+ * site can never disagree with the registry about whether a retry is sane.
103
+ * Optional keys are omitted rather than set to undefined, so JSON.stringify
104
+ * produces a clean payload.
105
+ */
106
+ export declare const buildError: (code: PushtoErrorCode, human: string, extra?: {
107
+ fix?: PushtoErrorFix;
108
+ stage?: PushtoErrorStage;
109
+ details?: Record<string, unknown>;
110
+ }) => PushtoError;
111
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE3F;;;GAGG;AACH,eAAO,MAAM,IAAI;;IAEf,uFAAuF;;;;;;;;;CAS/E,CAAC;AAOX;;;;GAIG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuB2B,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,KAAK,CAAC;AAEjD,eAAO,MAAM,WAAW,GAAI,MAAM,eAAe,KAAG,MAA0B,CAAC;AAE/E,eAAO,MAAM,WAAW,GAAI,MAAM,eAAe,KAAG,OAAgC,CAAC;AAErF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,eAAe,EACrB,OAAO,MAAM,EACb,QAAQ;IAAE,GAAG,CAAC,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KAC5F,WAMF,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Exit bands. A caller that reads only $? still behaves correctly, because
3
+ * the band alone says what to do next.
4
+ */
5
+ export const EXIT = {
6
+ OK: 0,
7
+ /** reserved for genuinely unclassified failures — never reachable from a known code */
8
+ UNCLASSIFIED: 1,
9
+ USAGE: 2,
10
+ AUTH: 10,
11
+ NOT_FOUND: 20,
12
+ INVALID_INPUT: 30,
13
+ BUILD_FAILED: 40,
14
+ QUOTA: 50,
15
+ UPSTREAM: 60,
16
+ };
17
+ /**
18
+ * The closed registry. Adding a code here is the ONLY way to add one, which
19
+ * is what stops a call site from inventing an exit number under time
20
+ * pressure — the failure mode that produced 112 identical exit(1)s.
21
+ */
22
+ export const CODES = {
23
+ NOT_AUTHENTICATED: { band: EXIT.AUTH, retryable: false },
24
+ SESSION_EXPIRED: { band: EXIT.AUTH, retryable: false },
25
+ NO_PROJECT: { band: EXIT.NOT_FOUND, retryable: false },
26
+ PROJECT_NOT_FOUND: { band: EXIT.NOT_FOUND, retryable: false },
27
+ NO_DEPLOYMENT: { band: EXIT.NOT_FOUND, retryable: false },
28
+ BAD_USAGE: { band: EXIT.USAGE, retryable: false },
29
+ MISSING_ENTRYPOINT: { band: EXIT.INVALID_INPUT, retryable: false },
30
+ NO_FILES: { band: EXIT.INVALID_INPUT, retryable: false },
31
+ ARTIFACT_TOO_LARGE: { band: EXIT.INVALID_INPUT, retryable: false },
32
+ INVALID_ENV_KEY: { band: EXIT.INVALID_INPUT, retryable: false },
33
+ // The request was well-formed but wrong for the current state: the project
34
+ // already has a live database. Retrying identically cannot succeed, and the
35
+ // caller's move is to use the existing one, so this sits with invalid input
36
+ // rather than in the upstream/retryable band.
37
+ DB_ALREADY_EXISTS: { band: EXIT.INVALID_INPUT, retryable: false },
38
+ BUILD_FAILED: { band: EXIT.BUILD_FAILED, retryable: false },
39
+ CONTAINER_BOOT_FAILED: { band: EXIT.BUILD_FAILED, retryable: false },
40
+ PAID_FEATURE: { band: EXIT.QUOTA, retryable: false },
41
+ QUOTA_EXCEEDED: { band: EXIT.QUOTA, retryable: false },
42
+ NETWORK: { band: EXIT.UPSTREAM, retryable: true },
43
+ SERVER_ERROR: { band: EXIT.UPSTREAM, retryable: true },
44
+ DB_PROVISION_FAILED: { band: EXIT.UPSTREAM, retryable: true },
45
+ };
46
+ export const exitCodeFor = (code) => CODES[code].band;
47
+ export const isRetryable = (code) => CODES[code].retryable;
48
+ /**
49
+ * Build an error carrying the retryable bit derived from its code, so a call
50
+ * site can never disagree with the registry about whether a retry is sane.
51
+ * Optional keys are omitted rather than set to undefined, so JSON.stringify
52
+ * produces a clean payload.
53
+ */
54
+ export const buildError = (code, human, extra) => {
55
+ const err = { code, human, retryable: isRetryable(code) };
56
+ if (extra?.fix)
57
+ err.fix = extra.fix;
58
+ if (extra?.stage)
59
+ err.stage = extra.stage;
60
+ if (extra?.details)
61
+ err.details = extra.details;
62
+ return err;
63
+ };
64
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type Ignore } from 'ignore';
2
+ export declare const ALWAYS_IGNORE: string[];
3
+ export declare const loadIgnoreFilter: (rootDir: string) => Ignore;
4
+ export interface WalkedFile {
5
+ /** Absolute filesystem path. */
6
+ fullPath: string;
7
+ /** Path relative to rootDir, with forward-slash separators (zip-archive style). */
8
+ archivePath: string;
9
+ /** File size in bytes. */
10
+ sizeBytes: number;
11
+ }
12
+ /**
13
+ * Walk a project directory using the same ignore semantics deploy.ts uses
14
+ * to build the deploy zip. Skips symlinks (traversal protection) and
15
+ * caps depth at 20.
16
+ *
17
+ * Both deploy.ts (zip the matched files) and doctor.ts (preview the
18
+ * matched files: count, total size, biggest) call this. If the two ever
19
+ * disagreed, doctor's "looks fine" would mismatch deploy's "exceeds limit"
20
+ * — frustrating to debug. One source = one truth.
21
+ */
22
+ export declare const walkProject: (rootDir: string) => WalkedFile[];
23
+ //# sourceMappingURL=file-walker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-walker.d.ts","sourceRoot":"","sources":["../../src/lib/file-walker.ts"],"names":[],"mappings":"AAEA,OAAe,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAY7C,eAAO,MAAM,aAAa,UA0BzB,CAAC;AAWF,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,MAclD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,UAAU,EAyCvD,CAAC"}
@@ -0,0 +1,114 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import ignore from 'ignore';
4
+ // Always-exclude footguns: things that should never be deployed regardless of
5
+ // what the user's gitignore says. Catches secrets, pushto-internal files, and
6
+ // universally non-deployable build/cache artifacts even if the user forgot
7
+ // to gitignore them. Patterns without a slash match at any directory level
8
+ // per gitignore semantics — so `.venv` here also catches `sdks/python/.venv`
9
+ // (the chartmuse-engine case that had us shipping a 76MB zip).
10
+ //
11
+ // Conservative: only includes things that are NEVER the deployable artifact.
12
+ // `dist`, `build`, `out` are intentionally NOT here — for static sites those
13
+ // ARE what gets deployed.
14
+ export const ALWAYS_IGNORE = [
15
+ // pushto-internal + secrets
16
+ '.git',
17
+ '.pushto',
18
+ '.env',
19
+ '.env.local',
20
+ '.env.*.local',
21
+ '.DS_Store',
22
+ // Node
23
+ 'node_modules',
24
+ '.next',
25
+ '.vercel',
26
+ '.turbo',
27
+ // Python — venvs (~hundreds of MB), bytecode (regenerated on import),
28
+ // and test/type-check caches. None of these are ever deployed.
29
+ '.venv',
30
+ 'venv',
31
+ '__pycache__',
32
+ '*.pyc',
33
+ '.pytest_cache',
34
+ '.mypy_cache',
35
+ '.ruff_cache',
36
+ // Rust / Java / generic build dirs — multi-GB build output. Source-only
37
+ // deploys; if the user wants to ship a prebuilt binary, they put it
38
+ // somewhere other than `target/`.
39
+ 'target',
40
+ ];
41
+ // Build an ignore filter that combines ALWAYS_IGNORE with the project's own
42
+ // .gitignore and an optional .pushtoignore. Both files use gitignore syntax
43
+ // (negations, glob patterns, dir-only `foo/` markers).
44
+ //
45
+ // This is what unblocks non-Node projects: every Rust project has `target/`
46
+ // in .gitignore, every Python project has `__pycache__/` and `.venv/`, every
47
+ // Go project has `bin/`. Without reading gitignore, the zipper walked into
48
+ // those and either hit the 500MB cap or ground to a halt on tens of
49
+ // thousands of intermediate files.
50
+ export const loadIgnoreFilter = (rootDir) => {
51
+ const ig = ignore().add(ALWAYS_IGNORE);
52
+ const gitignorePath = path.join(rootDir, '.gitignore');
53
+ if (fs.existsSync(gitignorePath)) {
54
+ ig.add(fs.readFileSync(gitignorePath, 'utf-8'));
55
+ }
56
+ const pushtoignorePath = path.join(rootDir, '.pushtoignore');
57
+ if (fs.existsSync(pushtoignorePath)) {
58
+ ig.add(fs.readFileSync(pushtoignorePath, 'utf-8'));
59
+ }
60
+ return ig;
61
+ };
62
+ /**
63
+ * Walk a project directory using the same ignore semantics deploy.ts uses
64
+ * to build the deploy zip. Skips symlinks (traversal protection) and
65
+ * caps depth at 20.
66
+ *
67
+ * Both deploy.ts (zip the matched files) and doctor.ts (preview the
68
+ * matched files: count, total size, biggest) call this. If the two ever
69
+ * disagreed, doctor's "looks fine" would mismatch deploy's "exceeds limit"
70
+ * — frustrating to debug. One source = one truth.
71
+ */
72
+ export const walkProject = (rootDir) => {
73
+ const ig = loadIgnoreFilter(rootDir);
74
+ const files = [];
75
+ const MAX_DEPTH = 20;
76
+ const recurse = (dirPath, prefix, depth) => {
77
+ if (depth > MAX_DEPTH)
78
+ return;
79
+ let entries;
80
+ try {
81
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
82
+ }
83
+ catch {
84
+ return;
85
+ }
86
+ for (const entry of entries) {
87
+ if (entry.isSymbolicLink())
88
+ continue;
89
+ const archivePath = prefix ? `${prefix}/${entry.name}` : entry.name;
90
+ // Gitignore syntax: directory-only patterns (`target/`) match only when
91
+ // the test path ends in a slash. Always append `/` for directories so
92
+ // both file and dir patterns resolve correctly.
93
+ const testPath = entry.isDirectory() ? `${archivePath}/` : archivePath;
94
+ if (ig.ignores(testPath))
95
+ continue;
96
+ const fullPath = path.join(dirPath, entry.name);
97
+ if (entry.isFile()) {
98
+ try {
99
+ const stat = fs.statSync(fullPath);
100
+ files.push({ fullPath, archivePath, sizeBytes: stat.size });
101
+ }
102
+ catch {
103
+ // skip unreadable files
104
+ }
105
+ }
106
+ else if (entry.isDirectory()) {
107
+ recurse(fullPath, archivePath, depth + 1);
108
+ }
109
+ }
110
+ };
111
+ recurse(rootDir, '', 0);
112
+ return files;
113
+ };
114
+ //# sourceMappingURL=file-walker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-walker.js","sourceRoot":"","sources":["../../src/lib/file-walker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The single source of truth for whether a command may block on a human.
3
+ *
4
+ * No command reads process.stdin.isTTY itself — a guard in
5
+ * apps/web/scripts/guard-cli-interactive.mjs fails the build if one does.
6
+ * That centralisation is the whole point: the deploy hang existed because
7
+ * one call site made this decision privately and got it wrong.
8
+ */
9
+ export declare const canPrompt: (flags?: {
10
+ json?: boolean;
11
+ yes?: boolean;
12
+ }) => boolean;
13
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/lib/interactive.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,eAAO,MAAM,SAAS,GAAI,QAAQ;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,KAAG,OAIrE,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The single source of truth for whether a command may block on a human.
3
+ *
4
+ * No command reads process.stdin.isTTY itself — a guard in
5
+ * apps/web/scripts/guard-cli-interactive.mjs fails the build if one does.
6
+ * That centralisation is the whole point: the deploy hang existed because
7
+ * one call site made this decision privately and got it wrong.
8
+ */
9
+ /** "", "0" and "false" all mean not-set, matching shell convention. */
10
+ const envFlagSet = (name) => {
11
+ const raw = process.env[name];
12
+ if (!raw)
13
+ return false;
14
+ const v = raw.toLowerCase();
15
+ return v !== '0' && v !== 'false';
16
+ };
17
+ export const canPrompt = (flags) => {
18
+ if (flags?.json || flags?.yes)
19
+ return false;
20
+ if (envFlagSet('CI') || envFlagSet('PUSHTO_NONINTERACTIVE'))
21
+ return false;
22
+ return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
23
+ };
24
+ //# sourceMappingURL=interactive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive.js","sourceRoot":"","sources":["../../src/lib/interactive.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uEAAuE;AACvE,MAAM,UAAU,GAAG,CAAC,IAAY,EAAW,EAAE;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,MAAM,CAAC,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,OAAO,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAyC,EAAW,EAAE;IAC9E,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,GAAG;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,uBAAuB,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1E,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { PushtoError } from '@pushto/shared/errors';
2
+ import { buildError, type PushtoErrorCode } from './errors.js';
3
+ export interface OutputOpts {
4
+ json?: boolean;
5
+ }
6
+ /**
7
+ * Under --json, stdout carries exactly one JSON object and nothing else, so
8
+ * `pushto deploy --json | jq` works unfiltered. Everything a human would
9
+ * enjoy — spinners, colour, progress — goes to stderr instead.
10
+ *
11
+ * lib/api.ts already wrote its session-expired notice to stderr to keep user
12
+ * pipes clean; this module finishes that discipline for the whole spine.
13
+ */
14
+ /** Human-only progress. Never stdout; in json mode it still goes to stderr. */
15
+ export declare const progress: (line: string, opts: OutputOpts) => void;
16
+ /** ora writes to stdout by default. In json mode that would corrupt the payload. */
17
+ export declare const spinnerStream: (opts: OutputOpts) => NodeJS.WriteStream;
18
+ export declare const emitOk: (payload: Record<string, unknown> & {
19
+ human?: string;
20
+ }, opts: OutputOpts) => void;
21
+ export declare const emitErr: (err: PushtoError, opts: OutputOpts) => void;
22
+ /** Render the error and exit on the band its code derives. Never returns. */
23
+ export declare const fail: (code: PushtoErrorCode, human: string, opts: OutputOpts, extra?: Parameters<typeof buildError>[2]) => never;
24
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAe,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE5E,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,EAAE,MAAM,UAAU,KAAG,IAEzD,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,aAAa,GAAI,MAAM,UAAU,KAAG,MAAM,CAAC,WACX,CAAC;AAE9C,eAAO,MAAM,MAAM,GACjB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EACrD,MAAM,UAAU,KACf,IAOF,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,KAAK,WAAW,EAAE,MAAM,UAAU,KAAG,IA4C5D,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,IAAI,GACf,MAAM,eAAe,EACrB,OAAO,MAAM,EACb,MAAM,UAAU,EAChB,QAAQ,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,KACvC,KAGF,CAAC"}
@@ -0,0 +1,71 @@
1
+ import chalk from 'chalk';
2
+ import { buildError, exitCodeFor } from './errors.js';
3
+ /**
4
+ * Under --json, stdout carries exactly one JSON object and nothing else, so
5
+ * `pushto deploy --json | jq` works unfiltered. Everything a human would
6
+ * enjoy — spinners, colour, progress — goes to stderr instead.
7
+ *
8
+ * lib/api.ts already wrote its session-expired notice to stderr to keep user
9
+ * pipes clean; this module finishes that discipline for the whole spine.
10
+ */
11
+ /** Human-only progress. Never stdout; in json mode it still goes to stderr. */
12
+ export const progress = (line, opts) => {
13
+ process.stderr.write(opts.json ? `${line}\n` : `${chalk.dim(line)}\n`);
14
+ };
15
+ /** ora writes to stdout by default. In json mode that would corrupt the payload. */
16
+ export const spinnerStream = (opts) => opts.json ? process.stderr : process.stdout;
17
+ export const emitOk = (payload, opts) => {
18
+ if (opts.json) {
19
+ const { human: _human, ...rest } = payload;
20
+ process.stdout.write(`${JSON.stringify({ ok: true, ...rest })}\n`);
21
+ return;
22
+ }
23
+ if (payload.human)
24
+ process.stdout.write(`${payload.human}\n`);
25
+ };
26
+ export const emitErr = (err, opts) => {
27
+ if (opts.json) {
28
+ // `prose` is a human-render hint carrying a {cmd} placeholder. Machines
29
+ // read label/cmd/href, so it is stripped here rather than shipped as a
30
+ // half-interpolated string a caller might print raw.
31
+ const machineFix = err.fix
32
+ ? {
33
+ label: err.fix.label,
34
+ ...(err.fix.cmd ? { cmd: err.fix.cmd } : {}),
35
+ ...(err.fix.href ? { href: err.fix.href } : {}),
36
+ }
37
+ : undefined;
38
+ // Keys are spread in a fixed order so the payload shape stays stable for
39
+ // consumers that diff or snapshot it. Optional keys are omitted, not set
40
+ // to undefined, because JSON.stringify would drop them inconsistently.
41
+ process.stdout.write(`${JSON.stringify({
42
+ ok: false,
43
+ ...(err.stage ? { stage: err.stage } : {}),
44
+ retryable: err.retryable,
45
+ ...(err.details ? { details: err.details } : {}),
46
+ error: { code: err.code, human: err.human, ...(machineFix ? { fix: machineFix } : {}) },
47
+ })}\n`);
48
+ return;
49
+ }
50
+ process.stderr.write(`${chalk.red(`> ${err.human}`)}\n`);
51
+ if (!err.fix)
52
+ return;
53
+ const target = err.fix.cmd ?? err.fix.href;
54
+ if (!target)
55
+ return;
56
+ // `prose` wins so hand-written copy survives the structured shape: the
57
+ // sentence is split on its single {cmd} placeholder and the command is
58
+ // highlighted in place, reproducing the original line exactly.
59
+ if (err.fix.prose) {
60
+ const [before = '', after = ''] = err.fix.prose.split('{cmd}');
61
+ process.stderr.write(`${chalk.dim(` ${before}`)}${chalk.cyan(target)}${chalk.dim(after)}\n`);
62
+ return;
63
+ }
64
+ process.stderr.write(`${chalk.dim(` ${err.fix.label}: `)}${chalk.cyan(target)}\n`);
65
+ };
66
+ /** Render the error and exit on the band its code derives. Never returns. */
67
+ export const fail = (code, human, opts, extra) => {
68
+ emitErr(buildError(code, human, extra), opts);
69
+ process.exit(exitCodeFor(code));
70
+ };
71
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The one entry point deploy + preview share: decide whether this
3
+ * directory deploys as-is (source) or gets built first (artifact).
4
+ * Keeping it here means `pushto` and `pushto preview` cannot drift —
5
+ * the exact failure the spec calls out (working deploys, blank previews).
6
+ */
7
+ export type ArtifactPrep = {
8
+ kind: 'source';
9
+ zipDir: string;
10
+ } | {
11
+ kind: 'artifact';
12
+ zipDir: string;
13
+ framework: string;
14
+ typeWarnings: string | null;
15
+ cleanup: () => void;
16
+ } | {
17
+ kind: 'failed';
18
+ message: string;
19
+ buildOutput: string;
20
+ };
21
+ export declare const prepareArtifact: (cwd: string, log: (line: string) => void) => Promise<ArtifactPrep>;
22
+ //# sourceMappingURL=prepare-artifact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-artifact.d.ts","sourceRoot":"","sources":["../../src/lib/prepare-artifact.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GACzG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,eAAO,MAAM,eAAe,GAC1B,KAAK,MAAM,EACX,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC1B,OAAO,CAAC,YAAY,CAsBtB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { detectStaticFramework } from './detect-static-framework.js';
2
+ import { runStaticBuild, writeArtifactMeta } from './build-runner.js';
3
+ export const prepareArtifact = async (cwd, log) => {
4
+ const detection = detectStaticFramework(cwd);
5
+ if (!detection)
6
+ return { kind: 'source', zipDir: cwd };
7
+ const build = await runStaticBuild(cwd, detection, { log });
8
+ if (!build.ok)
9
+ return { kind: 'failed', message: build.message, buildOutput: build.buildOutput };
10
+ const cleanup = writeArtifactMeta(build.outputDir, {
11
+ framework: detection.framework,
12
+ spa: detection.spa,
13
+ });
14
+ // The marker must never outlive the command: registering cleanup on
15
+ // process exit covers every failure branch (process.exit calls
16
+ // included), so no call site can forget it. cleanup is idempotent.
17
+ process.once('exit', cleanup);
18
+ return {
19
+ kind: 'artifact',
20
+ zipDir: build.outputDir,
21
+ framework: detection.framework,
22
+ typeWarnings: build.typeWarnings,
23
+ cleanup,
24
+ };
25
+ };
26
+ //# sourceMappingURL=prepare-artifact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-artifact.js","sourceRoot":"","sources":["../../src/lib/prepare-artifact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAatE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAClC,GAAW,EACX,GAA2B,EACJ,EAAE;IACzB,MAAM,SAAS,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAEvD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;IAEjG,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE;QACjD,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,GAAG,EAAE,SAAS,CAAC,GAAG;KACnB,CAAC,CAAC;IACH,oEAAoE;IACpE,+DAA+D;IAC/D,mEAAmE;IACnE,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,KAAK,CAAC,SAAS;QACvB,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,OAAO;KACR,CAAC;AACJ,CAAC,CAAC"}