gt 2.22.4 → 2.23.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/CHANGELOG.md +28 -0
- package/dist/auth/errors.d.ts +1 -0
- package/dist/auth/errors.js +12 -2
- package/dist/auth/errors.js.map +1 -1
- package/dist/auth/interactiveLogin.d.ts +2 -0
- package/dist/auth/interactiveLogin.js +22 -0
- package/dist/auth/interactiveLogin.js.map +1 -0
- package/dist/auth/oauth.js +2 -2
- package/dist/auth/oauth.js.map +1 -1
- package/dist/cli/base.d.ts +22 -4
- package/dist/cli/base.js +328 -223
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/api.js +15 -16
- package/dist/cli/commands/api.js.map +1 -1
- package/dist/cli/node.js +1 -1
- package/dist/cli/node.js.map +1 -1
- package/dist/cli/react.js +1 -1
- package/dist/cli/react.js.map +1 -1
- package/dist/cli/vue.js +2 -2
- package/dist/cli/vue.js.map +1 -1
- package/dist/config/generateSettings.d.ts +2 -0
- package/dist/config/generateSettings.js +13 -6
- package/dist/config/generateSettings.js.map +1 -1
- package/dist/config/resolveConfig.js +0 -4
- package/dist/config/resolveConfig.js.map +1 -1
- package/dist/config/validateSettings.js +5 -4
- package/dist/config/validateSettings.js.map +1 -1
- package/dist/console/{inkLocaleData.d.ts → localeOptions.d.ts} +7 -2
- package/dist/console/{inkLocaleData.js → localeOptions.js} +3 -8
- package/dist/console/localeOptions.js.map +1 -0
- package/dist/console/logger.d.ts +7 -0
- package/dist/console/logger.js +48 -85
- package/dist/console/logger.js.map +1 -1
- package/dist/console/logging.d.ts +11 -1
- package/dist/console/logging.js +88 -100
- package/dist/console/logging.js.map +1 -1
- package/dist/formats/files/aggregateFiles.js +1 -1
- package/dist/formats/files/supportedFiles.d.ts +3 -0
- package/dist/formats/files/supportedFiles.js +10 -1
- package/dist/formats/files/supportedFiles.js.map +1 -1
- package/dist/formats/yaml/mergeYaml.js +1 -1
- package/dist/fs/config/loadConfig.d.ts +2 -0
- package/dist/fs/config/loadConfig.js +5 -1
- package/dist/fs/config/loadConfig.js.map +1 -1
- package/dist/fs/config/setupConfig.d.ts +18 -9
- package/dist/fs/config/setupConfig.js +51 -31
- package/dist/fs/config/setupConfig.js.map +1 -1
- package/dist/fs/createLoadTranslationsFile.d.ts +22 -1
- package/dist/fs/createLoadTranslationsFile.js +43 -20
- package/dist/fs/createLoadTranslationsFile.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/hooks/postProcess.d.ts +1 -2
- package/dist/hooks/postProcess.js +1 -1
- package/dist/hooks/postProcess.js.map +1 -1
- package/dist/locadex/setupFlow.d.ts +4 -1
- package/dist/locadex/setupFlow.js +5 -3
- package/dist/locadex/setupFlow.js.map +1 -1
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
- package/dist/react/jsx/utils/parseString.js +1 -1
- package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
- package/dist/setup/buildTools/index.d.ts +44 -0
- package/dist/setup/buildTools/index.js +10 -0
- package/dist/setup/buildTools/index.js.map +1 -0
- package/dist/setup/buildTools/vite.d.ts +2 -0
- package/dist/setup/buildTools/vite.js +61 -0
- package/dist/setup/buildTools/vite.js.map +1 -0
- package/dist/setup/developmentCredentials.d.ts +40 -5
- package/dist/setup/developmentCredentials.js +119 -37
- package/dist/setup/developmentCredentials.js.map +1 -1
- package/dist/setup/instructions/base.md +1 -1
- package/dist/setup/onboarding.d.ts +131 -0
- package/dist/setup/onboarding.js +289 -0
- package/dist/setup/onboarding.js.map +1 -0
- package/dist/setup/setupTarget.d.ts +1 -0
- package/dist/setup/setupTarget.js +52 -0
- package/dist/setup/setupTarget.js.map +1 -0
- package/dist/setup/setupViteSPA.d.ts +32 -1
- package/dist/setup/setupViteSPA.js +110 -20
- package/dist/setup/setupViteSPA.js.map +1 -1
- package/dist/setup/userInput.d.ts +12 -3
- package/dist/setup/userInput.js +27 -11
- package/dist/setup/userInput.js.map +1 -1
- package/dist/setup/wizard.d.ts +19 -3
- package/dist/setup/wizard.js +119 -105
- package/dist/setup/wizard.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +13 -1
- package/dist/types/index.js.map +1 -0
- package/dist/utils/credentials.d.ts +9 -6
- package/dist/utils/credentials.js +36 -22
- package/dist/utils/credentials.js.map +1 -1
- package/dist/utils/monorepoVersionCheck.d.ts +1 -1
- package/dist/utils/monorepoVersionCheck.js +5 -5
- package/dist/utils/monorepoVersionCheck.js.map +1 -1
- package/dist/utils/packageManager.js +13 -14
- package/dist/utils/packageManager.js.map +1 -1
- package/package.json +5 -9
- package/dist/console/inkFields.d.ts +0 -23
- package/dist/console/inkFields.js +0 -88
- package/dist/console/inkFields.js.map +0 -1
- package/dist/console/inkLayout.d.ts +0 -7
- package/dist/console/inkLayout.js +0 -140
- package/dist/console/inkLayout.js.map +0 -1
- package/dist/console/inkLocaleData.js.map +0 -1
- package/dist/console/inkPrompts.d.ts +0 -8
- package/dist/console/inkPrompts.js +0 -499
- package/dist/console/inkPrompts.js.map +0 -1
- package/dist/console/inkSession.d.ts +0 -3
- package/dist/console/inkSession.js +0 -42
- package/dist/console/inkSession.js.map +0 -1
- package/dist/console/inkTerminal.d.ts +0 -4
- package/dist/console/inkTerminal.js +0 -15
- package/dist/console/inkTerminal.js.map +0 -1
- package/dist/console/inkTheme.d.ts +0 -1
- package/dist/console/inkTheme.js +0 -6
- package/dist/console/inkTheme.js.map +0 -1
- package/dist/console/inkTypes.d.ts +0 -69
- package/dist/console/inkTypes.js +0 -1
- package/dist/console/inkUtils.d.ts +0 -24
- package/dist/console/inkUtils.js +0 -87
- package/dist/console/inkUtils.js.map +0 -1
- package/dist/console/terminalSession.d.ts +0 -5
- package/dist/console/terminalSession.js +0 -38
- package/dist/console/terminalSession.js.map +0 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { logger } from "../console/logger.js";
|
|
2
|
+
import "../utils/constants.js";
|
|
3
|
+
import { displayHeader, getLastExitError, logErrorAndExit, stripAnsi } from "../console/logging.js";
|
|
4
|
+
import findFilepath from "../fs/findFilepath.js";
|
|
5
|
+
import { withJsonExtension } from "../fs/config/loadConfig.js";
|
|
6
|
+
import { SETUP_FILE_FORMATS } from "../formats/files/supportedFiles.js";
|
|
7
|
+
import { getFrameworkDisplayName, getReactFrameworkLibrary } from "./frameworkUtils.js";
|
|
8
|
+
import { SUPPORTED_REACT_FRAMEWORKS } from "../types/index.js";
|
|
9
|
+
import { getBuildToolSetup } from "./buildTools/index.js";
|
|
10
|
+
import { NoPackageManagerError, getPackageManager, packageManagers } from "../utils/packageManager.js";
|
|
11
|
+
import { installPackage } from "../utils/installPackage.js";
|
|
12
|
+
import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/internal";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import { Option } from "commander";
|
|
15
|
+
//#region src/setup/onboarding.ts
|
|
16
|
+
/** Flags for every configuration question; values prefill interactive runs. */
|
|
17
|
+
function attachConfigureFlags(command, sourceHelp) {
|
|
18
|
+
return reportJsonArgumentErrors(command).option("--src <paths...>", sourceHelp).option("-c, --config <path>", "Filepath to config file, by default gt.config.json", findFilepath(["gt.config.json"])).option("--no-interactive", "Never prompt; fail listing the options still needed. Automatic when stdin or stdout is not a terminal").option("--json", "Write JSON events (sign-in, handoff, result) to stdout and all other output to stderr; implies --no-interactive").option("--defaults", "Accept the recommended value for every local choice that no flag or gt.config.json answers; never creates projects or keys").option("--no-defaults", "Do not offer the recommended defaults").option("--default-locale <locale>", "Default locale, e.g. en").option("--locales <locales...>", "Locales to translate into, e.g. fr es; replaces the configured list").addOption(new Option("--storage <storage>", "Where GT translations are stored for GT runtimes").choices(["local", "cdn"])).option("--translations-dir <path>", "Directory for local GT translation files").addOption(new Option("--file-formats <formats...>", "Additional file formats to translate, or none").choices([...SETUP_FILE_FORMATS, "none"])).option("--file-patterns <patterns...>", "Glob patterns as <format>=<glob> including [locale], e.g. json=./locales/[locale]/*.json; selects the format").option("--live-translations", "Local Vite storage: set up live development translations (creates a development key)").option("--no-live-translations", "Skip live development translations").option("--dev-credentials", "Save a project ID and a new development key to .env.local").option("--no-dev-credentials", "Do not create development credentials").option("--project-id <id>", "Existing project for the development credentials").option("--create-project", "Create a new project for the development credentials").option("--org-id <id>", "Organization that owns --create-project").option("--project-name <name>", "Name for --create-project").addOption(new Option("--package-manager <id>", "Package manager for installs when it cannot be detected").choices(packageManagers.map((packageManager) => packageManager.id)));
|
|
19
|
+
}
|
|
20
|
+
let commandBeforeOnboarding;
|
|
21
|
+
function reportExitBeforeOnboarding(code) {
|
|
22
|
+
if (code === 0 || commandBeforeOnboarding === void 0) return;
|
|
23
|
+
fs.writeSync(process.stdout.fd, `${JSON.stringify({
|
|
24
|
+
type: "result",
|
|
25
|
+
command: commandBeforeOnboarding,
|
|
26
|
+
outcome: "failed",
|
|
27
|
+
completedSteps: [],
|
|
28
|
+
error: stripAnsi(getLastExitError() ?? "Setup exited before it started; see stderr")
|
|
29
|
+
})}\n`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pre-action hooks, such as the monorepo version check, can exit before
|
|
33
|
+
* runOnboarding installs its own result reporting; `--json` callers still
|
|
34
|
+
* get a result. runOnboarding takes over once it starts.
|
|
35
|
+
*/
|
|
36
|
+
function reportJsonExitsBeforeOnboarding(command) {
|
|
37
|
+
commandBeforeOnboarding = command;
|
|
38
|
+
process.once("exit", reportExitBeforeOnboarding);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Argument errors happen before the action runs, so `--json` callers would
|
|
42
|
+
* otherwise get no result. Commander prints the error to stderr, then calls
|
|
43
|
+
* this exit hook; returning lets it exit with the same code as before. The
|
|
44
|
+
* hook replaces an exit override inherited from the program.
|
|
45
|
+
*/
|
|
46
|
+
function reportJsonArgumentErrors(command) {
|
|
47
|
+
return command.exitOverride((error) => {
|
|
48
|
+
if (error.exitCode === 0) return;
|
|
49
|
+
if (!(command.opts().json === true || process.argv.includes("--json"))) return;
|
|
50
|
+
fs.writeSync(process.stdout.fd, `${JSON.stringify({
|
|
51
|
+
type: "result",
|
|
52
|
+
command: command.name(),
|
|
53
|
+
outcome: "failed",
|
|
54
|
+
completedSteps: [],
|
|
55
|
+
error: stripAnsi(error.message)
|
|
56
|
+
})}\n`);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Configuration flags plus the React application setup and Locadex choices. */
|
|
60
|
+
function attachInitFlags(command, sourceHelp) {
|
|
61
|
+
return attachConfigureFlags(command, sourceHelp).option("--locadex", "Mintlify and Next.js App Router: hand setup to the Locadex AI agent through GitHub instead").option("--no-locadex", "Set up this project locally").option("--react-setup", "React projects: install the GT library and add GTProvider or initializeGTSPA").option("--no-react-setup", "Do not modify application source").addOption(new Option("--framework <framework>", "React framework for --react-setup; overrides detection").choices(SUPPORTED_REACT_FRAMEWORKS)).option("--format", "Next.js App Router: format files changed by setup with the detected formatter").option("--no-format", "Leave files changed by setup unformatted");
|
|
62
|
+
}
|
|
63
|
+
/** Needs-input and validation failures, raised before setup changes files. */
|
|
64
|
+
var OnboardingError = class extends Error {
|
|
65
|
+
constructor(message, missingOptions = []) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.missingOptions = missingOptions;
|
|
68
|
+
this.name = "OnboardingError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function isInteractiveTerminal() {
|
|
72
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One setup run: how unanswered questions are resolved, which options are
|
|
76
|
+
* still missing, and which steps already changed the project.
|
|
77
|
+
*/
|
|
78
|
+
var OnboardingSession = class {
|
|
79
|
+
interactive;
|
|
80
|
+
json;
|
|
81
|
+
defaults = false;
|
|
82
|
+
completedSteps = [];
|
|
83
|
+
/** Manual steps left for a person; they make the outcome needs_human_action. */
|
|
84
|
+
humanActions = [];
|
|
85
|
+
missing = /* @__PURE__ */ new Set();
|
|
86
|
+
invalid = [];
|
|
87
|
+
reported = false;
|
|
88
|
+
constructor(command, options) {
|
|
89
|
+
this.command = command;
|
|
90
|
+
this.json = options.json === true;
|
|
91
|
+
this.interactive = options.interactive !== false && !this.json && isInteractiveTerminal();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Explicit flag, then existing config, then the recommended value when
|
|
95
|
+
* defaults were accepted, then a prompt. Noninteractive runs use the
|
|
96
|
+
* fallback of an optional question, or record the option as missing.
|
|
97
|
+
*/
|
|
98
|
+
async answer(option, { explicit, configured, recommended, fallback, ask }) {
|
|
99
|
+
if (explicit !== void 0) return explicit;
|
|
100
|
+
if (configured !== void 0) return configured;
|
|
101
|
+
if (this.defaults && recommended !== void 0) return recommended;
|
|
102
|
+
if (this.interactive) return ask();
|
|
103
|
+
if (fallback !== void 0) return fallback;
|
|
104
|
+
this.require(option);
|
|
105
|
+
}
|
|
106
|
+
require(option) {
|
|
107
|
+
this.missing.add(option);
|
|
108
|
+
}
|
|
109
|
+
reject(problem) {
|
|
110
|
+
this.invalid.push(problem);
|
|
111
|
+
}
|
|
112
|
+
/** Stops before any change when an answer is missing or invalid. */
|
|
113
|
+
assertResolved() {
|
|
114
|
+
if (this.missing.size === 0 && this.invalid.length === 0) return;
|
|
115
|
+
const missingOptions = [...this.missing];
|
|
116
|
+
throw new OnboardingError(createDiagnosticMessage({
|
|
117
|
+
source: "gt",
|
|
118
|
+
severity: "Error",
|
|
119
|
+
whatHappened: missingOptions.length > 0 ? `Setup needs these options: ${missingOptions.join(", ")}` : "Setup received invalid options",
|
|
120
|
+
reassurance: "No project files were changed",
|
|
121
|
+
details: this.invalid,
|
|
122
|
+
fix: "Pass the options (see --help), add --defaults to accept the recommended local choices, or rerun in an interactive terminal"
|
|
123
|
+
}), missingOptions);
|
|
124
|
+
}
|
|
125
|
+
step(name) {
|
|
126
|
+
this.completedSteps.push(name);
|
|
127
|
+
}
|
|
128
|
+
emit(event) {
|
|
129
|
+
if (!this.json) return;
|
|
130
|
+
if (event.type === "result") this.reported = true;
|
|
131
|
+
fs.writeSync(process.stdout.fd, `${JSON.stringify(event)}\n`);
|
|
132
|
+
}
|
|
133
|
+
/** A result for exits that bypass runOnboarding (e.g. settings validation). */
|
|
134
|
+
reportUnexpectedExit(code) {
|
|
135
|
+
if (this.reported || code === 0) return;
|
|
136
|
+
this.emit({
|
|
137
|
+
type: "result",
|
|
138
|
+
command: this.command,
|
|
139
|
+
outcome: "failed",
|
|
140
|
+
completedSteps: this.completedSteps,
|
|
141
|
+
error: stripAnsi(getLastExitError() ?? "Setup exited before it finished; see stderr")
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
function partialSetupWarning(session) {
|
|
146
|
+
return createDiagnosticMessage({
|
|
147
|
+
source: "gt",
|
|
148
|
+
severity: "Warning",
|
|
149
|
+
whatHappened: `Setup stopped after these steps: ${session.completedSteps.join(", ")}`,
|
|
150
|
+
why: "the error below happened before the remaining steps ran",
|
|
151
|
+
fix: `Review those changes, fix the error, then rerun \`gt ${session.command}\`; it reuses the saved configuration and credentials`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Runs one setup command: selects prompt and output modes, then reports the
|
|
156
|
+
* outcome. Failures exit nonzero with the steps that already ran.
|
|
157
|
+
*/
|
|
158
|
+
async function runOnboarding(command, options, run) {
|
|
159
|
+
commandBeforeOnboarding = void 0;
|
|
160
|
+
process.removeListener("exit", reportExitBeforeOnboarding);
|
|
161
|
+
const session = new OnboardingSession(command, options);
|
|
162
|
+
if (session.json) logger.setConsoleOutput("stderr");
|
|
163
|
+
const progressWasAnimated = logger.setAnimatedProgress(session.interactive);
|
|
164
|
+
const reportExit = (code) => session.reportUnexpectedExit(code);
|
|
165
|
+
process.once("exit", reportExit);
|
|
166
|
+
try {
|
|
167
|
+
const result = await run(session);
|
|
168
|
+
const needsHuman = result.outcome === "needs_human_action" || session.humanActions.length > 0;
|
|
169
|
+
session.emit({
|
|
170
|
+
type: "result",
|
|
171
|
+
command,
|
|
172
|
+
outcome: needsHuman ? "needs_human_action" : "success",
|
|
173
|
+
completedSteps: session.completedSteps,
|
|
174
|
+
...result.outcome === "needs_human_action" && { url: result.url },
|
|
175
|
+
...session.humanActions.length > 0 && { actions: session.humanActions }
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : formatDiagnosticErrorDetails(error) ?? "Setup failed";
|
|
179
|
+
session.emit({
|
|
180
|
+
type: "result",
|
|
181
|
+
command,
|
|
182
|
+
outcome: "failed",
|
|
183
|
+
completedSteps: session.completedSteps,
|
|
184
|
+
error: stripAnsi(message),
|
|
185
|
+
...error instanceof OnboardingError && error.missingOptions.length > 0 && { missingOptions: error.missingOptions }
|
|
186
|
+
});
|
|
187
|
+
if (session.completedSteps.length > 0) logger.warn(partialSetupWarning(session));
|
|
188
|
+
return logErrorAndExit(message);
|
|
189
|
+
} finally {
|
|
190
|
+
process.removeListener("exit", reportExit);
|
|
191
|
+
logger.setAnimatedProgress(progressWasAnimated);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function asRecord(value) {
|
|
195
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
196
|
+
}
|
|
197
|
+
/** Reads the setup config, failing before any change when it is unusable. */
|
|
198
|
+
function readSetupConfig(configFilepath) {
|
|
199
|
+
if (!fs.existsSync(configFilepath)) return {};
|
|
200
|
+
try {
|
|
201
|
+
const config = asRecord(JSON.parse(fs.readFileSync(configFilepath, "utf8")));
|
|
202
|
+
if (config) return config;
|
|
203
|
+
throw new Error("the file does not contain a JSON object");
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new OnboardingError(createDiagnosticMessage({
|
|
206
|
+
source: "gt",
|
|
207
|
+
severity: "Error",
|
|
208
|
+
whatHappened: `${configFilepath} is not a valid JSON config`,
|
|
209
|
+
reassurance: "No project files were changed",
|
|
210
|
+
details: formatDiagnosticErrorDetails(error),
|
|
211
|
+
fix: "Fix or remove the file, then rerun the command"
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const INIT_SOURCE_HELP = "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'";
|
|
216
|
+
function setupConfigPath(options) {
|
|
217
|
+
return options.config ? withJsonExtension(options.config) : "gt.config.json";
|
|
218
|
+
}
|
|
219
|
+
function getConfiguredFramework(config) {
|
|
220
|
+
const framework = config.framework;
|
|
221
|
+
return typeof framework === "string" && [...SUPPORTED_REACT_FRAMEWORKS, "mintlify"].includes(framework) ? framework : void 0;
|
|
222
|
+
}
|
|
223
|
+
/** JSON mode keeps the banner off stdout. */
|
|
224
|
+
function displaySetupHeader(session, message) {
|
|
225
|
+
if (session.json) logger.startCommand(message);
|
|
226
|
+
else displayHeader(message);
|
|
227
|
+
}
|
|
228
|
+
function describeDefaults(framework) {
|
|
229
|
+
const buildTool = getBuildToolSetup(framework?.name);
|
|
230
|
+
const translationsDir = buildTool?.defaultTranslationsDir ?? "./public/_gt";
|
|
231
|
+
if (framework?.type !== "react") return `Files saved locally in ${translationsDir}`;
|
|
232
|
+
return `${getReactFrameworkLibrary(framework)} & ${buildTool?.initializer ?? "GTProvider"}, ${getFrameworkDisplayName(framework)}, Files saved locally in ${translationsDir}`;
|
|
233
|
+
}
|
|
234
|
+
/** Setup patterns name where each locale's files live, so they need [locale]. */
|
|
235
|
+
function validateSetupPattern(pattern) {
|
|
236
|
+
return pattern.includes("[locale]") || `"${pattern}" must include [locale], e.g. ./locales/[locale]/*.json`;
|
|
237
|
+
}
|
|
238
|
+
/** Parses `--file-patterns <format>=<glob>` values, grouped by format. */
|
|
239
|
+
function parseFilePatterns(session, values = []) {
|
|
240
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const value of values) {
|
|
242
|
+
const separator = value.indexOf("=");
|
|
243
|
+
const format = value.slice(0, separator);
|
|
244
|
+
const pattern = value.slice(separator + 1).trim();
|
|
245
|
+
if (separator < 0 || !SETUP_FILE_FORMATS.includes(format) || !pattern) {
|
|
246
|
+
session.reject(`--file-patterns "${value}" must look like <format>=<glob> with a format of ${SETUP_FILE_FORMATS.join(", ")}`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const problem = validateSetupPattern(pattern);
|
|
250
|
+
if (problem !== true) {
|
|
251
|
+
session.reject(`--file-patterns "${value}": ${problem}`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
patterns.set(format, [...patterns.get(format) ?? [], pattern]);
|
|
255
|
+
}
|
|
256
|
+
return patterns;
|
|
257
|
+
}
|
|
258
|
+
/** Detects the package manager, or asks only when prompts are allowed. */
|
|
259
|
+
async function resolvePackageManager(session, specified) {
|
|
260
|
+
try {
|
|
261
|
+
return await getPackageManager(process.cwd(), specified, !session.interactive);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (!(error instanceof NoPackageManagerError)) throw error;
|
|
264
|
+
session.require("--package-manager");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function installWithProgress(session, packageName, packageManager, asDevDependency) {
|
|
269
|
+
const spinner = logger.createSpinner("timer");
|
|
270
|
+
spinner.start(`Installing ${packageName}${asDevDependency ? " as a dev dependency" : ""} with ${packageManager.name}...`);
|
|
271
|
+
try {
|
|
272
|
+
await installPackage(packageName, packageManager, asDevDependency);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
spinner.stop(`Could not install ${packageName}.`, 2);
|
|
275
|
+
throw new Error(createDiagnosticMessage({
|
|
276
|
+
source: "gt",
|
|
277
|
+
severity: "Error",
|
|
278
|
+
whatHappened: `Failed to install ${packageName}`,
|
|
279
|
+
details: formatDiagnosticErrorDetails(error),
|
|
280
|
+
fix: `Install it with \`${packageManager.name} ${packageManager.installCommand} ${packageName}\`, then rerun the command`
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
spinner.stop(`Installed ${packageName}.`);
|
|
284
|
+
session.step(`installed ${packageName}`);
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
export { INIT_SOURCE_HELP, OnboardingError, OnboardingSession, asRecord, attachConfigureFlags, attachInitFlags, describeDefaults, displaySetupHeader, getConfiguredFramework, installWithProgress, parseFilePatterns, readSetupConfig, reportJsonExitsBeforeOnboarding, resolvePackageManager, runOnboarding, setupConfigPath, validateSetupPattern };
|
|
288
|
+
|
|
289
|
+
//# sourceMappingURL=onboarding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.js","names":[],"sources":["../../src/setup/onboarding.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { Command, Option, type CommanderError } from 'commander';\nimport {\n createDiagnosticMessage,\n formatDiagnosticErrorDetails,\n} from 'generaltranslation/internal';\nimport findFilepath from '../fs/findFilepath.js';\nimport { withJsonExtension } from '../fs/config/loadConfig.js';\nimport { logger } from '../console/logger.js';\nimport {\n displayHeader,\n getLastExitError,\n logErrorAndExit,\n stripAnsi,\n} from '../console/logging.js';\nimport {\n SUPPORTED_REACT_FRAMEWORKS,\n type FrameworkObject,\n type SupportedReactFrameworks,\n} from '../types/index.js';\nimport { DEFAULT_TRANSLATIONS_DIR } from '../utils/constants.js';\nimport { getBuildToolSetup } from './buildTools/index.js';\nimport {\n getFrameworkDisplayName,\n getReactFrameworkLibrary,\n} from './frameworkUtils.js';\nimport {\n SETUP_FILE_FORMATS,\n type SetupFileFormat,\n} from '../formats/files/supportedFiles.js';\nimport {\n getPackageManager,\n NoPackageManagerError,\n packageManagers,\n type PackageManager,\n} from '../utils/packageManager.js';\nimport { installPackage } from '../utils/installPackage.js';\n\n/** Answers accepted by `gt configure` (and the gt-vue `gt init`). */\nexport type ConfigureOptions = {\n config: string;\n src?: string[];\n interactive?: boolean;\n json?: boolean;\n defaults?: boolean;\n defaultLocale?: string;\n locales?: string[];\n storage?: 'local' | 'cdn';\n translationsDir?: string;\n fileFormats?: Array<SetupFileFormat | 'none'>;\n filePatterns?: string[];\n liveTranslations?: boolean;\n devCredentials?: boolean;\n projectId?: string;\n createProject?: boolean;\n orgId?: string;\n projectName?: string;\n packageManager?: string;\n};\n\n/** Answers accepted by the React-aware `gt init`. */\nexport type InitOptions = ConfigureOptions & {\n locadex?: boolean;\n reactSetup?: boolean;\n framework?: SupportedReactFrameworks;\n format?: boolean;\n};\n\n/** Flags for every configuration question; values prefill interactive runs. */\nexport function attachConfigureFlags(\n command: Command,\n sourceHelp: string\n): Command {\n return reportJsonArgumentErrors(command)\n .option('--src <paths...>', sourceHelp)\n .option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n .option(\n '--no-interactive',\n 'Never prompt; fail listing the options still needed. Automatic when stdin or stdout is not a terminal'\n )\n .option(\n '--json',\n 'Write JSON events (sign-in, handoff, result) to stdout and all other output to stderr; implies --no-interactive'\n )\n .option(\n '--defaults',\n 'Accept the recommended value for every local choice that no flag or gt.config.json answers; never creates projects or keys'\n )\n .option('--no-defaults', 'Do not offer the recommended defaults')\n .option('--default-locale <locale>', 'Default locale, e.g. en')\n .option(\n '--locales <locales...>',\n 'Locales to translate into, e.g. fr es; replaces the configured list'\n )\n .addOption(\n new Option(\n '--storage <storage>',\n 'Where GT translations are stored for GT runtimes'\n ).choices(['local', 'cdn'])\n )\n .option(\n '--translations-dir <path>',\n 'Directory for local GT translation files'\n )\n .addOption(\n new Option(\n '--file-formats <formats...>',\n 'Additional file formats to translate, or none'\n ).choices([...SETUP_FILE_FORMATS, 'none'])\n )\n .option(\n '--file-patterns <patterns...>',\n 'Glob patterns as <format>=<glob> including [locale], e.g. json=./locales/[locale]/*.json; selects the format'\n )\n .option(\n '--live-translations',\n 'Local Vite storage: set up live development translations (creates a development key)'\n )\n .option('--no-live-translations', 'Skip live development translations')\n .option(\n '--dev-credentials',\n 'Save a project ID and a new development key to .env.local'\n )\n .option('--no-dev-credentials', 'Do not create development credentials')\n .option(\n '--project-id <id>',\n 'Existing project for the development credentials'\n )\n .option(\n '--create-project',\n 'Create a new project for the development credentials'\n )\n .option('--org-id <id>', 'Organization that owns --create-project')\n .option('--project-name <name>', 'Name for --create-project')\n .addOption(\n new Option(\n '--package-manager <id>',\n 'Package manager for installs when it cannot be detected'\n ).choices(packageManagers.map((packageManager) => packageManager.id))\n );\n}\n\nlet commandBeforeOnboarding: string | undefined;\n\nfunction reportExitBeforeOnboarding(code: number): void {\n if (code === 0 || commandBeforeOnboarding === undefined) return;\n fs.writeSync(\n process.stdout.fd,\n `${JSON.stringify({\n type: 'result',\n command: commandBeforeOnboarding,\n outcome: 'failed',\n completedSteps: [],\n error: stripAnsi(\n getLastExitError() ?? 'Setup exited before it started; see stderr'\n ),\n } satisfies OnboardingEvent)}\\n`\n );\n}\n\n/**\n * Pre-action hooks, such as the monorepo version check, can exit before\n * runOnboarding installs its own result reporting; `--json` callers still\n * get a result. runOnboarding takes over once it starts.\n */\nexport function reportJsonExitsBeforeOnboarding(command: string): void {\n commandBeforeOnboarding = command;\n process.once('exit', reportExitBeforeOnboarding);\n}\n\n/**\n * Argument errors happen before the action runs, so `--json` callers would\n * otherwise get no result. Commander prints the error to stderr, then calls\n * this exit hook; returning lets it exit with the same code as before. The\n * hook replaces an exit override inherited from the program.\n */\nfunction reportJsonArgumentErrors(command: Command): Command {\n return command.exitOverride((error: CommanderError) => {\n if (error.exitCode === 0) return; // help and version output\n // An option after the failing argument is not parsed yet.\n const json =\n command.opts().json === true || process.argv.includes('--json');\n if (!json) return;\n fs.writeSync(\n process.stdout.fd,\n `${JSON.stringify({\n type: 'result',\n command: command.name(),\n outcome: 'failed',\n completedSteps: [],\n error: stripAnsi(error.message),\n } satisfies OnboardingEvent)}\\n`\n );\n });\n}\n\n/** Configuration flags plus the React application setup and Locadex choices. */\nexport function attachInitFlags(command: Command, sourceHelp: string) {\n return attachConfigureFlags(command, sourceHelp)\n .option(\n '--locadex',\n 'Mintlify and Next.js App Router: hand setup to the Locadex AI agent through GitHub instead'\n )\n .option('--no-locadex', 'Set up this project locally')\n .option(\n '--react-setup',\n 'React projects: install the GT library and add GTProvider or initializeGTSPA'\n )\n .option('--no-react-setup', 'Do not modify application source')\n .addOption(\n new Option(\n '--framework <framework>',\n 'React framework for --react-setup; overrides detection'\n ).choices(SUPPORTED_REACT_FRAMEWORKS)\n )\n .option(\n '--format',\n 'Next.js App Router: format files changed by setup with the detected formatter'\n )\n .option('--no-format', 'Leave files changed by setup unformatted');\n}\n\nexport type OnboardingEvent =\n | {\n type: 'authorization_required';\n verificationUri: string;\n verificationUriComplete?: string;\n userCode: string;\n }\n | { type: 'handoff'; url: string; reason: 'locadex' }\n | {\n type: 'result';\n command: string;\n outcome: 'success' | 'needs_human_action' | 'failed';\n completedSteps: string[];\n url?: string;\n /** Manual steps setup could not do, for needs_human_action. */\n actions?: string[];\n missingOptions?: string[];\n error?: string;\n };\n\n/** Needs-input and validation failures, raised before setup changes files. */\nexport class OnboardingError extends Error {\n constructor(\n message: string,\n readonly missingOptions: string[] = []\n ) {\n super(message);\n this.name = 'OnboardingError';\n }\n}\n\nfunction isInteractiveTerminal(): boolean {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\n/**\n * One setup run: how unanswered questions are resolved, which options are\n * still missing, and which steps already changed the project.\n */\nexport class OnboardingSession {\n readonly interactive: boolean;\n readonly json: boolean;\n defaults = false;\n readonly completedSteps: string[] = [];\n /** Manual steps left for a person; they make the outcome needs_human_action. */\n readonly humanActions: string[] = [];\n private readonly missing = new Set<string>();\n private readonly invalid: string[] = [];\n private reported = false;\n\n constructor(\n readonly command: string,\n options: Pick<ConfigureOptions, 'json' | 'interactive'>\n ) {\n this.json = options.json === true;\n this.interactive =\n options.interactive !== false && !this.json && isInteractiveTerminal();\n }\n\n /**\n * Explicit flag, then existing config, then the recommended value when\n * defaults were accepted, then a prompt. Noninteractive runs use the\n * fallback of an optional question, or record the option as missing.\n */\n async answer<T>(\n option: string,\n {\n explicit,\n configured,\n recommended,\n fallback,\n ask,\n }: {\n explicit?: T;\n configured?: T;\n recommended?: T;\n fallback?: T;\n ask: () => Promise<T>;\n }\n ): Promise<T | undefined> {\n if (explicit !== undefined) return explicit;\n if (configured !== undefined) return configured;\n if (this.defaults && recommended !== undefined) return recommended;\n if (this.interactive) return ask();\n if (fallback !== undefined) return fallback;\n this.require(option);\n return undefined;\n }\n\n require(option: string): void {\n this.missing.add(option);\n }\n\n reject(problem: string): void {\n this.invalid.push(problem);\n }\n\n /** Stops before any change when an answer is missing or invalid. */\n assertResolved(): void {\n if (this.missing.size === 0 && this.invalid.length === 0) return;\n const missingOptions = [...this.missing];\n throw new OnboardingError(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened:\n missingOptions.length > 0\n ? `Setup needs these options: ${missingOptions.join(', ')}`\n : 'Setup received invalid options',\n reassurance: 'No project files were changed',\n details: this.invalid,\n fix: 'Pass the options (see --help), add --defaults to accept the recommended local choices, or rerun in an interactive terminal',\n }),\n missingOptions\n );\n }\n\n step(name: string): void {\n this.completedSteps.push(name);\n }\n\n emit(event: OnboardingEvent): void {\n if (!this.json) return;\n if (event.type === 'result') this.reported = true;\n // Synchronous so events survive an immediate process.exit.\n fs.writeSync(process.stdout.fd, `${JSON.stringify(event)}\\n`);\n }\n\n /** A result for exits that bypass runOnboarding (e.g. settings validation). */\n reportUnexpectedExit(code: number): void {\n if (this.reported || code === 0) return;\n this.emit({\n type: 'result',\n command: this.command,\n outcome: 'failed',\n completedSteps: this.completedSteps,\n error: stripAnsi(\n getLastExitError() ?? 'Setup exited before it finished; see stderr'\n ),\n });\n }\n}\n\nexport type OnboardingOutcome =\n | { outcome: 'success' }\n | { outcome: 'needs_human_action'; url: string };\n\nfunction partialSetupWarning(session: OnboardingSession): string {\n return createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened: `Setup stopped after these steps: ${session.completedSteps.join(', ')}`,\n why: 'the error below happened before the remaining steps ran',\n fix: `Review those changes, fix the error, then rerun \\`gt ${session.command}\\`; it reuses the saved configuration and credentials`,\n });\n}\n\n/**\n * Runs one setup command: selects prompt and output modes, then reports the\n * outcome. Failures exit nonzero with the steps that already ran.\n */\nexport async function runOnboarding(\n command: string,\n options: Pick<ConfigureOptions, 'json' | 'interactive'>,\n run: (session: OnboardingSession) => Promise<OnboardingOutcome>\n): Promise<void> {\n commandBeforeOnboarding = undefined;\n process.removeListener('exit', reportExitBeforeOnboarding);\n const session = new OnboardingSession(command, options);\n if (session.json) logger.setConsoleOutput('stderr');\n // Per-run modes; console routing is reset per command by BaseCLI.\n const progressWasAnimated = logger.setAnimatedProgress(session.interactive);\n const reportExit = (code: number) => session.reportUnexpectedExit(code);\n process.once('exit', reportExit);\n try {\n const result = await run(session);\n const needsHuman =\n result.outcome === 'needs_human_action' ||\n session.humanActions.length > 0;\n session.emit({\n type: 'result',\n command,\n outcome: needsHuman ? 'needs_human_action' : 'success',\n completedSteps: session.completedSteps,\n ...(result.outcome === 'needs_human_action' && { url: result.url }),\n ...(session.humanActions.length > 0 && {\n actions: session.humanActions,\n }),\n });\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : (formatDiagnosticErrorDetails(error) ?? 'Setup failed');\n session.emit({\n type: 'result',\n command,\n outcome: 'failed',\n completedSteps: session.completedSteps,\n error: stripAnsi(message),\n ...(error instanceof OnboardingError &&\n error.missingOptions.length > 0 && {\n missingOptions: error.missingOptions,\n }),\n });\n if (session.completedSteps.length > 0) {\n logger.warn(partialSetupWarning(session));\n }\n return logErrorAndExit(message);\n } finally {\n process.removeListener('exit', reportExit);\n logger.setAnimatedProgress(progressWasAnimated);\n }\n}\n\nexport function asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** Reads the setup config, failing before any change when it is unusable. */\nexport function readSetupConfig(\n configFilepath: string\n): Record<string, unknown> {\n if (!fs.existsSync(configFilepath)) return {};\n try {\n const config = asRecord(\n JSON.parse(fs.readFileSync(configFilepath, 'utf8'))\n );\n if (config) return config;\n throw new Error('the file does not contain a JSON object');\n } catch (error) {\n throw new OnboardingError(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: `${configFilepath} is not a valid JSON config`,\n reassurance: 'No project files were changed',\n details: formatDiagnosticErrorDetails(error),\n fix: 'Fix or remove the file, then rerun the command',\n })\n );\n }\n}\n\nexport const INIT_SOURCE_HELP =\n \"Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'\";\n\nexport function setupConfigPath(\n options: Pick<ConfigureOptions, 'config'>\n): string {\n return options.config ? withJsonExtension(options.config) : 'gt.config.json';\n}\n\nexport function getConfiguredFramework(\n config: Record<string, unknown>\n): SupportedReactFrameworks | 'mintlify' | undefined {\n const framework = config.framework;\n return typeof framework === 'string' &&\n [...SUPPORTED_REACT_FRAMEWORKS, 'mintlify'].includes(\n framework as SupportedReactFrameworks\n )\n ? (framework as SupportedReactFrameworks | 'mintlify')\n : undefined;\n}\n\n/** JSON mode keeps the banner off stdout. */\nexport function displaySetupHeader(\n session: OnboardingSession,\n message: string\n) {\n if (session.json) logger.startCommand(message);\n else displayHeader(message);\n}\n\nexport function describeDefaults(\n framework: FrameworkObject | undefined\n): string {\n const buildTool = getBuildToolSetup(framework?.name);\n const translationsDir =\n buildTool?.defaultTranslationsDir ?? DEFAULT_TRANSLATIONS_DIR;\n if (framework?.type !== 'react') {\n return `Files saved locally in ${translationsDir}`;\n }\n const library = getReactFrameworkLibrary(framework);\n const setup = buildTool?.initializer ?? 'GTProvider';\n return `${library} & ${setup}, ${getFrameworkDisplayName(framework)}, Files saved locally in ${translationsDir}`;\n}\n\n/** Setup patterns name where each locale's files live, so they need [locale]. */\nexport function validateSetupPattern(pattern: string): true | string {\n return (\n pattern.includes('[locale]') ||\n `\"${pattern}\" must include [locale], e.g. ./locales/[locale]/*.json`\n );\n}\n\n/** Parses `--file-patterns <format>=<glob>` values, grouped by format. */\nexport function parseFilePatterns(\n session: OnboardingSession,\n values: string[] = []\n): Map<SetupFileFormat, string[]> {\n const patterns = new Map<SetupFileFormat, string[]>();\n for (const value of values) {\n const separator = value.indexOf('=');\n const format = value.slice(0, separator) as SetupFileFormat;\n const pattern = value.slice(separator + 1).trim();\n if (separator < 0 || !SETUP_FILE_FORMATS.includes(format) || !pattern) {\n session.reject(\n `--file-patterns \"${value}\" must look like <format>=<glob> with a format of ${SETUP_FILE_FORMATS.join(', ')}`\n );\n continue;\n }\n const problem = validateSetupPattern(pattern);\n if (problem !== true) {\n session.reject(`--file-patterns \"${value}\": ${problem}`);\n continue;\n }\n patterns.set(format, [...(patterns.get(format) ?? []), pattern]);\n }\n return patterns;\n}\n\n/** Detects the package manager, or asks only when prompts are allowed. */\nexport async function resolvePackageManager(\n session: OnboardingSession,\n specified?: string\n): Promise<PackageManager | undefined> {\n try {\n return await getPackageManager(\n process.cwd(),\n specified,\n !session.interactive\n );\n } catch (error) {\n if (!(error instanceof NoPackageManagerError)) throw error;\n session.require('--package-manager');\n return undefined;\n }\n}\n\nexport async function installWithProgress(\n session: OnboardingSession,\n packageName: string,\n packageManager: PackageManager,\n asDevDependency: boolean\n): Promise<void> {\n const spinner = logger.createSpinner('timer');\n spinner.start(\n `Installing ${packageName}${asDevDependency ? ' as a dev dependency' : ''} with ${packageManager.name}...`\n );\n try {\n await installPackage(packageName, packageManager, asDevDependency);\n } catch (error) {\n spinner.stop(`Could not install ${packageName}.`, 2);\n throw new Error(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: `Failed to install ${packageName}`,\n details: formatDiagnosticErrorDetails(error),\n fix: `Install it with \\`${packageManager.name} ${packageManager.installCommand} ${packageName}\\`, then rerun the command`,\n })\n );\n }\n spinner.stop(`Installed ${packageName}.`);\n session.step(`installed ${packageName}`);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function exitIfUnsupportedSetupTarget(): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isPackageInstalled, searchForPackageJson } from "../utils/packageJson.js";
|
|
2
|
+
import { OnboardingError, asRecord } from "./onboarding.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { createDiagnosticMessage } from "generaltranslation/diagnostics";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
//#region src/setup/setupTarget.ts
|
|
8
|
+
const workspaceRootSetupError = createDiagnosticMessage({
|
|
9
|
+
source: "gt",
|
|
10
|
+
severity: "Error",
|
|
11
|
+
whatHappened: "The setup wizard cannot run from a monorepo workspace root",
|
|
12
|
+
why: "GT must be configured in the specific app you want to localize",
|
|
13
|
+
fix: "Change to that app's directory and rerun `npx gt@latest`"
|
|
14
|
+
});
|
|
15
|
+
const electronSetupError = createDiagnosticMessage({
|
|
16
|
+
source: "gt",
|
|
17
|
+
severity: "Error",
|
|
18
|
+
whatHappened: "The automatic setup wizard is not ready for Electron applications",
|
|
19
|
+
docsUrl: "https://generaltranslation.com/docs/react"
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Whether workspace package patterns name packages besides the root itself.
|
|
23
|
+
* A single app may list only '.' (or nothing, keeping pnpm settings there).
|
|
24
|
+
*/
|
|
25
|
+
function listsChildPackages(patterns) {
|
|
26
|
+
if (patterns === void 0 || patterns === null) return false;
|
|
27
|
+
if (!Array.isArray(patterns)) return true;
|
|
28
|
+
return patterns.some((pattern) => typeof pattern !== "string" || !/^(?:\.\/?|!.*)$/.test(pattern.trim()));
|
|
29
|
+
}
|
|
30
|
+
function isMonorepoRoot(packageJson) {
|
|
31
|
+
const pnpmWorkspace = path.join(process.cwd(), "pnpm-workspace.yaml");
|
|
32
|
+
if (fs.existsSync(pnpmWorkspace)) {
|
|
33
|
+
let packages;
|
|
34
|
+
try {
|
|
35
|
+
packages = asRecord(YAML.parse(fs.readFileSync(pnpmWorkspace, "utf8")))?.packages;
|
|
36
|
+
} catch {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (listsChildPackages(packages)) return true;
|
|
40
|
+
}
|
|
41
|
+
const workspaces = packageJson?.workspaces;
|
|
42
|
+
return listsChildPackages(Array.isArray(workspaces) ? workspaces : asRecord(workspaces)?.packages);
|
|
43
|
+
}
|
|
44
|
+
async function exitIfUnsupportedSetupTarget() {
|
|
45
|
+
const packageJson = await searchForPackageJson();
|
|
46
|
+
if (packageJson && isPackageInstalled("electron", packageJson, false, true)) throw new OnboardingError(electronSetupError);
|
|
47
|
+
if (isMonorepoRoot(packageJson)) throw new OnboardingError(workspaceRootSetupError);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { exitIfUnsupportedSetupTarget };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=setupTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupTarget.js","names":[],"sources":["../../src/setup/setupTarget.ts"],"sourcesContent":["import path from 'node:path';\nimport fs from 'node:fs';\nimport YAML from 'yaml';\nimport { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport {\n isPackageInstalled,\n searchForPackageJson,\n} from '../utils/packageJson.js';\nimport { asRecord, OnboardingError } from './onboarding.js';\n\nconst workspaceRootSetupError = createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: 'The setup wizard cannot run from a monorepo workspace root',\n why: 'GT must be configured in the specific app you want to localize',\n fix: \"Change to that app's directory and rerun `npx gt@latest`\",\n});\n\nconst electronSetupError = createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened:\n 'The automatic setup wizard is not ready for Electron applications',\n docsUrl: 'https://generaltranslation.com/docs/react',\n});\n\n/**\n * Whether workspace package patterns name packages besides the root itself.\n * A single app may list only '.' (or nothing, keeping pnpm settings there).\n */\nfunction listsChildPackages(patterns: unknown): boolean {\n if (patterns === undefined || patterns === null) return false;\n if (!Array.isArray(patterns)) return true;\n return patterns.some(\n (pattern) =>\n typeof pattern !== 'string' || !/^(?:\\.\\/?|!.*)$/.test(pattern.trim())\n );\n}\n\nfunction isMonorepoRoot(packageJson: Record<string, unknown> | null): boolean {\n const pnpmWorkspace = path.join(process.cwd(), 'pnpm-workspace.yaml');\n if (fs.existsSync(pnpmWorkspace)) {\n let packages: unknown;\n try {\n packages = asRecord(\n YAML.parse(fs.readFileSync(pnpmWorkspace, 'utf8'))\n )?.packages;\n } catch {\n return true; // Unreadable: keep refusing, as for any workspace file.\n }\n if (listsChildPackages(packages)) return true;\n }\n const workspaces = packageJson?.workspaces;\n return listsChildPackages(\n Array.isArray(workspaces) ? workspaces : asRecord(workspaces)?.packages\n );\n}\n\nexport async function exitIfUnsupportedSetupTarget(): Promise<void> {\n const packageJson = await searchForPackageJson();\n if (packageJson && isPackageInstalled('electron', packageJson, false, true)) {\n throw new OnboardingError(electronSetupError);\n }\n if (isMonorepoRoot(packageJson)) {\n throw new OnboardingError(workspaceRootSetupError);\n }\n}\n"],"mappings":";;;;;;;AAUA,MAAM,0BAA0B,wBAAwB;CACtD,QAAQ;CACR,UAAU;CACV,cAAc;CACd,KAAK;CACL,KAAK;CACN,CAAC;AAEF,MAAM,qBAAqB,wBAAwB;CACjD,QAAQ;CACR,UAAU;CACV,cACE;CACF,SAAS;CACV,CAAC;;;;;AAMF,SAAS,mBAAmB,UAA4B;AACtD,KAAI,aAAa,KAAA,KAAa,aAAa,KAAM,QAAO;AACxD,KAAI,CAAC,MAAM,QAAQ,SAAS,CAAE,QAAO;AACrC,QAAO,SAAS,MACb,YACC,OAAO,YAAY,YAAY,CAAC,kBAAkB,KAAK,QAAQ,MAAM,CAAC,CACzE;;AAGH,SAAS,eAAe,aAAsD;CAC5E,MAAM,gBAAgB,KAAK,KAAK,QAAQ,KAAK,EAAE,sBAAsB;AACrE,KAAI,GAAG,WAAW,cAAc,EAAE;EAChC,IAAI;AACJ,MAAI;AACF,cAAW,SACT,KAAK,MAAM,GAAG,aAAa,eAAe,OAAO,CAAC,CACnD,EAAE;UACG;AACN,UAAO;;AAET,MAAI,mBAAmB,SAAS,CAAE,QAAO;;CAE3C,MAAM,aAAa,aAAa;AAChC,QAAO,mBACL,MAAM,QAAQ,WAAW,GAAG,aAAa,SAAS,WAAW,EAAE,SAChE;;AAGH,eAAsB,+BAA8C;CAClE,MAAM,cAAc,MAAM,sBAAsB;AAChD,KAAI,eAAe,mBAAmB,YAAY,aAAa,OAAO,KAAK,CACzE,OAAM,IAAI,gBAAgB,mBAAmB;AAE/C,KAAI,eAAe,YAAY,CAC7B,OAAM,IAAI,gBAAgB,wBAAwB"}
|
|
@@ -4,6 +4,37 @@ type SetupViteSPAOptions = {
|
|
|
4
4
|
defaultLocale: string;
|
|
5
5
|
locales: string[];
|
|
6
6
|
translationsDir?: string;
|
|
7
|
+
previousTranslationsDir?: string;
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Reads the Vite entry and bootstrap without writing, so setup can report an
|
|
11
|
+
* unsupported layout before changing any file.
|
|
12
|
+
*/
|
|
13
|
+
export declare function inspectViteSPA(appDirectory: string): Promise<{
|
|
14
|
+
indexHtmlPath: string;
|
|
15
|
+
sourceDirectory: string;
|
|
16
|
+
indexHtml: string;
|
|
17
|
+
script: string;
|
|
18
|
+
source: string;
|
|
19
|
+
isAlreadyConfigured: boolean;
|
|
20
|
+
customBootstrap: string | undefined;
|
|
21
|
+
bootstrapFilename: string;
|
|
22
|
+
bootstrapPath: string;
|
|
23
|
+
entryImport: string;
|
|
24
|
+
}>;
|
|
25
|
+
export type ViteLoaderResult = 'written' | 'custom' | 'missing';
|
|
26
|
+
/**
|
|
27
|
+
* Points the generated src/loadTranslations.ts at translationsDir and adds
|
|
28
|
+
* empty locale stubs. Only a template matching the previous config is
|
|
29
|
+
* refreshed; other existing loaders are left unchanged. An absent loader
|
|
30
|
+
* is only created with `create`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeViteLoader({ appDirectory, defaultLocale, locales, translationsDir, previousTranslationsDir, create, }: Omit<SetupViteSPAOptions, 'configFilepath' | 'translationsDir'> & {
|
|
33
|
+
translationsDir: string;
|
|
34
|
+
create: boolean;
|
|
35
|
+
}): Promise<ViteLoaderResult>;
|
|
36
|
+
export declare function setupViteSPA({ appDirectory, configFilepath, defaultLocale, locales, translationsDir, previousTranslationsDir, }: SetupViteSPAOptions): Promise<{
|
|
37
|
+
loader?: ViteLoaderResult;
|
|
38
|
+
manualAction?: string;
|
|
39
|
+
}>;
|
|
9
40
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { logger } from "../console/logger.js";
|
|
2
|
+
import "../types/libraries.js";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import { createDiagnosticMessage } from "generaltranslation/diagnostics";
|
|
6
|
+
import { parse } from "@babel/parser";
|
|
5
7
|
//#region src/setup/setupViteSPA.ts
|
|
6
8
|
const defaultBootstrapFilename = "gt-entry.ts";
|
|
7
9
|
const alternateBootstrapFilename = "gt-bootstrap.ts";
|
|
@@ -21,16 +23,27 @@ function getLoaderContent(translationsImport) {
|
|
|
21
23
|
}
|
|
22
24
|
`;
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Top-level statements, or undefined for syntax this inspector cannot parse
|
|
28
|
+
* but Vite may accept, such as decorators.
|
|
29
|
+
*/
|
|
30
|
+
function parseModule(content, filename) {
|
|
31
|
+
try {
|
|
32
|
+
return parse(content, {
|
|
33
|
+
sourceType: "module",
|
|
34
|
+
plugins: /\.[cm]?ts$/i.test(filename) ? ["typescript"] : ["typescript", "jsx"]
|
|
35
|
+
}).program.body;
|
|
36
|
+
} catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
26
39
|
}
|
|
27
40
|
function toRelativeImport(fromDirectory, toPath) {
|
|
28
41
|
const relativePath = path.relative(fromDirectory, toPath).split(path.sep).join(path.posix.sep);
|
|
29
42
|
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
30
43
|
}
|
|
31
|
-
function
|
|
44
|
+
function getEntryPath(appDirectory, source) {
|
|
32
45
|
const sourcePath = source.replace(/[?#].*$/, "");
|
|
33
|
-
return
|
|
46
|
+
return sourcePath.startsWith("/") ? path.resolve(appDirectory, `.${sourcePath}`) : path.resolve(appDirectory, sourcePath);
|
|
34
47
|
}
|
|
35
48
|
function getBootstrapEntry(bootstrap) {
|
|
36
49
|
return bootstrap.match(/await\s+import\(\s*(['"])([^'"]+)\1\s*\)/)?.[2];
|
|
@@ -51,18 +64,29 @@ function getModuleEntry(indexHtml) {
|
|
|
51
64
|
fix: "Add a module script for the app entry and rerun `npx gt@latest`"
|
|
52
65
|
}));
|
|
53
66
|
}
|
|
54
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Reads the Vite entry and bootstrap without writing, so setup can report an
|
|
69
|
+
* unsupported layout before changing any file.
|
|
70
|
+
*/
|
|
71
|
+
async function inspectViteSPA(appDirectory) {
|
|
55
72
|
const indexHtmlPath = path.join(appDirectory, "index.html");
|
|
56
73
|
const sourceDirectory = path.join(appDirectory, "src");
|
|
57
74
|
const indexHtml = await fs.promises.readFile(indexHtmlPath, "utf8");
|
|
58
75
|
const { script, source } = getModuleEntry(indexHtml);
|
|
59
|
-
const
|
|
76
|
+
const declaredEntryPath = getEntryPath(appDirectory, source);
|
|
77
|
+
const declaredEntryImport = toRelativeImport(sourceDirectory, declaredEntryPath).replace(/\.(?:[cm]?[jt]sx?)$/i, "");
|
|
60
78
|
const configuredBootstrap = source.match(/^\/?src\/(gt-entry\.ts|gt-bootstrap\.ts)(?:[?#].*)?$/)?.[1];
|
|
79
|
+
let customBootstrap;
|
|
80
|
+
if (!configuredBootstrap && fs.existsSync(declaredEntryPath)) {
|
|
81
|
+
const entry = await fs.promises.readFile(declaredEntryPath, "utf8");
|
|
82
|
+
const mentionsInitializer = entry.includes("initializeGTSPA");
|
|
83
|
+
if (parseModule(entry, declaredEntryPath)?.some((statement) => statement.type === "ImportDeclaration" && statement.source.value === "gt-react" && statement.importKind !== "type" && statement.specifiers.some((specifier) => specifier.type === "ImportNamespaceSpecifier" && mentionsInitializer || specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && specifier.imported.name === "initializeGTSPA")) ?? mentionsInitializer) customBootstrap = path.relative(appDirectory, declaredEntryPath);
|
|
84
|
+
}
|
|
61
85
|
const isAlreadyConfigured = configuredBootstrap !== void 0;
|
|
62
86
|
const bootstrapFilename = configuredBootstrap ?? (declaredEntryImport === "./gt-entry" ? alternateBootstrapFilename : defaultBootstrapFilename);
|
|
63
87
|
const bootstrapPath = path.join(sourceDirectory, bootstrapFilename);
|
|
64
88
|
let existingBootstrap;
|
|
65
|
-
if (fs.existsSync(bootstrapPath)) {
|
|
89
|
+
if (!customBootstrap && fs.existsSync(bootstrapPath)) {
|
|
66
90
|
existingBootstrap = await fs.promises.readFile(bootstrapPath, "utf8");
|
|
67
91
|
if (!existingBootstrap.includes("initializeGTSPA")) throw new Error(getBootstrapConflictError(bootstrapFilename));
|
|
68
92
|
}
|
|
@@ -73,22 +97,84 @@ async function setupViteSPA({ appDirectory, configFilepath, defaultLocale, local
|
|
|
73
97
|
whatHappened: "The existing Vite bootstrap has no app entry import",
|
|
74
98
|
fix: "Restore the app entry import and rerun `npx gt@latest`"
|
|
75
99
|
}));
|
|
100
|
+
return {
|
|
101
|
+
indexHtmlPath,
|
|
102
|
+
sourceDirectory,
|
|
103
|
+
indexHtml,
|
|
104
|
+
script,
|
|
105
|
+
source,
|
|
106
|
+
isAlreadyConfigured,
|
|
107
|
+
customBootstrap,
|
|
108
|
+
bootstrapFilename,
|
|
109
|
+
bootstrapPath,
|
|
110
|
+
entryImport
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Points the generated src/loadTranslations.ts at translationsDir and adds
|
|
115
|
+
* empty locale stubs. Only a template matching the previous config is
|
|
116
|
+
* refreshed; other existing loaders are left unchanged. An absent loader
|
|
117
|
+
* is only created with `create`.
|
|
118
|
+
*/
|
|
119
|
+
async function writeViteLoader({ appDirectory, defaultLocale, locales, translationsDir, previousTranslationsDir, create }) {
|
|
120
|
+
const sourceDirectory = path.join(appDirectory, "src");
|
|
121
|
+
const translationsPath = path.resolve(appDirectory, translationsDir);
|
|
122
|
+
const loaderPath = path.join(sourceDirectory, "loadTranslations.ts");
|
|
123
|
+
const existingLoader = fs.existsSync(loaderPath) ? await fs.promises.readFile(loaderPath, "utf8") : void 0;
|
|
124
|
+
if (existingLoader === void 0 && !create) return "missing";
|
|
125
|
+
const content = getLoaderContent(toRelativeImport(sourceDirectory, translationsPath));
|
|
126
|
+
const previousContent = previousTranslationsDir === void 0 ? void 0 : getLoaderContent(toRelativeImport(sourceDirectory, path.resolve(appDirectory, previousTranslationsDir)));
|
|
127
|
+
const normalizedLoader = existingLoader?.replace(/\r\n/g, "\n").trimEnd();
|
|
128
|
+
const custom = existingLoader !== void 0 && normalizedLoader !== content.trimEnd() && normalizedLoader !== previousContent?.trimEnd();
|
|
129
|
+
await fs.promises.mkdir(translationsPath, { recursive: true });
|
|
130
|
+
for (const locale of new Set(locales)) {
|
|
131
|
+
if (locale === defaultLocale) continue;
|
|
132
|
+
const stubPath = path.join(translationsPath, `${locale}.json`);
|
|
133
|
+
if (!fs.existsSync(stubPath)) await fs.promises.writeFile(stubPath, "{}\n");
|
|
134
|
+
}
|
|
135
|
+
if (custom) return "custom";
|
|
136
|
+
await fs.promises.writeFile(loaderPath, content);
|
|
137
|
+
return "written";
|
|
138
|
+
}
|
|
139
|
+
function getLoaderExport(content) {
|
|
140
|
+
const statements = parseModule(content, "loadTranslations.ts");
|
|
141
|
+
if (!statements) return void 0;
|
|
142
|
+
const names = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const statement of statements) {
|
|
144
|
+
if (statement.type === "ExportDefaultDeclaration" && statement.declaration.type !== "TSInterfaceDeclaration") names.add("default");
|
|
145
|
+
if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") continue;
|
|
146
|
+
for (const specifier of statement.specifiers) if (specifier.type === "ExportSpecifier" && specifier.exportKind !== "type") names.add(specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value);
|
|
147
|
+
const declaration = statement.declaration;
|
|
148
|
+
if (declaration?.type === "FunctionDeclaration" && !declaration.declare && declaration.id) names.add(declaration.id.name);
|
|
149
|
+
if (declaration?.type === "VariableDeclaration" && !declaration.declare) {
|
|
150
|
+
for (const variable of declaration.declarations) if (variable.id.type === "Identifier") names.add(variable.id.name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (names.has("default")) return "default";
|
|
154
|
+
if (names.has("loadTranslations")) return "loadTranslations";
|
|
155
|
+
}
|
|
156
|
+
async function setupViteSPA({ appDirectory, configFilepath, defaultLocale, locales, translationsDir, previousTranslationsDir }) {
|
|
157
|
+
const { indexHtmlPath, sourceDirectory, indexHtml, script, source, isAlreadyConfigured, customBootstrap, bootstrapFilename, bootstrapPath, entryImport } = await inspectViteSPA(appDirectory);
|
|
158
|
+
if (customBootstrap) return { manualAction: `Verify the existing GT bootstrap ${customBootstrap} uses ${configFilepath}${translationsDir ? ` and loads translations from ${translationsDir}` : " with CDN loading"}` };
|
|
76
159
|
await fs.promises.mkdir(sourceDirectory, { recursive: true });
|
|
77
160
|
let loadTranslationsImport = "";
|
|
78
161
|
let loadTranslationsOption = "gtConfig";
|
|
162
|
+
let loader;
|
|
79
163
|
if (translationsDir) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
164
|
+
loader = await writeViteLoader({
|
|
165
|
+
appDirectory,
|
|
166
|
+
defaultLocale,
|
|
167
|
+
locales,
|
|
168
|
+
translationsDir,
|
|
169
|
+
previousTranslationsDir,
|
|
170
|
+
create: true
|
|
171
|
+
});
|
|
172
|
+
const loaderExport = loader === "custom" ? getLoaderExport(await fs.promises.readFile(path.join(sourceDirectory, "loadTranslations.ts"), "utf8")) : "default";
|
|
173
|
+
if (!loaderExport) return {
|
|
174
|
+
loader,
|
|
175
|
+
manualAction: `Update your custom src/loadTranslations.ts to load translations from ${translationsDir} and export a default or named loadTranslations function, then rerun gt init`
|
|
176
|
+
};
|
|
177
|
+
loadTranslationsImport = loaderExport === "default" ? "import loadTranslations from './loadTranslations';\n" : "import { loadTranslations } from './loadTranslations';\n";
|
|
92
178
|
loadTranslationsOption = "{ ...gtConfig, loadTranslations }";
|
|
93
179
|
}
|
|
94
180
|
const configImport = toRelativeImport(sourceDirectory, path.resolve(appDirectory, configFilepath));
|
|
@@ -104,8 +190,12 @@ await import('${entryImport}');
|
|
|
104
190
|
await fs.promises.writeFile(indexHtmlPath, indexHtml.replace(script, updatedScript));
|
|
105
191
|
}
|
|
106
192
|
logger.success("Configured initializeGTSPA for this Vite application.");
|
|
193
|
+
return {
|
|
194
|
+
loader,
|
|
195
|
+
...loader === "custom" && { manualAction: `Verify your preserved custom src/loadTranslations.ts loads translations from ${translationsDir}` }
|
|
196
|
+
};
|
|
107
197
|
}
|
|
108
198
|
//#endregion
|
|
109
|
-
export { setupViteSPA };
|
|
199
|
+
export { inspectViteSPA, setupViteSPA, writeViteLoader };
|
|
110
200
|
|
|
111
201
|
//# sourceMappingURL=setupViteSPA.js.map
|