neon 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/_shared/auth_selection.js +76 -79
- package/dist/_shared/credentials.js +166 -187
- package/dist/_shared/env-core/env.js +354 -517
- package/dist/_shared/env-core/reuse-secrets.js +159 -203
- package/dist/_shared/paths.js +129 -126
- package/dist/_shared/profiles.js +192 -242
- package/dist/_shared/secure_file.js +36 -38
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +243 -303
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +614 -845
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +313 -394
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +36 -38
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +727 -983
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +1293 -2082
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +547 -1099
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +261 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +762 -1180
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1302
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +626 -815
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +1414 -1927
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +2 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +37 -44
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +7 -5
|
@@ -1,495 +1,430 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { isCi } from "../env.js";
|
|
3
|
+
import { log } from "../log.js";
|
|
4
|
+
import { getCliName } from "../utils/cli_name.js";
|
|
5
|
+
import { credentialInputs } from "../_shared/auth_selection.js";
|
|
6
|
+
import { BootstrapInputError, FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
7
|
+
import { DO_NOT_SUBSTITUTE_HINT, formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
1
8
|
import { existsSync } from "node:fs";
|
|
2
9
|
import { join, relative, resolve } from "node:path";
|
|
3
10
|
import chalk from "chalk";
|
|
4
11
|
import prompts from "prompts";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
await runAgentSafely(props);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const templates = await resolveTemplateList(props);
|
|
85
|
-
// --default is a non-interactive quick start: it fills in the template and
|
|
86
|
-
// directory and runs setup without asking, so it must not fall into the
|
|
87
|
-
// prompt path even on a TTY.
|
|
88
|
-
const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
|
|
89
|
-
const template = await resolveSelectedTemplate(props, interactive, templates);
|
|
90
|
-
const targetDir = await resolveTargetDir(props, interactive, template);
|
|
91
|
-
ensureTargetUsable(targetDir, props.force);
|
|
92
|
-
await scaffold(template, targetDir);
|
|
93
|
-
printScaffolded(template, targetDir);
|
|
94
|
-
await runPostScaffoldSteps(props, targetDir, interactive);
|
|
12
|
+
//#region src/commands/bootstrap.ts
|
|
13
|
+
var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
14
|
+
builder: () => builder,
|
|
15
|
+
command: () => command,
|
|
16
|
+
describe: () => describe,
|
|
17
|
+
handler: () => handler
|
|
18
|
+
});
|
|
19
|
+
const command = "bootstrap [directory]";
|
|
20
|
+
const describe = "Scaffold a new project from a Neon starter template";
|
|
21
|
+
const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
|
|
22
|
+
describe: "Directory to scaffold into. Use \".\" for the current directory. Omit to be prompted.",
|
|
23
|
+
type: "string"
|
|
24
|
+
}).options({
|
|
25
|
+
template: {
|
|
26
|
+
describe: "Template to use (skips the interactive picker). Run with --list-templates to see available templates.",
|
|
27
|
+
type: "string"
|
|
28
|
+
},
|
|
29
|
+
"list-templates": {
|
|
30
|
+
alias: ["list", "ls"],
|
|
31
|
+
describe: "List available templates and exit.",
|
|
32
|
+
type: "boolean",
|
|
33
|
+
default: false
|
|
34
|
+
},
|
|
35
|
+
force: {
|
|
36
|
+
describe: "Scaffold into the target directory even if it is not empty (colliding files are overwritten).",
|
|
37
|
+
type: "boolean",
|
|
38
|
+
default: false
|
|
39
|
+
},
|
|
40
|
+
agent: {
|
|
41
|
+
describe: "Emit a JSON state-machine response designed for AI agents instead of prompting. The output is a single JSON object with a discriminated `status` field describing the next step.",
|
|
42
|
+
type: "boolean",
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
45
|
+
default: {
|
|
46
|
+
alias: "y",
|
|
47
|
+
describe: "Quick start: scaffold the default template (or --template) and run the usual setup (install dependencies, git init) without prompting. Linking is left to you since it needs a project choice.",
|
|
48
|
+
type: "boolean",
|
|
49
|
+
default: false
|
|
50
|
+
},
|
|
51
|
+
install: {
|
|
52
|
+
describe: "Install dependencies after scaffolding. In interactive mode this is offered as a prompt; use --no-install to skip without being asked.",
|
|
53
|
+
type: "boolean",
|
|
54
|
+
default: true
|
|
55
|
+
},
|
|
56
|
+
git: {
|
|
57
|
+
describe: "Initialize a git repository after scaffolding. In interactive mode this is offered as a prompt; use --no-git to skip without being asked.",
|
|
58
|
+
type: "boolean",
|
|
59
|
+
default: true
|
|
60
|
+
},
|
|
61
|
+
link: {
|
|
62
|
+
describe: `Run \`${getCliName()} link\` in the scaffolded directory after installing. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
|
|
63
|
+
type: "boolean",
|
|
64
|
+
default: true
|
|
65
|
+
}
|
|
66
|
+
}).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap my-app --template hono --agent", "Scaffold without prompting and emit the JSON state machine for AI agents").strict();
|
|
67
|
+
const handler = async (props) => {
|
|
68
|
+
if (props.listTemplates) {
|
|
69
|
+
const templates = await fetchTemplates();
|
|
70
|
+
for (const t of templates) {
|
|
71
|
+
const services = t.services && t.services.length > 0 ? ` [${t.services.join(" · ")}]` : "";
|
|
72
|
+
process.stdout.write(`${t.id} — ${t.description}${services}\n`);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (props.agent) {
|
|
77
|
+
await runAgentSafely(props);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const templates = await resolveTemplateList(props);
|
|
81
|
+
const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
|
|
82
|
+
const template = await resolveSelectedTemplate(props, interactive, templates);
|
|
83
|
+
const targetDir = await resolveTargetDir(props, interactive, template);
|
|
84
|
+
ensureTargetUsable(targetDir, props.force);
|
|
85
|
+
await scaffold(template, targetDir);
|
|
86
|
+
printScaffolded(template, targetDir);
|
|
87
|
+
await runPostScaffoldSteps(props, targetDir, interactive);
|
|
95
88
|
};
|
|
96
89
|
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const resolveTemplateList = async (props) => props.template && findTemplate(FALLBACK_TEMPLATES, props.template)
|
|
102
|
-
? FALLBACK_TEMPLATES
|
|
103
|
-
: fetchTemplates();
|
|
90
|
+
* The template list to choose from. When --template is given we try the
|
|
91
|
+
* built-in fallback list first to avoid a network round-trip, only fetching the
|
|
92
|
+
* remote manifest if the id isn't one of the defaults.
|
|
93
|
+
*/
|
|
94
|
+
const resolveTemplateList = async (props) => props.template && findTemplate(FALLBACK_TEMPLATES, props.template) ? FALLBACK_TEMPLATES : fetchTemplates();
|
|
104
95
|
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
96
|
+
* The picker label for a template: the title first, then the Neon services it
|
|
97
|
+
* uses as a dim, italic suffix, e.g. "Hono API … Postgres · Functions". The
|
|
98
|
+
* suffix is styled with chalk.dim (and italic) only — never a foreground color —
|
|
99
|
+
* so it survives the cyan/underline `prompts` paints over the focused row: dim
|
|
100
|
+
* and italic reset with their own SGRs, leaving the row's color and underline
|
|
101
|
+
* intact. Descriptions are intentionally omitted to keep the picker uncluttered.
|
|
102
|
+
*/
|
|
112
103
|
const formatTemplateTitle = (template) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
return `${template.title} ${chalk.dim.italic(template.services.join(" · "))}`;
|
|
104
|
+
if (!template.services || template.services.length === 0) return template.title;
|
|
105
|
+
return `${template.title} ${chalk.dim.italic(template.services.join(" · "))}`;
|
|
117
106
|
};
|
|
118
107
|
const resolveSelectedTemplate = async (props, interactive, templates) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
title: formatTemplateTitle(template),
|
|
145
|
-
value: template.id,
|
|
146
|
-
})),
|
|
147
|
-
initial: 0,
|
|
148
|
-
});
|
|
149
|
-
const template = findTemplate(templates, id);
|
|
150
|
-
if (!template) {
|
|
151
|
-
throw new Error("No template selected.");
|
|
152
|
-
}
|
|
153
|
-
return template;
|
|
108
|
+
if (props.template) {
|
|
109
|
+
const template = findTemplate(templates, props.template);
|
|
110
|
+
if (!template) throw new Error(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`);
|
|
111
|
+
return template;
|
|
112
|
+
}
|
|
113
|
+
if (props.default) {
|
|
114
|
+
const fallback = templates[0];
|
|
115
|
+
if (!fallback) throw new Error("No templates available to scaffold from.");
|
|
116
|
+
return fallback;
|
|
117
|
+
}
|
|
118
|
+
if (!interactive) throw new Error(`No template selected. Re-run in an interactive terminal to pick one, or pass --template <id>. Available templates: ${templateIds(templates)}.`);
|
|
119
|
+
const { id } = await prompts({
|
|
120
|
+
onState: onPromptState,
|
|
121
|
+
type: "select",
|
|
122
|
+
name: "id",
|
|
123
|
+
message: "Which template would you like to use?",
|
|
124
|
+
choices: templates.map((template) => ({
|
|
125
|
+
title: formatTemplateTitle(template),
|
|
126
|
+
value: template.id
|
|
127
|
+
})),
|
|
128
|
+
initial: 0
|
|
129
|
+
});
|
|
130
|
+
const template = findTemplate(templates, id);
|
|
131
|
+
if (!template) throw new Error("No template selected.");
|
|
132
|
+
return template;
|
|
154
133
|
};
|
|
155
134
|
const resolveTargetDir = async (props, interactive, template) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
initial: defaultDirName(template),
|
|
172
|
-
validate: (input) => input && input.trim().length > 0
|
|
173
|
-
? true
|
|
174
|
-
: 'Enter a directory (use "." for the current directory).',
|
|
175
|
-
});
|
|
176
|
-
dir = String(value).trim();
|
|
177
|
-
}
|
|
178
|
-
return resolve(process.cwd(), dir === "." ? "" : dir);
|
|
135
|
+
let dir = props.directory;
|
|
136
|
+
if (dir === void 0) {
|
|
137
|
+
if (props.default) return resolve(process.cwd(), defaultDirName(template));
|
|
138
|
+
if (!interactive) throw new Error(`No target directory given. Pass one, e.g. \`${getCliName()} bootstrap my-app\` (or "." for the current directory).`);
|
|
139
|
+
const { value } = await prompts({
|
|
140
|
+
onState: onPromptState,
|
|
141
|
+
type: "text",
|
|
142
|
+
name: "value",
|
|
143
|
+
message: "Where should we scaffold your project?",
|
|
144
|
+
initial: defaultDirName(template),
|
|
145
|
+
validate: (input) => input && input.trim().length > 0 ? true : "Enter a directory (use \".\" for the current directory)."
|
|
146
|
+
});
|
|
147
|
+
dir = String(value).trim();
|
|
148
|
+
}
|
|
149
|
+
return resolve(process.cwd(), dir === "." ? "" : dir);
|
|
179
150
|
};
|
|
180
151
|
const defaultDirName = (template) => template.source.subdir.split("/").pop() || template.id;
|
|
181
152
|
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
153
|
+
* Download and materialize the template into `targetDir`. The actual
|
|
154
|
+
* download/extract/write lives in `src/init/bootstrap.ts`, shared with `neon init`
|
|
155
|
+
* (exec-bit and symlink fidelity, graceful symlink fallback); here we just
|
|
156
|
+
* frame it with progress logging. Returns the number of files written.
|
|
157
|
+
*/
|
|
187
158
|
const scaffold = async (template, targetDir) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
|
|
195
|
-
return filesWritten;
|
|
159
|
+
log.info("Fetching template \"%s\" from GitHub…", template.id);
|
|
160
|
+
const filesWritten = await scaffoldTemplate(template, targetDir, { onWarn: (message) => {
|
|
161
|
+
log.warning(message);
|
|
162
|
+
} });
|
|
163
|
+
log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
|
|
164
|
+
return filesWritten;
|
|
196
165
|
};
|
|
197
|
-
// ----------------------------------------------------------------------------
|
|
198
|
-
// Post-scaffold steps (install dependencies, git init, link to a Neon project)
|
|
199
|
-
// ----------------------------------------------------------------------------
|
|
200
166
|
/**
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
167
|
+
* After a human scaffold, offer the things you almost always do next: install
|
|
168
|
+
* dependencies, initialize a git repo, and link the directory to a Neon
|
|
169
|
+
* project. In an interactive terminal each is a y/n prompt (skippable up front
|
|
170
|
+
* with --no-install / --no-git / --no-link); `--default` runs install + git
|
|
171
|
+
* without asking; otherwise we just print the manual steps so nothing runs
|
|
172
|
+
* behind the user's back. Agent mode never reaches here — it returns these as
|
|
173
|
+
* structured `next_steps` instead (see {@link runAgent}).
|
|
174
|
+
*/
|
|
209
175
|
const runPostScaffoldSteps = async (props, targetDir, interactive) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
else if (await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`)) {
|
|
247
|
-
await runNeonLink(props, targetDir);
|
|
248
|
-
// link prints its own summary (and pulls env), so end with just the run hint.
|
|
249
|
-
printNextSteps(targetDir, pm, { installed, suggestLink: false });
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
printNextSteps(targetDir, pm, { installed, suggestLink: true });
|
|
176
|
+
const inferred = inferPackageManager(targetDir);
|
|
177
|
+
const defaultPm = resolvePackageManager(targetDir);
|
|
178
|
+
if (props.default) {
|
|
179
|
+
await runDefaultSteps(props, targetDir, defaultPm);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!interactive) {
|
|
183
|
+
printNextSteps(targetDir, defaultPm, {
|
|
184
|
+
installed: false,
|
|
185
|
+
suggestLink: true
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
let pm = defaultPm;
|
|
190
|
+
let installed = false;
|
|
191
|
+
if (props.install && await confirm(installPrompt(inferred))) {
|
|
192
|
+
pm = inferred ?? await selectPackageManager();
|
|
193
|
+
installed = await runCommand(pm, installArgs(pm), targetDir);
|
|
194
|
+
}
|
|
195
|
+
if (props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?")) await initGitRepo(targetDir);
|
|
196
|
+
if (props.link) {
|
|
197
|
+
if (!installed && hasNeonConfig(targetDir)) log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
|
|
198
|
+
else if (await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`)) {
|
|
199
|
+
await runNeonLink(props, targetDir);
|
|
200
|
+
printNextSteps(targetDir, pm, {
|
|
201
|
+
installed,
|
|
202
|
+
suggestLink: false
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
printNextSteps(targetDir, pm, {
|
|
208
|
+
installed,
|
|
209
|
+
suggestLink: true
|
|
210
|
+
});
|
|
254
211
|
};
|
|
255
|
-
const installPrompt = (
|
|
256
|
-
? `Install dependencies with ${detected}?`
|
|
257
|
-
: "Install dependencies?";
|
|
212
|
+
const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
|
|
258
213
|
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
214
|
+
* `--default` quick start: run install + git init without prompting, honoring
|
|
215
|
+
* --no-install / --no-git. Linking is intentionally skipped — it needs an
|
|
216
|
+
* org/project choice we can't make non-interactively — so we point at it in the
|
|
217
|
+
* closing hint instead.
|
|
218
|
+
*/
|
|
264
219
|
const runDefaultSteps = async (props, targetDir, pm) => {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
printNextSteps(targetDir, pm, { installed, suggestLink: true });
|
|
220
|
+
log.info("Quick start (--default): running setup without prompting.");
|
|
221
|
+
let installed = false;
|
|
222
|
+
if (props.install) installed = await runCommand(pm, installArgs(pm), targetDir);
|
|
223
|
+
if (props.git && !isGitRepo(targetDir)) await initGitRepo(targetDir);
|
|
224
|
+
printNextSteps(targetDir, pm, {
|
|
225
|
+
installed,
|
|
226
|
+
suggestLink: true
|
|
227
|
+
});
|
|
274
228
|
};
|
|
275
229
|
const isGitRepo = (dir) => existsSync(join(dir, ".git"));
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
230
|
+
const NEON_CONFIG_FILENAMES = [
|
|
231
|
+
"neon.ts",
|
|
232
|
+
"neon.mts",
|
|
233
|
+
"neon.js",
|
|
234
|
+
"neon.mjs"
|
|
235
|
+
];
|
|
279
236
|
const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
|
|
280
237
|
/**
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
238
|
+
* Initialize a git repository in the scaffolded directory. Just `git init` — we
|
|
239
|
+
* deliberately don't auto-commit, both to avoid failing on a machine with no
|
|
240
|
+
* git identity configured and to leave the first commit to the user.
|
|
241
|
+
*/
|
|
285
242
|
const initGitRepo = async (dir) => {
|
|
286
|
-
|
|
243
|
+
await runCommand("git", ["init"], dir);
|
|
287
244
|
};
|
|
288
245
|
const confirm = async (message) => {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
246
|
+
const { value } = await prompts({
|
|
247
|
+
onState: onPromptState,
|
|
248
|
+
type: "confirm",
|
|
249
|
+
name: "value",
|
|
250
|
+
message,
|
|
251
|
+
initial: true
|
|
252
|
+
});
|
|
253
|
+
return value === true;
|
|
297
254
|
};
|
|
298
255
|
/**
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
256
|
+
* Ask which package manager to install with when we couldn't infer one from the
|
|
257
|
+
* invocation. Offers the managers actually installed (npm preselected); with
|
|
258
|
+
* one or none installed there's nothing to choose, so it returns that one (or
|
|
259
|
+
* npm) without prompting. A cancelled prompt falls back to npm.
|
|
260
|
+
*/
|
|
304
261
|
const selectPackageManager = async () => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
});
|
|
320
|
-
return pm ?? "npm";
|
|
262
|
+
const installed = installedPackageManagers();
|
|
263
|
+
if (installed.length <= 1) return installed[0] ?? "npm";
|
|
264
|
+
const { pm } = await prompts({
|
|
265
|
+
onState: onPromptState,
|
|
266
|
+
type: "select",
|
|
267
|
+
name: "pm",
|
|
268
|
+
message: "Which package manager should we use?",
|
|
269
|
+
choices: installed.map((manager) => ({
|
|
270
|
+
title: manager,
|
|
271
|
+
value: manager
|
|
272
|
+
})),
|
|
273
|
+
initial: Math.max(0, installed.indexOf("npm"))
|
|
274
|
+
});
|
|
275
|
+
return pm ?? "npm";
|
|
321
276
|
};
|
|
322
277
|
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
278
|
+
* Re-invoke this same CLI as `neon link` inside the scaffolded directory, so the
|
|
279
|
+
* new project's `.neon` context (and pulled `.env`) land in the right place and
|
|
280
|
+
* link's own interactive picker drives org/project/branch selection. Re-execing
|
|
281
|
+
* (rather than calling the handler in-process) keeps link running with `cwd` set
|
|
282
|
+
* to the target dir, which is where its env pull writes.
|
|
283
|
+
*/
|
|
329
284
|
const runNeonLink = async (props, targetDir) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (props.configDir) {
|
|
337
|
-
args.push("--config-dir", props.configDir);
|
|
338
|
-
}
|
|
339
|
-
if (props.profile) {
|
|
340
|
-
args.push("--profile", props.profile);
|
|
341
|
-
}
|
|
342
|
-
args.push("--api-host", props.apiHost, "--output", props.output);
|
|
343
|
-
// An explicit `--api-key` has nowhere else to go — the child cannot re-resolve a flag we
|
|
344
|
-
// were given — so it travels in the environment, which neither `ps` nor our own logging
|
|
345
|
-
// exposes. A profile needs none of this; the child reads the credential itself.
|
|
346
|
-
const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
|
|
347
|
-
await runCommand(process.execPath, args, targetDir, explicitKey ? { NEON_API_KEY: explicitKey } : undefined);
|
|
285
|
+
const args = [process.argv[1], "link"];
|
|
286
|
+
if (props.configDir) args.push("--config-dir", props.configDir);
|
|
287
|
+
if (props.profile) args.push("--profile", props.profile);
|
|
288
|
+
args.push("--api-host", props.apiHost, "--output", props.output);
|
|
289
|
+
const explicitKey = props.profile ? "" : credentialInputs().apiKeyFlag;
|
|
290
|
+
await runCommand(process.execPath, args, targetDir, explicitKey ? { NEON_API_KEY: explicitKey } : void 0);
|
|
348
291
|
};
|
|
349
292
|
const printScaffolded = (template, targetDir) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
? "the current directory"
|
|
353
|
-
: displayDir(targetDir));
|
|
293
|
+
log.info("");
|
|
294
|
+
log.info("Done. Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
|
|
354
295
|
};
|
|
355
296
|
/**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
297
|
+
* The closing "Next steps" hint. Skips `cd` for the current directory, omits
|
|
298
|
+
* the install line once deps are in, and only nudges `neon link` when linking
|
|
299
|
+
* wasn't already offered/run — so the user never sees a step they just did.
|
|
300
|
+
*/
|
|
360
301
|
const printNextSteps = (targetDir, pm, opts) => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
if (opts.suggestLink) {
|
|
370
|
-
log.info(` ${getCliName()} link`);
|
|
371
|
-
}
|
|
372
|
-
log.info(" See the README to run it.");
|
|
373
|
-
log.info("");
|
|
302
|
+
log.info("");
|
|
303
|
+
log.info("Next steps:");
|
|
304
|
+
if (!isCurrentDir(targetDir)) log.info(" cd %s", displayDir(targetDir));
|
|
305
|
+
if (!opts.installed) log.info(" %s", formatInstallCommand(pm));
|
|
306
|
+
if (opts.suggestLink) log.info(` ${getCliName()} link`);
|
|
307
|
+
log.info(" See the README to run it.");
|
|
308
|
+
log.info("");
|
|
374
309
|
};
|
|
375
310
|
const runAgentSafely = async (props) => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
311
|
+
try {
|
|
312
|
+
await runAgent(props);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
emitAgent(toAgentError(err));
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
383
317
|
};
|
|
384
318
|
/**
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
319
|
+
* The `--agent` flow: resolve what the flags determine and emit one JSON object
|
|
320
|
+
* describing either the next input needed (`needs_template` / `needs_directory`)
|
|
321
|
+
* or the terminal result (`scaffolded`). Unlike interactive mode it never
|
|
322
|
+
* prompts and never runs install/git/link itself — those come back as structured
|
|
323
|
+
* `next_steps` so the agent can confirm with the user and run them (the link
|
|
324
|
+
* step chains into `neon link --agent`).
|
|
325
|
+
*/
|
|
392
326
|
const runAgent = async (props) => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
327
|
+
if (!props.template) {
|
|
328
|
+
const templates = await fetchTemplates();
|
|
329
|
+
emitAgent({
|
|
330
|
+
status: "needs_template",
|
|
331
|
+
instruction: `Ask the user which template to scaffold, then re-run the next_command_template with the chosen --template value${props.directory ? "" : " and a target directory"}.`,
|
|
332
|
+
options: templates.map((template) => ({
|
|
333
|
+
id: template.id,
|
|
334
|
+
title: template.title,
|
|
335
|
+
description: template.description,
|
|
336
|
+
...template.services ? { services: template.services } : {}
|
|
337
|
+
})),
|
|
338
|
+
next_command_template: `${getCliName()} bootstrap --agent ${props.directory ? shellArg(props.directory) : "<directory>"} --template <template_id>`
|
|
339
|
+
});
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const templates = await resolveTemplateList(props);
|
|
343
|
+
const template = findTemplate(templates, props.template);
|
|
344
|
+
if (!template) throw new BootstrapInputError(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`, "UNKNOWN_TEMPLATE");
|
|
345
|
+
if (props.directory === void 0) {
|
|
346
|
+
emitAgent({
|
|
347
|
+
status: "needs_directory",
|
|
348
|
+
instruction: "Ask the user which directory to scaffold into (use \".\" for the current directory), then re-run the next_command_template with it.",
|
|
349
|
+
next_command_template: `${getCliName()} bootstrap --agent <directory> --template ${shellArg(template.id)}`
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const targetDir = resolve(process.cwd(), props.directory === "." ? "" : props.directory);
|
|
354
|
+
ensureTargetUsable(targetDir, props.force);
|
|
355
|
+
const filesWritten = await scaffold(template, targetDir);
|
|
356
|
+
const dir = displayDir(targetDir);
|
|
357
|
+
const runIn = isCurrentDir(targetDir) ? "" : `cd ${shellArg(dir)} && `;
|
|
358
|
+
const installPm = resolvePackageManager(targetDir);
|
|
359
|
+
emitAgent({
|
|
360
|
+
status: "scaffolded",
|
|
361
|
+
directory: targetDir,
|
|
362
|
+
template: {
|
|
363
|
+
id: template.id,
|
|
364
|
+
title: template.title
|
|
365
|
+
},
|
|
366
|
+
files_written: filesWritten,
|
|
367
|
+
next_steps: [
|
|
368
|
+
{
|
|
369
|
+
action: "install_dependencies",
|
|
370
|
+
instruction: `Ask the user whether to install dependencies, then run this in the project directory. ${DO_NOT_SUBSTITUTE_HINT}`,
|
|
371
|
+
command: `${runIn}${formatInstallCommand(installPm)}`
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
action: "initialize_git",
|
|
375
|
+
instruction: "Ask the user whether to initialize a git repository in the project directory.",
|
|
376
|
+
command: `${runIn}git init`
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
action: "link_neon_project",
|
|
380
|
+
instruction: "Ask the user whether to link the project to a Neon project now. This runs the link state machine — follow its JSON output for the next step.",
|
|
381
|
+
command: `${runIn}${getCliName()} link --agent`
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
message: `Scaffolded "${template.title}" (${filesWritten} files) into ${dir}. Offer the next_steps to the user: install dependencies, initialize git, then link a Neon project.`
|
|
385
|
+
});
|
|
450
386
|
};
|
|
451
387
|
const emitAgent = (response) => {
|
|
452
|
-
|
|
388
|
+
process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
453
389
|
};
|
|
454
390
|
const toAgentError = (err) => {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
391
|
+
if (err instanceof BootstrapInputError) return {
|
|
392
|
+
status: "error",
|
|
393
|
+
code: err.agentCode,
|
|
394
|
+
message: err.message
|
|
395
|
+
};
|
|
396
|
+
if (err instanceof Error) return {
|
|
397
|
+
status: "error",
|
|
398
|
+
code: "INTERNAL_ERROR",
|
|
399
|
+
message: err.message
|
|
400
|
+
};
|
|
401
|
+
return {
|
|
402
|
+
status: "error",
|
|
403
|
+
code: "INTERNAL_ERROR",
|
|
404
|
+
message: String(err)
|
|
405
|
+
};
|
|
466
406
|
};
|
|
467
|
-
// ----------------------------------------------------------------------------
|
|
468
|
-
// Path display helpers
|
|
469
|
-
// ----------------------------------------------------------------------------
|
|
470
407
|
const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
|
|
471
408
|
/**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
409
|
+
* The path to show the user: the bare relative path for the common
|
|
410
|
+
* `bootstrap my-app` case, the absolute path when the target sits outside the
|
|
411
|
+
* cwd (a deep `../../..` is noise), and "." for the current directory.
|
|
412
|
+
*/
|
|
476
413
|
const displayDir = (targetDir) => {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
return rel.startsWith("..") ? targetDir : rel;
|
|
414
|
+
const rel = relative(process.cwd(), targetDir);
|
|
415
|
+
if (rel === "") return ".";
|
|
416
|
+
return rel.startsWith("..") ? targetDir : rel;
|
|
482
417
|
};
|
|
483
418
|
const shellArg = (value) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
419
|
+
if (/^[A-Za-z0-9._:/-]+$/.test(value)) return value;
|
|
420
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
488
421
|
};
|
|
489
422
|
const onPromptState = (state) => {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
423
|
+
if (state.aborted) {
|
|
424
|
+
process.stdout.write("\x1B[?25h");
|
|
425
|
+
process.stdout.write("\n");
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
495
428
|
};
|
|
429
|
+
//#endregion
|
|
430
|
+
export { bootstrap_exports, builder, command, describe, handler };
|