@uipkge/nuxt 0.1.34 → 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,107 +1,331 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'child_process'
3
- import fs from 'fs'
4
- import path from 'path'
2
+ import { execSync } from "child_process";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { createInterface } from "readline";
5
6
 
6
- const PKG_DIR = path.resolve(import.meta.dirname, '..')
7
- const TARGET_DIR = process.cwd()
8
- const distDir = path.join(PKG_DIR, 'dist')
7
+ const TARGET_DIR = process.cwd();
9
8
 
10
- const isInstalled = fs.existsSync(distDir)
9
+ // ── Helpers ──────────────────────────────────────────────────────────
11
10
 
12
- function run(cmd, opts = {}) {
13
- console.log(`> ${cmd}`)
14
- 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
+ });
15
19
  }
16
20
 
17
- function exists(file) {
18
- 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;
19
32
  }
20
33
 
21
- console.log('\n🚀 @uipkge/nuxt setup\n')
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";
40
+ }
22
41
 
23
- if (isInstalled) {
24
- console.log('📦 Package ready')
25
- } else {
26
- console.log('📦 Building package...')
27
- run('npm run build', { cwd: PKG_DIR })
42
+ function run(cmd) {
43
+ execSync(cmd, { stdio: "inherit", cwd: TARGET_DIR });
28
44
  }
29
45
 
30
- console.log('\n📥 Installing @nuxtjs/i18n...')
31
- run('npm install @nuxtjs/i18n@latest', { cwd: TARGET_DIR })
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
+ }
32
55
 
33
- if (!isInstalled) {
34
- run('npm link', { cwd: PKG_DIR })
35
- run('npm link @uipkge/nuxt', { cwd: TARGET_DIR })
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;
36
62
  }
37
63
 
38
- const nuxtConfigPath = path.join(TARGET_DIR, 'nuxt.config.ts')
64
+ function devCommand(pm) {
65
+ if (pm === "npm") return "npm run dev";
66
+ return `${pm} dev`;
67
+ }
39
68
 
40
- function prompt(question) {
41
- const readline = require('readline')
42
- return new Promise(resolve => {
43
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
44
- rl.question(question, ans => { rl.close(); resolve(ans) })
45
- })
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;
46
77
  }
47
78
 
48
- async function getInput(question, defaultValue) {
49
- const answer = await prompt(question)
50
- return answer.trim() || defaultValue
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}`;
51
84
  }
52
85
 
53
- console.log('\n⚙️ i18now configuration:')
54
-
55
- const projectId = await getInput('Enter projectId (or press Enter to skip): ', '')
56
- const apiKey = await getInput('Enter apiKey (or press Enter to skip): ', '')
57
-
58
- const nuxtConfigContent = `export default defineNuxtConfig({
59
- compatibilityDate: '2025-07-15',
60
- devtools: { enabled: true },
61
- modules: ['@nuxtjs/i18n', '@uipkge/nuxt'],
62
- i18n: {
63
- defaultLocale: 'en',
64
- },
65
- i18now: {
66
- projectId: '${projectId}',
67
- apiKey: '${apiKey}',
68
- },
69
- })
70
- `
71
- fs.writeFileSync(nuxtConfigPath, nuxtConfigContent)
72
-
73
- const pagesDir = path.join(TARGET_DIR, 'app/pages')
74
- if (!fs.existsSync(pagesDir)) {
75
- fs.mkdirSync(pagesDir, { recursive: true })
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);
96
+ } else {
97
+ fs.writeFileSync(envPath, `I18NOW_API_KEY=${apiKey}\n`);
98
+ }
76
99
  }
77
100
 
78
- const testPagePath = path.join(TARGET_DIR, 'app/pages/i18now-test.vue')
79
- if (!fs.existsSync(testPagePath)) {
80
- console.log('📄 Creating test page at /i18now-test...')
81
- fs.writeFileSync(testPagePath, `<script setup>
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>
82
156
  const { t, locale } = useI18now()
83
157
  </script>
84
158
 
85
159
  <template>
86
160
  <div style="padding: 20px; font-family: sans-serif; max-width: 600px;">
87
161
  <h1>i18now Test Page</h1>
88
-
162
+
89
163
  <h2>Translation Keys (try adding new ones):</h2>
90
164
  <p>{{ t('welcome_message', 'Welcome to i18now!') }}</p>
91
165
  <p>{{ t('description', 'This is a test page for translations.') }}</p>
92
166
  <p>{{ t('feature_sync', 'Key sync is enabled in development mode.') }}</p>
93
-
167
+
94
168
  <h2>Language Switcher:</h2>
95
169
  <select v-model="locale" style="padding: 8px; margin: 10px 0;">
96
170
  <option value="en">English</option>
97
- <option value="es">Español</option>
171
+ <option value="es">Espa\u00f1ol</option>
98
172
  </select>
99
-
173
+
100
174
  <p><small>Current locale: {{ locale }}</small></p>
101
175
  <p><small>Add new t() calls above to test key sync!</small></p>
102
176
  </div>
103
177
  </template>
104
- `)
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");
321
+ }
322
+
323
+ // 10. Done
324
+ console.log(`\nDone! Start your dev server and visit /i18now-test:\n`);
325
+ console.log(` ${devCommand(pm)}\n`);
105
326
  }
106
327
 
107
- console.log('\n✅ Ready! Run: npm run dev\n')
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.34",
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.34",
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",