neon 3.0.0 → 3.1.1

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 (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,448 +1,377 @@
1
- import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
1
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { gunzipSync } from "fflate";
4
3
  import YAML from "yaml";
4
+ import { gunzipSync } from "fflate";
5
+ //#region src/init/bootstrap.ts
5
6
  /** Default features when a template doesn't specify `requires`. */
6
7
  const DEFAULT_REQUIRES = ["database"];
7
8
  /**
8
- * Hardcoded fallback used when every remote manifest source is unreachable.
9
- * Kept in sync with `neondatabase/examples/bootstrap.yaml` (the source of
10
- * truth) so that, even fully offline from the manifest, the picker still offers
11
- * the full set of starters rather than a single template.
12
- */
13
- export const FALLBACK_TEMPLATES = [
14
- {
15
- id: "hono",
16
- title: "REST API",
17
- description: "A Hono REST API on Neon Functions, backed by Lakebase Postgres via Drizzle.",
18
- tools: ["Hono", "Drizzle"],
19
- services: ["Postgres", "Functions"],
20
- requires: ["database", "functions"],
21
- source: {
22
- owner: "neondatabase",
23
- repo: "examples",
24
- ref: "main",
25
- subdir: "with-hono",
26
- },
27
- },
28
- {
29
- id: "ai-sdk",
30
- title: "Image-generation agent",
31
- description: "A Vercel AI SDK agent that streams chat through the Neon AI Gateway and stores generated images in Neon object storage, indexed in Postgres via Drizzle.",
32
- tools: ["AI SDK", "Drizzle"],
33
- services: ["Postgres", "Functions", "Object Storage", "AI Gateway"],
34
- requires: ["database", "functions", "object-storage", "ai-gateway"],
35
- source: {
36
- owner: "neondatabase",
37
- repo: "examples",
38
- ref: "main",
39
- subdir: "with-ai-sdk",
40
- },
41
- },
42
- {
43
- id: "mastra",
44
- title: "Personal-assistant agent",
45
- description: "A Mastra agent that streams chat through the Neon AI Gateway and uses Mastra Memory on Lakebase Postgres to remember you across threads.",
46
- tools: ["Mastra", "Mastra Memory"],
47
- services: ["Postgres", "Functions", "AI Gateway"],
48
- requires: ["database", "functions", "ai-gateway"],
49
- source: {
50
- owner: "neondatabase",
51
- repo: "examples",
52
- ref: "main",
53
- subdir: "with-mastra",
54
- },
55
- },
9
+ * Hardcoded fallback used when every remote manifest source is unreachable.
10
+ * Kept in sync with `neondatabase/examples/bootstrap.yaml` (the source of
11
+ * truth) so that, even fully offline from the manifest, the picker still offers
12
+ * the full set of starters rather than a single template.
13
+ */
14
+ const FALLBACK_TEMPLATES = [
15
+ {
16
+ id: "hono",
17
+ title: "REST API",
18
+ description: "A Hono REST API on Neon Functions, backed by Lakebase Postgres via Drizzle.",
19
+ tools: ["Hono", "Drizzle"],
20
+ services: ["Postgres", "Functions"],
21
+ requires: ["database", "functions"],
22
+ source: {
23
+ owner: "neondatabase",
24
+ repo: "examples",
25
+ ref: "main",
26
+ subdir: "with-hono"
27
+ }
28
+ },
29
+ {
30
+ id: "ai-sdk",
31
+ title: "Image-generation agent",
32
+ description: "A Vercel AI SDK agent that streams chat through the Neon AI Gateway and stores generated images in Neon object storage, indexed in Postgres via Drizzle.",
33
+ tools: ["AI SDK", "Drizzle"],
34
+ services: [
35
+ "Postgres",
36
+ "Functions",
37
+ "Object Storage",
38
+ "AI Gateway"
39
+ ],
40
+ requires: [
41
+ "database",
42
+ "functions",
43
+ "object-storage",
44
+ "ai-gateway"
45
+ ],
46
+ source: {
47
+ owner: "neondatabase",
48
+ repo: "examples",
49
+ ref: "main",
50
+ subdir: "with-ai-sdk"
51
+ }
52
+ },
53
+ {
54
+ id: "mastra",
55
+ title: "Personal-assistant agent",
56
+ description: "A Mastra agent that streams chat through the Neon AI Gateway and uses Mastra Memory on Lakebase Postgres to remember you across threads.",
57
+ tools: ["Mastra", "Mastra Memory"],
58
+ services: [
59
+ "Postgres",
60
+ "Functions",
61
+ "AI Gateway"
62
+ ],
63
+ requires: [
64
+ "database",
65
+ "functions",
66
+ "ai-gateway"
67
+ ],
68
+ source: {
69
+ owner: "neondatabase",
70
+ repo: "examples",
71
+ ref: "main",
72
+ subdir: "with-mastra"
73
+ }
74
+ }
56
75
  ];
57
- export const templateIds = (templates) => templates.map((t) => t.id).join(", ");
58
- export const findTemplate = (templates, id) => templates.find((t) => t.id === id);
76
+ const templateIds = (templates) => templates.map((t) => t.id).join(", ");
77
+ const findTemplate = (templates, id) => templates.find((t) => t.id === id);
59
78
  const githubToken = () => process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? "";
60
- // A token is never required for public templates, but we forward it when
61
- // present so the same code path works behind proxies that authenticate, and
62
- // (in future) for private template repos.
63
79
  const downloadHeaders = () => ({
64
- // GitHub rejects a request with no User-Agent; the value is free-form and this
65
- // one only has to name the client honestly.
66
- "User-Agent": "neon",
67
- ...(githubToken() ? { Authorization: `Bearer ${githubToken()}` } : {}),
80
+ "User-Agent": "neon",
81
+ ...githubToken() ? { Authorization: `Bearer ${githubToken()}` } : {}
68
82
  });
69
- // The codeload host is overridable so the e2e tests can point the downloader at
70
- // a local server (the same trick `--api-host` uses to redirect the Neon API).
71
83
  const codeloadBase = () => process.env.NEON_BOOTSTRAP_GITHUB_CODELOAD ?? "https://codeload.github.com";
72
84
  const isRecord = (value) => typeof value === "object" && value !== null;
73
85
  /**
74
- * Normalize a manifest entry's string list (`tools` or `services`) into a clean
75
- * array. Tolerant by design: a missing or non-array value yields `undefined`,
76
- * and non-string/blank items are dropped, so a malformed list never sinks an
77
- * otherwise-valid template (it just renders without that detail).
78
- */
86
+ * Normalize a manifest entry's string list (`tools` or `services`) into a clean
87
+ * array. Tolerant by design: a missing or non-array value yields `undefined`,
88
+ * and non-string/blank items are dropped, so a malformed list never sinks an
89
+ * otherwise-valid template (it just renders without that detail).
90
+ */
79
91
  const parseStringList = (value) => {
80
- if (!Array.isArray(value))
81
- return undefined;
82
- const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
83
- return items.length > 0 ? items : undefined;
92
+ if (!Array.isArray(value)) return void 0;
93
+ const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
94
+ return items.length > 0 ? items : void 0;
84
95
  };
85
- // ---------------------------------------------------------------------------
86
- // Remote template manifest
87
- // ---------------------------------------------------------------------------
88
- // Primary manifest host is neon.com (CDN-backed, no GitHub rate limiting), with
89
- // the raw GitHub copy as a fallback and the hardcoded list as the last resort.
90
- // A single env override (used by tests) short-circuits the chain.
91
96
  const NEON_MANIFEST_URL = "https://neon.com/bootstrap/templates.yaml";
92
97
  const GITHUB_RAW_MANIFEST_URL = "https://raw.githubusercontent.com/neondatabase/examples/main/bootstrap.yaml";
93
98
  function manifestUrls() {
94
- const override = process.env.NEON_BOOTSTRAP_MANIFEST_URL;
95
- if (override)
96
- return [override];
97
- return [NEON_MANIFEST_URL, GITHUB_RAW_MANIFEST_URL];
99
+ const override = process.env.NEON_BOOTSTRAP_MANIFEST_URL;
100
+ if (override) return [override];
101
+ return [NEON_MANIFEST_URL, GITHUB_RAW_MANIFEST_URL];
98
102
  }
99
- export function parseManifest(text) {
100
- const data = YAML.parse(text);
101
- if (!isRecord(data) || !Array.isArray(data.templates)) {
102
- throw new Error('Invalid bootstrap manifest: missing "templates" array.');
103
- }
104
- const templates = [];
105
- for (const item of data.templates) {
106
- if (!isRecord(item) ||
107
- typeof item.id !== "string" ||
108
- typeof item.title !== "string" ||
109
- typeof item.description !== "string" ||
110
- !isRecord(item.source) ||
111
- typeof item.source.owner !== "string" ||
112
- typeof item.source.repo !== "string" ||
113
- typeof item.source.ref !== "string" ||
114
- typeof item.source.subdir !== "string") {
115
- continue;
116
- }
117
- // Parse requires — accept a string array, default to ["database"].
118
- const requires = Array.isArray(item.requires) &&
119
- item.requires.every((r) => typeof r === "string")
120
- ? item.requires
121
- : DEFAULT_REQUIRES;
122
- const tools = parseStringList(item.tools);
123
- const services = parseStringList(item.services);
124
- templates.push({
125
- id: item.id,
126
- title: item.title,
127
- description: item.description,
128
- ...(tools ? { tools } : {}),
129
- ...(services ? { services } : {}),
130
- requires,
131
- source: {
132
- owner: item.source.owner,
133
- repo: item.source.repo,
134
- ref: item.source.ref,
135
- subdir: item.source.subdir,
136
- },
137
- });
138
- }
139
- return templates;
103
+ function parseManifest(text) {
104
+ const data = YAML.parse(text);
105
+ if (!isRecord(data) || !Array.isArray(data.templates)) throw new Error("Invalid bootstrap manifest: missing \"templates\" array.");
106
+ const templates = [];
107
+ for (const item of data.templates) {
108
+ if (!isRecord(item) || typeof item.id !== "string" || typeof item.title !== "string" || typeof item.description !== "string" || !isRecord(item.source) || typeof item.source.owner !== "string" || typeof item.source.repo !== "string" || typeof item.source.ref !== "string" || typeof item.source.subdir !== "string") continue;
109
+ const requires = Array.isArray(item.requires) && item.requires.every((r) => typeof r === "string") ? item.requires : DEFAULT_REQUIRES;
110
+ const tools = parseStringList(item.tools);
111
+ const services = parseStringList(item.services);
112
+ templates.push({
113
+ id: item.id,
114
+ title: item.title,
115
+ description: item.description,
116
+ ...tools ? { tools } : {},
117
+ ...services ? { services } : {},
118
+ requires,
119
+ source: {
120
+ owner: item.source.owner,
121
+ repo: item.source.repo,
122
+ ref: item.source.ref,
123
+ subdir: item.source.subdir
124
+ }
125
+ });
126
+ }
127
+ return templates;
140
128
  }
141
129
  /**
142
- * Fetch the template manifest, trying each source in {@link manifestUrls} in
143
- * order and returning the first that yields a non-empty template list. Falls
144
- * back to the hardcoded list when every source is unreachable or empty, so the
145
- * picker never fails just because a host is down.
146
- */
147
- export async function fetchTemplates() {
148
- for (const url of manifestUrls()) {
149
- try {
150
- const res = await fetch(url, {
151
- headers: downloadHeaders(),
152
- signal: AbortSignal.timeout(10000),
153
- });
154
- if (!res.ok)
155
- throw new Error(`HTTP ${res.status}`);
156
- const templates = parseManifest(await res.text());
157
- if (templates.length > 0)
158
- return templates;
159
- }
160
- catch {
161
- // Try the next source.
162
- }
163
- }
164
- return FALLBACK_TEMPLATES;
130
+ * Fetch the template manifest, trying each source in {@link manifestUrls} in
131
+ * order and returning the first that yields a non-empty template list. Falls
132
+ * back to the hardcoded list when every source is unreachable or empty, so the
133
+ * picker never fails just because a host is down.
134
+ */
135
+ async function fetchTemplates() {
136
+ for (const url of manifestUrls()) try {
137
+ const res = await fetch(url, {
138
+ headers: downloadHeaders(),
139
+ signal: AbortSignal.timeout(1e4)
140
+ });
141
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
142
+ const templates = parseManifest(await res.text());
143
+ if (templates.length > 0) return templates;
144
+ } catch {}
145
+ return FALLBACK_TEMPLATES;
165
146
  }
166
147
  const TAR_BLOCK = 512;
167
148
  const readTarString = (buf, offset, length) => {
168
- let end = offset;
169
- const max = offset + length;
170
- while (end < max && buf[end] !== 0)
171
- end++;
172
- return buf.toString("utf8", offset, end);
149
+ let end = offset;
150
+ const max = offset + length;
151
+ while (end < max && buf[end] !== 0) end++;
152
+ return buf.toString("utf8", offset, end);
173
153
  };
174
154
  const readTarOctal = (buf, offset, length) => {
175
- const text = readTarString(buf, offset, length).trim();
176
- if (text === "")
177
- return 0;
178
- const value = parseInt(text, 8);
179
- return Number.isNaN(value) ? 0 : value;
155
+ const text = readTarString(buf, offset, length).trim();
156
+ if (text === "") return 0;
157
+ const value = parseInt(text, 8);
158
+ return Number.isNaN(value) ? 0 : value;
180
159
  };
181
160
  const isZeroBlock = (buf, offset) => {
182
- for (let i = offset; i < offset + TAR_BLOCK; i++) {
183
- if (buf[i] !== 0)
184
- return false;
185
- }
186
- return true;
161
+ for (let i = offset; i < offset + TAR_BLOCK; i++) if (buf[i] !== 0) return false;
162
+ return true;
187
163
  };
188
164
  /**
189
- * Parse pax extended-header records ("<len> <key>=<value>\n"). GitHub uses
190
- * these for the global header and for any path that doesn't fit the legacy
191
- * 100-byte name field, so we must honor at least `path` and `linkpath`.
192
- */
165
+ * Parse pax extended-header records ("<len> <key>=<value>\n"). GitHub uses
166
+ * these for the global header and for any path that doesn't fit the legacy
167
+ * 100-byte name field, so we must honor at least `path` and `linkpath`.
168
+ */
193
169
  const parsePaxRecords = (data) => {
194
- const records = {};
195
- let pos = 0;
196
- const text = data.toString("utf8");
197
- while (pos < text.length) {
198
- const space = text.indexOf(" ", pos);
199
- if (space === -1)
200
- break;
201
- const len = parseInt(text.slice(pos, space), 10);
202
- if (Number.isNaN(len) || len <= 0)
203
- break;
204
- const record = text.slice(space + 1, pos + len - 1); // drop trailing "\n"
205
- const eq = record.indexOf("=");
206
- if (eq !== -1)
207
- records[record.slice(0, eq)] = record.slice(eq + 1);
208
- pos += len;
209
- }
210
- return records;
170
+ const records = {};
171
+ let pos = 0;
172
+ const text = data.toString("utf8");
173
+ while (pos < text.length) {
174
+ const space = text.indexOf(" ", pos);
175
+ if (space === -1) break;
176
+ const len = parseInt(text.slice(pos, space), 10);
177
+ if (Number.isNaN(len) || len <= 0) break;
178
+ const record = text.slice(space + 1, pos + len - 1);
179
+ const eq = record.indexOf("=");
180
+ if (eq !== -1) records[record.slice(0, eq)] = record.slice(eq + 1);
181
+ pos += len;
182
+ }
183
+ return records;
211
184
  };
212
185
  /**
213
- * Decode a (decompressed) tar archive into its file/symlink entries. Pure and
214
- * dependency-free so it can be unit tested without touching the network.
215
- * Handles the ustar `prefix` field, pax extended headers (type 'x'/'g'), and
216
- * GNU long-name/long-link headers (type 'L'/'K') so deep template paths and
217
- * long symlink targets round-trip correctly.
218
- */
219
- export const parseTar = (buf) => {
220
- const entries = [];
221
- // Overrides carried from a preceding pax/GNU header to the next real entry.
222
- let overridePath;
223
- let overrideLink;
224
- let offset = 0;
225
- while (offset + TAR_BLOCK <= buf.length) {
226
- if (isZeroBlock(buf, offset))
227
- break;
228
- let name = readTarString(buf, offset, 100);
229
- const mode = readTarOctal(buf, offset + 100, 8);
230
- const size = readTarOctal(buf, offset + 124, 12);
231
- const typeByte = buf[offset + 156];
232
- const type = typeByte === 0 ? "0" : String.fromCharCode(typeByte);
233
- let linkname = readTarString(buf, offset + 157, 100);
234
- const magic = readTarString(buf, offset + 257, 6);
235
- if (magic.startsWith("ustar")) {
236
- const prefix = readTarString(buf, offset + 345, 155);
237
- if (prefix !== "")
238
- name = `${prefix}/${name}`;
239
- }
240
- offset += TAR_BLOCK;
241
- const data = buf.subarray(offset, offset + size);
242
- offset += Math.ceil(size / TAR_BLOCK) * TAR_BLOCK;
243
- if (type === "x") {
244
- const records = parsePaxRecords(data);
245
- if (records.path !== undefined)
246
- overridePath = records.path;
247
- if (records.linkpath !== undefined)
248
- overrideLink = records.linkpath;
249
- continue;
250
- }
251
- if (type === "g") {
252
- // Global pax header (e.g. GitHub's comment block): not per-entry state.
253
- continue;
254
- }
255
- if (type === "L" || type === "K") {
256
- const longValue = data.toString("utf8").replace(/\0+$/, "");
257
- if (type === "L")
258
- overridePath = longValue;
259
- else
260
- overrideLink = longValue;
261
- continue;
262
- }
263
- if (overridePath !== undefined)
264
- name = overridePath;
265
- if (overrideLink !== undefined)
266
- linkname = overrideLink;
267
- overridePath = undefined;
268
- overrideLink = undefined;
269
- entries.push({ name, type, mode, linkname, data: Buffer.from(data) });
270
- }
271
- return entries;
186
+ * Decode a (decompressed) tar archive into its file/symlink entries. Pure and
187
+ * dependency-free so it can be unit tested without touching the network.
188
+ * Handles the ustar `prefix` field, pax extended headers (type 'x'/'g'), and
189
+ * GNU long-name/long-link headers (type 'L'/'K') so deep template paths and
190
+ * long symlink targets round-trip correctly.
191
+ */
192
+ const parseTar = (buf) => {
193
+ const entries = [];
194
+ let overridePath;
195
+ let overrideLink;
196
+ let offset = 0;
197
+ while (offset + TAR_BLOCK <= buf.length) {
198
+ if (isZeroBlock(buf, offset)) break;
199
+ let name = readTarString(buf, offset, 100);
200
+ const mode = readTarOctal(buf, offset + 100, 8);
201
+ const size = readTarOctal(buf, offset + 124, 12);
202
+ const typeByte = buf[offset + 156];
203
+ const type = typeByte === 0 ? "0" : String.fromCharCode(typeByte);
204
+ let linkname = readTarString(buf, offset + 157, 100);
205
+ if (readTarString(buf, offset + 257, 6).startsWith("ustar")) {
206
+ const prefix = readTarString(buf, offset + 345, 155);
207
+ if (prefix !== "") name = `${prefix}/${name}`;
208
+ }
209
+ offset += TAR_BLOCK;
210
+ const data = buf.subarray(offset, offset + size);
211
+ offset += Math.ceil(size / TAR_BLOCK) * TAR_BLOCK;
212
+ if (type === "x") {
213
+ const records = parsePaxRecords(data);
214
+ if (records.path !== void 0) overridePath = records.path;
215
+ if (records.linkpath !== void 0) overrideLink = records.linkpath;
216
+ continue;
217
+ }
218
+ if (type === "g") continue;
219
+ if (type === "L" || type === "K") {
220
+ const longValue = data.toString("utf8").replace(/\0+$/, "");
221
+ if (type === "L") overridePath = longValue;
222
+ else overrideLink = longValue;
223
+ continue;
224
+ }
225
+ if (overridePath !== void 0) name = overridePath;
226
+ if (overrideLink !== void 0) linkname = overrideLink;
227
+ overridePath = void 0;
228
+ overrideLink = void 0;
229
+ entries.push({
230
+ name,
231
+ type,
232
+ mode,
233
+ linkname,
234
+ data: Buffer.from(data)
235
+ });
236
+ }
237
+ return entries;
272
238
  };
273
239
  /**
274
- * Map decoded tar entries to the files under `subdir`, with the top-level
275
- * archive directory and the `subdir/` prefix stripped from each path. Pure so
276
- * it can be unit tested. Directory and other non-regular entries are dropped —
277
- * writing files re-creates their parent directories.
278
- */
279
- export const selectTemplateFiles = (entries, subdir) => {
280
- const prefix = `${subdir.replace(/^\/+|\/+$/g, "")}/`;
281
- const files = [];
282
- for (const entry of entries) {
283
- // codeload wraps everything in a single top-level dir ("<repo>-<ref>/");
284
- // strip that first segment to get the repo-relative path.
285
- const slash = entry.name.indexOf("/");
286
- if (slash === -1)
287
- continue;
288
- const repoPath = entry.name.slice(slash + 1);
289
- if (!repoPath.startsWith(prefix))
290
- continue;
291
- const path = repoPath.slice(prefix.length);
292
- if (path === "")
293
- continue;
294
- if (entry.type === "2") {
295
- files.push({ kind: "symlink", path, target: entry.linkname });
296
- }
297
- else if (entry.type === "0" || entry.type === "7") {
298
- files.push({
299
- kind: "file",
300
- path,
301
- bytes: entry.data,
302
- executable: (entry.mode & 0o111) !== 0,
303
- });
304
- }
305
- // Directories ('5') and any other node types are intentionally skipped.
306
- }
307
- return files;
240
+ * Map decoded tar entries to the files under `subdir`, with the top-level
241
+ * archive directory and the `subdir/` prefix stripped from each path. Pure so
242
+ * it can be unit tested. Directory and other non-regular entries are dropped —
243
+ * writing files re-creates their parent directories.
244
+ */
245
+ const selectTemplateFiles = (entries, subdir) => {
246
+ const prefix = `${subdir.replace(/^\/+|\/+$/g, "")}/`;
247
+ const files = [];
248
+ for (const entry of entries) {
249
+ const slash = entry.name.indexOf("/");
250
+ if (slash === -1) continue;
251
+ const repoPath = entry.name.slice(slash + 1);
252
+ if (!repoPath.startsWith(prefix)) continue;
253
+ const path = repoPath.slice(prefix.length);
254
+ if (path === "") continue;
255
+ if (entry.type === "2") files.push({
256
+ kind: "symlink",
257
+ path,
258
+ target: entry.linkname
259
+ });
260
+ else if (entry.type === "0" || entry.type === "7") files.push({
261
+ kind: "file",
262
+ path,
263
+ bytes: entry.data,
264
+ executable: (entry.mode & 73) !== 0
265
+ });
266
+ }
267
+ return files;
308
268
  };
309
269
  const tarballUrl = (template) => {
310
- const { owner, repo, ref } = template.source;
311
- return `${codeloadBase()}/${owner}/${repo}/tar.gz/${ref}`;
270
+ const { owner, repo, ref } = template.source;
271
+ return `${codeloadBase()}/${owner}/${repo}/tar.gz/${ref}`;
312
272
  };
313
273
  const friendlyGithubError = (status, url) => {
314
- if (status === 404) {
315
- return new Error(`GitHub returned 404 for ${url}. The template repo or ref may have moved.`);
316
- }
317
- if (status === 403 || status === 429) {
318
- return new Error(`GitHub rate limited the template download (${url}). Set a GITHUB_TOKEN environment variable to raise the limit, then retry.`);
319
- }
320
- return new Error(`GitHub returned HTTP ${status} for ${url}.`);
274
+ if (status === 404) return /* @__PURE__ */ new Error(`GitHub returned 404 for ${url}. The template repo or ref may have moved.`);
275
+ if (status === 403 || status === 429) return /* @__PURE__ */ new Error(`GitHub rate limited the template download (${url}). Set a GITHUB_TOKEN environment variable to raise the limit, then retry.`);
276
+ return /* @__PURE__ */ new Error(`GitHub returned HTTP ${status} for ${url}.`);
321
277
  };
322
278
  /**
323
- * Download a template and resolve it to the exact set of files to write. The
324
- * entire subtree is captured in one tarball request, so the copy is atomically
325
- * consistent: a push to the template repo mid-download cannot produce a
326
- * mismatched checkout (unlike fetching a file list and then each blob).
327
- */
328
- export const downloadTemplate = async (template) => {
329
- const url = tarballUrl(template);
330
- let gzipped;
331
- try {
332
- const res = await fetch(url, {
333
- headers: downloadHeaders(),
334
- signal: AbortSignal.timeout(30000),
335
- });
336
- if (!res.ok)
337
- throw friendlyGithubError(res.status, url);
338
- gzipped = Buffer.from(await res.arrayBuffer());
339
- }
340
- catch (err) {
341
- throw err instanceof Error ? err : new Error(String(err));
342
- }
343
- let tar;
344
- try {
345
- tar = Buffer.from(gunzipSync(new Uint8Array(gzipped)));
346
- }
347
- catch (err) {
348
- throw new Error(`Failed to decompress the template archive from ${url}: ${err instanceof Error ? err.message : String(err)}`);
349
- }
350
- const { owner, repo, ref, subdir } = template.source;
351
- const files = selectTemplateFiles(parseTar(tar), subdir);
352
- if (files.length === 0) {
353
- throw new Error(`Template subdirectory "${subdir}" was not found in ${owner}/${repo}@${ref}.`);
354
- }
355
- return files;
279
+ * Download a template and resolve it to the exact set of files to write. The
280
+ * entire subtree is captured in one tarball request, so the copy is atomically
281
+ * consistent: a push to the template repo mid-download cannot produce a
282
+ * mismatched checkout (unlike fetching a file list and then each blob).
283
+ */
284
+ const downloadTemplate = async (template) => {
285
+ const url = tarballUrl(template);
286
+ let gzipped;
287
+ try {
288
+ const res = await fetch(url, {
289
+ headers: downloadHeaders(),
290
+ signal: AbortSignal.timeout(3e4)
291
+ });
292
+ if (!res.ok) throw friendlyGithubError(res.status, url);
293
+ gzipped = Buffer.from(await res.arrayBuffer());
294
+ } catch (err) {
295
+ throw err instanceof Error ? err : new Error(String(err));
296
+ }
297
+ let tar;
298
+ try {
299
+ tar = Buffer.from(gunzipSync(new Uint8Array(gzipped)));
300
+ } catch (err) {
301
+ throw new Error(`Failed to decompress the template archive from ${url}: ${err instanceof Error ? err.message : String(err)}`);
302
+ }
303
+ const { owner, repo, ref, subdir } = template.source;
304
+ const files = selectTemplateFiles(parseTar(tar), subdir);
305
+ if (files.length === 0) throw new Error(`Template subdirectory "${subdir}" was not found in ${owner}/${repo}@${ref}.`);
306
+ return files;
356
307
  };
357
- // ---------------------------------------------------------------------------
358
- // Target validation + scaffolding to disk
359
- // ---------------------------------------------------------------------------
360
308
  /**
361
- * A bad caller-supplied input that an agent (or human) can correct: an unknown
362
- * template id or a non-empty target directory. Carries an `agentCode` so an
363
- * agent surface can report a precise error code instead of a generic
364
- * INTERNAL_ERROR, while a human path just surfaces the clear `message`.
365
- */
366
- export class BootstrapInputError extends Error {
367
- constructor(message, agentCode) {
368
- super(message);
369
- this.name = "BootstrapInputError";
370
- this.agentCode = agentCode;
371
- }
372
- }
309
+ * A bad caller-supplied input that an agent (or human) can correct: an unknown
310
+ * template id or a non-empty target directory. Carries an `agentCode` so an
311
+ * agent surface can report a precise error code instead of a generic
312
+ * INTERNAL_ERROR, while a human path just surfaces the clear `message`.
313
+ */
314
+ var BootstrapInputError = class extends Error {
315
+ constructor(message, agentCode) {
316
+ super(message);
317
+ this.name = "BootstrapInputError";
318
+ this.agentCode = agentCode;
319
+ }
320
+ };
373
321
  /**
374
- * Ensure `dir` is safe to scaffold into: it must be missing, or an empty
375
- * directory (a lone `.git` is ignored so you can scaffold into a freshly
376
- * `git init`ed folder). `force` allows scaffolding into a non-empty directory,
377
- * overwriting colliding files. Throws a {@link BootstrapInputError} otherwise.
378
- */
379
- export const ensureTargetUsable = (dir, force) => {
380
- if (!existsSync(dir))
381
- return;
382
- if (!statSync(dir).isDirectory()) {
383
- throw new BootstrapInputError(`Target ${dir} already exists and is not a directory.`, "TARGET_NOT_DIRECTORY");
384
- }
385
- const contents = readdirSync(dir).filter((name) => name !== ".git");
386
- if (contents.length > 0 && !force) {
387
- throw new BootstrapInputError(`Target directory ${dir} is not empty. Use --force to scaffold into it anyway (colliding files will be overwritten), or choose an empty directory.`, "TARGET_NOT_EMPTY");
388
- }
322
+ * Ensure `dir` is safe to scaffold into: it must be missing, or an empty
323
+ * directory (a lone `.git` is ignored so you can scaffold into a freshly
324
+ * `git init`ed folder). `force` allows scaffolding into a non-empty directory,
325
+ * overwriting colliding files. Throws a {@link BootstrapInputError} otherwise.
326
+ */
327
+ const ensureTargetUsable = (dir, force) => {
328
+ if (!existsSync(dir)) return;
329
+ if (!statSync(dir).isDirectory()) throw new BootstrapInputError(`Target ${dir} already exists and is not a directory.`, "TARGET_NOT_DIRECTORY");
330
+ if (readdirSync(dir).filter((name) => name !== ".git").length > 0 && !force) throw new BootstrapInputError(`Target directory ${dir} is not empty. Use --force to scaffold into it anyway (colliding files will be overwritten), or choose an empty directory.`, "TARGET_NOT_EMPTY");
389
331
  };
390
332
  const isSymlink = (path) => {
391
- try {
392
- return lstatSync(path).isSymbolicLink();
393
- }
394
- catch {
395
- return false;
396
- }
333
+ try {
334
+ return lstatSync(path).isSymbolicLink();
335
+ } catch {
336
+ return false;
337
+ }
397
338
  };
398
339
  const errnoCode = (err) => {
399
- if (typeof err === "object" &&
400
- err !== null &&
401
- "code" in err &&
402
- typeof err.code === "string") {
403
- return err.code;
404
- }
405
- return undefined;
340
+ if (typeof err === "object" && err !== null && "code" in err && typeof err.code === "string") return err.code;
406
341
  };
407
342
  const writeSymlink = (dest, target, onWarn) => {
408
- if (isSymlink(dest))
409
- rmSync(dest, { force: true });
410
- try {
411
- symlinkSync(target, dest);
412
- }
413
- catch (err) {
414
- // Windows refuses symlinks without elevated rights / developer mode. The
415
- // template still works for most tooling if we drop a regular file holding
416
- // the link target, so we degrade gracefully instead of failing the copy.
417
- if (errnoCode(err) === "EPERM" || process.platform === "win32") {
418
- onWarn?.(`Could not create symlink ${dest} -> ${target}; wrote it as a regular file instead.`);
419
- writeFileSync(dest, target);
420
- return;
421
- }
422
- throw err;
423
- }
343
+ if (isSymlink(dest)) rmSync(dest, { force: true });
344
+ try {
345
+ symlinkSync(target, dest);
346
+ } catch (err) {
347
+ if (errnoCode(err) === "EPERM" || process.platform === "win32") {
348
+ onWarn?.(`Could not create symlink ${dest} -> ${target}; wrote it as a regular file instead.`);
349
+ writeFileSync(dest, target);
350
+ return;
351
+ }
352
+ throw err;
353
+ }
424
354
  };
425
355
  /**
426
- * Download `template` and materialize its files into `targetDir`, creating
427
- * parent directories, preserving executable bits, and recreating symlinks
428
- * (with a graceful regular-file fallback on platforms that disallow them).
429
- * Returns the number of files written. The caller is responsible for any
430
- * target validation ({@link ensureTargetUsable}) and user-facing progress.
431
- */
432
- export const scaffoldTemplate = async (template, targetDir, options = {}) => {
433
- const files = await downloadTemplate(template);
434
- mkdirSync(targetDir, { recursive: true });
435
- for (const file of files) {
436
- const dest = join(targetDir, file.path);
437
- mkdirSync(dirname(dest), { recursive: true });
438
- if (file.kind === "symlink") {
439
- writeSymlink(dest, file.target, options.onWarn);
440
- }
441
- else {
442
- writeFileSync(dest, file.bytes);
443
- if (file.executable)
444
- chmodSync(dest, 0o755);
445
- }
446
- }
447
- return files.length;
356
+ * Download `template` and materialize its files into `targetDir`, creating
357
+ * parent directories, preserving executable bits, and recreating symlinks
358
+ * (with a graceful regular-file fallback on platforms that disallow them).
359
+ * Returns the number of files written. The caller is responsible for any
360
+ * target validation ({@link ensureTargetUsable}) and user-facing progress.
361
+ */
362
+ const scaffoldTemplate = async (template, targetDir, options = {}) => {
363
+ const files = await downloadTemplate(template);
364
+ mkdirSync(targetDir, { recursive: true });
365
+ for (const file of files) {
366
+ const dest = join(targetDir, file.path);
367
+ mkdirSync(dirname(dest), { recursive: true });
368
+ if (file.kind === "symlink") writeSymlink(dest, file.target, options.onWarn);
369
+ else {
370
+ writeFileSync(dest, file.bytes);
371
+ if (file.executable) chmodSync(dest, 493);
372
+ }
373
+ }
374
+ return files.length;
448
375
  };
376
+ //#endregion
377
+ export { BootstrapInputError, FALLBACK_TEMPLATES, downloadTemplate, ensureTargetUsable, fetchTemplates, findTemplate, parseManifest, parseTar, scaffoldTemplate, selectTemplateFiles, templateIds };