@uipkge/nuxt 0.1.35 → 0.1.36

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/bin/cli.mjs CHANGED
@@ -1,109 +1,331 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'child_process'
3
- import fs from 'fs'
4
- import path from 'path'
5
- import { createInterface } from 'readline'
2
+ import { execSync } from "child_process";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { createInterface } from "readline";
6
6
 
7
- const PKG_DIR = path.resolve(import.meta.dirname, '..')
8
- const TARGET_DIR = process.cwd()
9
- const distDir = path.join(PKG_DIR, 'dist')
7
+ const TARGET_DIR = process.cwd();
10
8
 
11
- const isInstalled = fs.existsSync(distDir)
9
+ // ── Helpers ──────────────────────────────────────────────────────────
12
10
 
13
- function run(cmd, opts = {}) {
14
- console.log(`> ${cmd}`)
15
- execSync(cmd, { stdio: 'inherit', ...opts })
11
+ function prompt(question) {
12
+ return new Promise((resolve) => {
13
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14
+ rl.question(question, (ans) => {
15
+ rl.close();
16
+ resolve(ans.trim());
17
+ });
18
+ });
16
19
  }
17
20
 
18
- function exists(file) {
19
- return fs.existsSync(path.join(TARGET_DIR, file))
21
+ function parseFlags(args) {
22
+ const flags = {};
23
+ for (const arg of args) {
24
+ const match = arg.match(/^--([a-zA-Z-]+)=(.+)$/);
25
+ if (match) {
26
+ // Normalize camelCase to kebab-case: projectId → project-id
27
+ const key = match[1].replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
28
+ flags[key] = match[2];
29
+ }
30
+ }
31
+ return flags;
20
32
  }
21
33
 
22
- function prompt(question) {
23
- return new Promise(resolve => {
24
- const rl = createInterface({ input: process.stdin, output: process.stdout })
25
- rl.question(question, ans => { rl.close(); resolve(ans) })
26
- })
34
+ function detectPackageManager() {
35
+ if (fs.existsSync(path.join(TARGET_DIR, "bun.lock")) || fs.existsSync(path.join(TARGET_DIR, "bun.lockb")))
36
+ return "bun";
37
+ if (fs.existsSync(path.join(TARGET_DIR, "pnpm-lock.yaml"))) return "pnpm";
38
+ if (fs.existsSync(path.join(TARGET_DIR, "yarn.lock"))) return "yarn";
39
+ return "npm";
27
40
  }
28
41
 
29
- async function getInput(question, defaultValue) {
30
- const answer = await prompt(question)
31
- return answer.trim() || defaultValue
42
+ function run(cmd) {
43
+ execSync(cmd, { stdio: "inherit", cwd: TARGET_DIR });
32
44
  }
33
45
 
34
- async function main() {
35
- console.log('\n🚀 @uipkge/nuxt setup\n')
46
+ function isInstalled(pkg) {
47
+ try {
48
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(TARGET_DIR, "package.json"), "utf-8"));
49
+ const all = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
50
+ return pkg in all;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
36
55
 
37
- if (isInstalled) {
38
- console.log('📦 Package ready')
56
+ function findNuxtConfig() {
57
+ for (const name of ["nuxt.config.ts", "nuxt.config.js"]) {
58
+ const p = path.join(TARGET_DIR, name);
59
+ if (fs.existsSync(p)) return p;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function devCommand(pm) {
65
+ if (pm === "npm") return "npm run dev";
66
+ return `${pm} dev`;
67
+ }
68
+
69
+ function validateProjectId(id) {
70
+ const trimmed = id.trim();
71
+ return trimmed.length > 0 && trimmed.length <= 100;
72
+ }
73
+
74
+ function validateApiKey(key) {
75
+ const trimmed = key.trim();
76
+ return trimmed.length >= 8 && trimmed.length <= 200;
77
+ }
78
+
79
+ function maskApiKey(key) {
80
+ if (key.length < 8) return "*".repeat(key.length);
81
+ const start = key.slice(0, 4);
82
+ const end = key.slice(-4);
83
+ return `${start}${"*".repeat(key.length - 8)}${end}`;
84
+ }
85
+
86
+ function writeEnvFile(apiKey) {
87
+ const envPath = path.join(TARGET_DIR, ".env");
88
+ if (fs.existsSync(envPath)) {
89
+ let content = fs.readFileSync(envPath, "utf-8");
90
+ if (content.match(/^I18NOW_API_KEY=.*/m)) {
91
+ content = content.replace(/^I18NOW_API_KEY=.*/m, `I18NOW_API_KEY=${apiKey}`);
92
+ } else {
93
+ content = content.trimEnd() + `\nI18NOW_API_KEY=${apiKey}\n`;
94
+ }
95
+ fs.writeFileSync(envPath, content);
39
96
  } else {
40
- console.log('📦 Building package...')
41
- run('npm run build', { cwd: PKG_DIR })
42
- }
43
-
44
- console.log('\n📥 Installing @nuxtjs/i18n...')
45
- run('npm install @nuxtjs/i18n@latest', { cwd: TARGET_DIR })
46
-
47
- if (!isInstalled) {
48
- run('npm link', { cwd: PKG_DIR })
49
- run('npm link @uipkge/nuxt', { cwd: TARGET_DIR })
50
- }
51
-
52
- console.log('\n⚙️ i18now configuration:')
53
- const projectId = await getInput('Enter projectId (or press Enter to skip): ', '')
54
- const apiKey = await getInput('Enter apiKey (or press Enter to skip): ', '')
55
-
56
- const nuxtConfigPath = path.join(TARGET_DIR, 'nuxt.config.ts')
57
- const nuxtConfigContent = `export default defineNuxtConfig({
58
- compatibilityDate: '2025-07-15',
59
- devtools: { enabled: true },
60
- modules: ['@nuxtjs/i18n', '@uipkge/nuxt'],
61
- i18n: {
62
- defaultLocale: 'en',
63
- },
64
- i18now: {
65
- projectId: '${projectId}',
66
- apiKey: '${apiKey}',
67
- },
68
- })
69
- `
70
- fs.writeFileSync(nuxtConfigPath, nuxtConfigContent)
71
-
72
- const pagesDir = path.join(TARGET_DIR, 'app/pages')
73
- if (!fs.existsSync(pagesDir)) {
74
- fs.mkdirSync(pagesDir, { recursive: true })
75
- }
76
-
77
- const testPagePath = path.join(TARGET_DIR, 'app/pages/i18now-test.vue')
78
- if (!fs.existsSync(testPagePath)) {
79
- console.log('📄 Creating test page at /i18now-test...')
80
- fs.writeFileSync(testPagePath, `<script setup>
97
+ fs.writeFileSync(envPath, `I18NOW_API_KEY=${apiKey}\n`);
98
+ }
99
+ }
100
+
101
+ // ── Smart Merge ──────────────────────────────────────────────────────
102
+
103
+ async function smartMerge(configPath, projectId) {
104
+ const { loadFile, writeFile, builders } = await import("magicast");
105
+ const { addNuxtModule } = await import("magicast/helpers");
106
+
107
+ const mod = await loadFile(configPath);
108
+ const config = mod.exports.default.$type === "function-call"
109
+ ? mod.exports.default.$args[0]
110
+ : mod.exports.default;
111
+
112
+ // Add modules (addNuxtModule takes the full magicast object)
113
+ addNuxtModule(mod, "@nuxtjs/i18n");
114
+ addNuxtModule(mod, "@uipkge/nuxt");
115
+
116
+ // Add i18n config if missing
117
+ if (!config.i18n) {
118
+ config.i18n = {
119
+ defaultLocale: "en",
120
+ locales: ["en"],
121
+ };
122
+ } else if (!config.i18n.defaultLocale) {
123
+ config.i18n.defaultLocale = "en";
124
+ }
125
+
126
+ // Add/update i18now config (preserve existing options)
127
+ if (!config.i18now) {
128
+ config.i18now = {};
129
+ }
130
+ config.i18now.projectId = projectId;
131
+ config.i18now.apiKey = builders.raw("process.env.I18NOW_API_KEY");
132
+
133
+ await writeFile(mod, configPath);
134
+ }
135
+
136
+ // ── Test Page ────────────────────────────────────────────────────────
137
+
138
+ function createTestPage() {
139
+ // Check both Nuxt 3 page directories
140
+ const appPagesDir = path.join(TARGET_DIR, "app/pages");
141
+ const pagesDir = path.join(TARGET_DIR, "pages");
142
+
143
+ // Use app/pages if app/ dir exists, otherwise pages/
144
+ const targetPagesDir = fs.existsSync(path.join(TARGET_DIR, "app")) ? appPagesDir : pagesDir;
145
+ const testPagePath = path.join(targetPagesDir, "i18now-test.vue");
146
+
147
+ if (fs.existsSync(testPagePath)) {
148
+ console.log(" Test page already exists, skipping");
149
+ return false;
150
+ }
151
+
152
+ fs.mkdirSync(targetPagesDir, { recursive: true });
153
+ fs.writeFileSync(
154
+ testPagePath,
155
+ `<script setup>
81
156
  const { t, locale } = useI18now()
82
157
  </script>
83
158
 
84
159
  <template>
85
160
  <div style="padding: 20px; font-family: sans-serif; max-width: 600px;">
86
161
  <h1>i18now Test Page</h1>
87
-
162
+
88
163
  <h2>Translation Keys (try adding new ones):</h2>
89
164
  <p>{{ t('welcome_message', 'Welcome to i18now!') }}</p>
90
165
  <p>{{ t('description', 'This is a test page for translations.') }}</p>
91
166
  <p>{{ t('feature_sync', 'Key sync is enabled in development mode.') }}</p>
92
-
167
+
93
168
  <h2>Language Switcher:</h2>
94
169
  <select v-model="locale" style="padding: 8px; margin: 10px 0;">
95
170
  <option value="en">English</option>
96
- <option value="es">Español</option>
171
+ <option value="es">Espa\u00f1ol</option>
97
172
  </select>
98
-
173
+
99
174
  <p><small>Current locale: {{ locale }}</small></p>
100
175
  <p><small>Add new t() calls above to test key sync!</small></p>
101
176
  </div>
102
177
  </template>
103
- `)
178
+ `,
179
+ );
180
+ return true;
181
+ }
182
+
183
+ // ── Main ─────────────────────────────────────────────────────────────
184
+
185
+ async function main() {
186
+ const args = process.argv.slice(2);
187
+ const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
188
+
189
+ if (args.includes("--version") || args.includes("-v")) {
190
+ console.log(pkg.version);
191
+ process.exit(0);
192
+ }
193
+
194
+ if (args.includes("--help") || args.includes("-h")) {
195
+ console.log(`
196
+ @uipkge/nuxt v${pkg.version}
197
+
198
+ Usage: npx @uipkge/nuxt init [options]
199
+
200
+ Options:
201
+ --project-id=<id> Your i18now project ID
202
+ --api-key=<key> Your i18now API key
203
+ --help, -h Show this help message
204
+ --version, -v Show version number
205
+
206
+ Aliases: --projectId, --apiKey (camelCase also accepted)
207
+ `);
208
+ process.exit(0);
209
+ }
210
+
211
+ const command = args[0];
212
+
213
+ if (command !== "init") {
214
+ console.log(`\n@uipkge/nuxt v${pkg.version}\n`);
215
+ console.log("Usage: npx @uipkge/nuxt init [--project-id=xxx] [--api-key=xxx]");
216
+ console.log(" npx @uipkge/nuxt --help\n");
217
+ process.exit(command ? 1 : 0);
218
+ }
219
+
220
+ console.log(`\n@uipkge/nuxt v${pkg.version} setup\n`);
221
+
222
+ // 1. Verify Nuxt project
223
+ const configPath = findNuxtConfig();
224
+ if (!configPath) {
225
+ console.error("No nuxt.config.ts or nuxt.config.js found. Run this inside a Nuxt project.");
226
+ process.exit(1);
227
+ }
228
+
229
+ // 2. Parse flags or prompt
230
+ const flags = parseFlags(args.slice(1));
231
+ let projectId = flags["project-id"] || "";
232
+ let apiKey = flags["api-key"] || "";
233
+
234
+ const isTTY = process.stdin.isTTY;
235
+
236
+ if (!projectId) {
237
+ if (!isTTY) {
238
+ console.error("Provide --project-id flag (non-interactive terminal detected).");
239
+ process.exit(1);
240
+ }
241
+ projectId = await prompt("Project ID: ");
242
+ if (!projectId) {
243
+ console.error("Project ID is required.");
244
+ process.exit(1);
245
+ }
246
+ }
247
+
248
+ if (!apiKey) {
249
+ if (!isTTY) {
250
+ console.error("Provide --api-key flag (non-interactive terminal detected).");
251
+ process.exit(1);
252
+ }
253
+ apiKey = await prompt("API Key: ");
254
+ if (!apiKey) {
255
+ console.error("API Key is required.");
256
+ process.exit(1);
257
+ }
258
+ }
259
+
260
+ // 3. Validate inputs
261
+ if (!validateProjectId(projectId)) {
262
+ console.error("Invalid Project ID. Must be 1-100 characters.");
263
+ process.exit(1);
264
+ }
265
+ if (!validateApiKey(apiKey)) {
266
+ console.error("Invalid API Key. Must be 8-200 characters.");
267
+ process.exit(1);
268
+ }
269
+
270
+ // 4. Detect package manager
271
+ const pm = detectPackageManager();
272
+
273
+ // 5. Install @nuxtjs/i18n if missing
274
+ if (!isInstalled("@nuxtjs/i18n")) {
275
+ console.log("Installing @nuxtjs/i18n...");
276
+ const installCmd = {
277
+ bun: "bun add @nuxtjs/i18n",
278
+ pnpm: "pnpm add @nuxtjs/i18n",
279
+ yarn: "yarn add @nuxtjs/i18n",
280
+ npm: "npm install @nuxtjs/i18n",
281
+ };
282
+ run(installCmd[pm]);
283
+ } else {
284
+ console.log("@nuxtjs/i18n already installed");
285
+ }
286
+
287
+ // 6. Install @uipkge/nuxt if missing
288
+ if (!isInstalled("@uipkge/nuxt")) {
289
+ console.log("Installing @uipkge/nuxt...");
290
+ const installCmd = {
291
+ bun: "bun add @uipkge/nuxt",
292
+ pnpm: "pnpm add @uipkge/nuxt",
293
+ yarn: "yarn add @uipkge/nuxt",
294
+ npm: "npm install @uipkge/nuxt",
295
+ };
296
+ run(installCmd[pm]);
297
+ }
298
+
299
+ // 7. Save API key to .env
300
+ writeEnvFile(apiKey);
301
+ console.log(" Saved API key to .env (add .env to .gitignore!)");
302
+
303
+ // 8. Smart merge nuxt.config
304
+ console.log("Updating nuxt.config...");
305
+ try {
306
+ await smartMerge(configPath, projectId);
307
+ console.log(" Added @uipkge/nuxt to nuxt.config");
308
+ } catch (err) {
309
+ console.error(`Failed to update nuxt.config: ${err.message}`);
310
+ console.log("\nAdd this manually to your nuxt.config:\n");
311
+ console.log(` modules: ['@nuxtjs/i18n', '@uipkge/nuxt'],`);
312
+ console.log(` i18n: { defaultLocale: 'en', locales: ['en'] },`);
313
+ console.log(` i18now: { projectId: '${projectId}', apiKey: process.env.I18NOW_API_KEY },`);
314
+ console.log(` (API key is in your .env file: ${maskApiKey(apiKey)})\n`);
315
+ process.exit(1);
316
+ }
317
+
318
+ // 9. Create test page
319
+ if (createTestPage()) {
320
+ console.log(" Created test page at /i18now-test");
104
321
  }
105
322
 
106
- console.log('\n✅ Ready! Run: npm run dev\n')
323
+ // 10. Done
324
+ console.log(`\nDone! Start your dev server and visit /i18now-test:\n`);
325
+ console.log(` ${devCommand(pm)}\n`);
107
326
  }
108
327
 
109
- main()
328
+ main().catch((err) => {
329
+ console.error(err);
330
+ process.exit(1);
331
+ });
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.1.35",
7
+ "version": "0.1.36",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@uipkge/nuxt",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
7
- "i18now-nuxt": "./bin/cli.mjs"
7
+ "i18now-nuxt": "./bin/cli.mjs",
8
+ "uipkge-nuxt": "./bin/cli.mjs"
8
9
  },
9
10
  "exports": {
10
11
  ".": {
@@ -19,7 +20,8 @@
19
20
  "bin"
20
21
  ],
21
22
  "dependencies": {
22
- "@nuxt/kit": "^3.0.0"
23
+ "@nuxt/kit": "^3.0.0",
24
+ "magicast": "^0.3.0"
23
25
  },
24
26
  "peerDependencies": {
25
27
  "vue-i18n": "^9.0.0 || ^10.0.0 || ^11.0.0",