needware-cli 1.6.3 → 1.6.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "needware-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "一个功能强大的 Node.js 命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -123,15 +123,19 @@ Examples:
123
123
  - `supabase/functions/ai-service/index.ts`
124
124
 
125
125
  ```typescript
126
- import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
126
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
127
127
 
128
+ // CORS headers for cross-origin requests
128
129
  const corsHeaders = {
129
130
  "Access-Control-Allow-Origin": "*",
130
- "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
131
+ "Access-Control-Allow-Headers":
132
+ "authorization, x-client-info, apikey, content-type",
133
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
134
+ "Access-Control-Max-Age": "86400",
131
135
  };
132
136
 
137
+ // Request interface
133
138
  interface AIRequest {
134
- // Define request parameters based on specific AI functionality
135
139
  input: string | object;
136
140
  options?: {
137
141
  model?: string;
@@ -140,10 +144,13 @@ interface AIRequest {
140
144
  };
141
145
  }
142
146
 
143
- serve(async (req) => {
144
- // Handle CORS preflight request
147
+ const handler = async (req: Request): Promise<Response> => {
148
+ // Handle CORS preflight requests
145
149
  if (req.method === "OPTIONS") {
146
- return new Response(null, { headers: corsHeaders });
150
+ return new Response(null, {
151
+ status: 200,
152
+ headers: corsHeaders
153
+ });
147
154
  }
148
155
 
149
156
  try {
@@ -153,27 +160,26 @@ serve(async (req) => {
153
160
  if (!requestData.input) {
154
161
  return new Response(
155
162
  JSON.stringify({ error: "Missing required input parameter" }),
156
- { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
163
+ {
164
+ status: 400,
165
+ headers: { "Content-Type": "application/json", ...corsHeaders }
166
+ }
157
167
  );
158
168
  }
159
169
 
160
- // Get API key (from environment variables)
170
+ // Get API key from environment variables
161
171
  const AI_API_KEY = Deno.env.get("AI_API_KEY");
162
172
  if (!AI_API_KEY) {
163
- console.error("AI_API_KEY not configured");
164
- return new Response(
165
- JSON.stringify({ error: "AI service not configured" }),
166
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
167
- );
173
+ throw new Error("AI service not configured");
168
174
  }
169
175
 
170
176
  console.log("Processing AI request...");
171
177
 
172
- // Call AI service (using Needware AI gateway as example)
178
+ // Call AI Gateway API
173
179
  const response = await fetch("https://ai.gateway.needware.dev/v1/chat/completions", {
174
180
  method: "POST",
175
181
  headers: {
176
- Authorization: `Bearer ${AI_API_KEY}`,
182
+ "Authorization": `Bearer ${AI_API_KEY}`,
177
183
  "Content-Type": "application/json",
178
184
  },
179
185
  body: JSON.stringify({
@@ -185,7 +191,7 @@ serve(async (req) => {
185
191
  },
186
192
  {
187
193
  role: "user",
188
- content: typeof requestData.input === 'string'
194
+ content: typeof requestData.input === "string"
189
195
  ? requestData.input
190
196
  : JSON.stringify(requestData.input)
191
197
  }
@@ -195,7 +201,7 @@ serve(async (req) => {
195
201
  }),
196
202
  });
197
203
 
198
- // Handle error response
204
+ // Handle error responses
199
205
  if (!response.ok) {
200
206
  const errorText = await response.text();
201
207
  console.error("AI service error:", response.status, errorText);
@@ -203,20 +209,24 @@ serve(async (req) => {
203
209
  if (response.status === 429) {
204
210
  return new Response(
205
211
  JSON.stringify({ error: "Request rate too high, please try again later" }),
206
- { status: 429, headers: { ...corsHeaders, "Content-Type": "application/json" } }
212
+ {
213
+ status: 429,
214
+ headers: { "Content-Type": "application/json", ...corsHeaders }
215
+ }
207
216
  );
208
217
  }
218
+
209
219
  if (response.status === 402) {
210
220
  return new Response(
211
221
  JSON.stringify({ error: "AI service quota exhausted" }),
212
- { status: 402, headers: { ...corsHeaders, "Content-Type": "application/json" } }
222
+ {
223
+ status: 402,
224
+ headers: { "Content-Type": "application/json", ...corsHeaders }
225
+ }
213
226
  );
214
227
  }
215
228
 
216
- return new Response(
217
- JSON.stringify({ error: "AI processing failed" }),
218
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
219
- );
229
+ throw new Error(`AI processing failed: ${errorText}`);
220
230
  }
221
231
 
222
232
  // Parse AI response
@@ -224,32 +234,36 @@ serve(async (req) => {
224
234
  const content = data.choices?.[0]?.message?.content;
225
235
 
226
236
  if (!content) {
227
- console.error("No content in AI response");
228
- return new Response(
229
- JSON.stringify({ error: "No AI response generated" }),
230
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
231
- );
237
+ throw new Error("No AI response generated");
232
238
  }
233
239
 
234
240
  console.log("AI processing completed");
235
241
 
236
- // Return processing result
237
242
  return new Response(
238
243
  JSON.stringify({
244
+ success: true,
239
245
  result: content,
240
246
  model: requestData.options?.model || "google/gemini-2.5-flash"
241
247
  }),
242
- { headers: { ...corsHeaders, "Content-Type": "application/json" } }
248
+ {
249
+ status: 200,
250
+ headers: { "Content-Type": "application/json", ...corsHeaders }
251
+ }
243
252
  );
244
253
 
245
- } catch (error) {
254
+ } catch (error: any) {
246
255
  console.error("Function execution error:", error);
247
256
  return new Response(
248
- JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }),
249
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
257
+ JSON.stringify({ error: error.message || "Unknown error" }),
258
+ {
259
+ status: 500,
260
+ headers: { "Content-Type": "application/json", ...corsHeaders }
261
+ }
250
262
  );
251
263
  }
252
- });
264
+ };
265
+
266
+ serve(handler);
253
267
  ```
254
268
 
255
269
  ### Frontend Usage Template (TypeScript)
@@ -515,42 +529,59 @@ Use this template when users need image analysis functionality.
515
529
 
516
530
  ```typescript
517
531
  // supabase/functions/analyze-image/index.ts
518
- import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
532
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
519
533
 
534
+ // CORS headers for cross-origin requests
520
535
  const corsHeaders = {
521
536
  "Access-Control-Allow-Origin": "*",
522
- "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
537
+ "Access-Control-Allow-Headers":
538
+ "authorization, x-client-info, apikey, content-type",
539
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
540
+ "Access-Control-Max-Age": "86400",
523
541
  };
524
542
 
525
- serve(async (req) => {
543
+ // Request interface
544
+ interface ImageAnalysisRequest {
545
+ image: string;
546
+ prompt?: string;
547
+ }
548
+
549
+ const handler = async (req: Request): Promise<Response> => {
550
+ // Handle CORS preflight requests
526
551
  if (req.method === "OPTIONS") {
527
- return new Response(null, { headers: corsHeaders });
552
+ return new Response(null, {
553
+ status: 200,
554
+ headers: corsHeaders
555
+ });
528
556
  }
529
557
 
530
558
  try {
531
- const { image, prompt } = await req.json();
559
+ const { image, prompt }: ImageAnalysisRequest = await req.json();
532
560
 
561
+ // Validate input
533
562
  if (!image) {
534
563
  return new Response(
535
564
  JSON.stringify({ error: "No image provided" }),
536
- { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
565
+ {
566
+ status: 400,
567
+ headers: { "Content-Type": "application/json", ...corsHeaders }
568
+ }
537
569
  );
538
570
  }
539
571
 
572
+ // Get AI API key from environment
540
573
  const AI_API_KEY = Deno.env.get("AI_API_KEY");
541
574
  if (!AI_API_KEY) {
542
- return new Response(
543
- JSON.stringify({ error: "AI service not configured" }),
544
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
545
- );
575
+ throw new Error("AI service not configured");
546
576
  }
547
577
 
548
578
  console.log("Analyzing image...");
549
579
 
580
+ // Call AI Gateway API
550
581
  const response = await fetch("https://ai.gateway.needware.dev/v1/chat/completions", {
551
582
  method: "POST",
552
583
  headers: {
553
- Authorization: `Bearer ${AI_API_KEY}`,
584
+ "Authorization": `Bearer ${AI_API_KEY}`,
554
585
  "Content-Type": "application/json",
555
586
  },
556
587
  body: JSON.stringify({
@@ -582,16 +613,13 @@ serve(async (req) => {
582
613
  if (!response.ok) {
583
614
  const errorText = await response.text();
584
615
  console.error("AI service error:", response.status, errorText);
585
- return new Response(
586
- JSON.stringify({ error: "Image analysis failed" }),
587
- { status: response.status, headers: { ...corsHeaders, "Content-Type": "application/json" } }
588
- );
616
+ throw new Error(`Image analysis failed: ${errorText}`);
589
617
  }
590
618
 
591
619
  const data = await response.json();
592
620
  const analysis = data.choices?.[0]?.message?.content;
593
621
 
594
- // If expecting structured JSON, try to parse
622
+ // Try to parse structured JSON from response
595
623
  let structuredResult;
596
624
  try {
597
625
  const jsonMatch = analysis.match(/```json\n?([\s\S]*?)\n?```/) || analysis.match(/\{[\s\S]*\}/);
@@ -608,20 +636,29 @@ serve(async (req) => {
608
636
 
609
637
  return new Response(
610
638
  JSON.stringify({
639
+ success: true,
611
640
  result: structuredResult || analysis,
612
641
  model: "google/gemini-2.5-flash"
613
642
  }),
614
- { headers: { ...corsHeaders, "Content-Type": "application/json" } }
643
+ {
644
+ status: 200,
645
+ headers: { "Content-Type": "application/json", ...corsHeaders }
646
+ }
615
647
  );
616
648
 
617
- } catch (error) {
649
+ } catch (error: any) {
618
650
  console.error("Image analysis error:", error);
619
651
  return new Response(
620
- JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }),
621
- { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
652
+ JSON.stringify({ error: error.message || "Unknown error" }),
653
+ {
654
+ status: 500,
655
+ headers: { "Content-Type": "application/json", ...corsHeaders }
656
+ }
622
657
  );
623
658
  }
624
- });
659
+ };
660
+
661
+ serve(handler);
625
662
  ```
626
663
 
627
664
  ## Implementation Checklist
@@ -341,48 +341,87 @@ Create functions in `/supabase/functions/<function-name>/index.ts`
341
341
  - **Runtime**: Deno with TypeScript support
342
342
  - **Import Supabase**: `import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'`
343
343
  - **Environment Variables**: `Deno.env.get('VARIABLE_NAME')`
344
+ - **Standard Library**: Use Deno standard library version 0.190.0 or higher
345
+
346
+ ### Edge Function Best Practices
347
+
348
+ - ✅ Always include CORS headers for cross-origin requests
349
+ - ✅ Handle OPTIONS requests for CORS preflight
350
+ - ✅ Define TypeScript interfaces for request/response types
351
+ - ✅ Use try-catch for comprehensive error handling
352
+ - ✅ Log errors with `console.error()` for debugging
353
+ - ✅ Return consistent response format with status codes
354
+ - ✅ Validate input data before processing
355
+ - ✅ Use environment variables for sensitive data
344
356
 
345
357
  ### Edge Function Example
346
358
 
347
359
  ```typescript
348
- import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
349
- import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
360
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
361
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
362
+
363
+ // CORS headers for cross-origin requests
364
+ const corsHeaders = {
365
+ "Access-Control-Allow-Origin": "*",
366
+ "Access-Control-Allow-Headers":
367
+ "authorization, x-client-info, apikey, content-type",
368
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
369
+ "Access-Control-Max-Age": "86400",
370
+ };
371
+
372
+ // Request interface
373
+ interface UserQueryRequest {
374
+ name: string;
375
+ }
376
+
377
+ const handler = async (req: Request): Promise<Response> => {
378
+ // Handle CORS preflight requests
379
+ if (req.method === "OPTIONS") {
380
+ return new Response(null, {
381
+ status: 200,
382
+ headers: corsHeaders
383
+ });
384
+ }
350
385
 
351
- serve(async (req) => {
352
386
  try {
353
- const { name } = await req.json();
354
-
387
+ const { name }: UserQueryRequest = await req.json();
388
+
355
389
  // Create Supabase client
356
390
  const supabaseClient = createClient(
357
- Deno.env.get('SUPABASE_URL') ?? '',
358
- Deno.env.get('SUPABASE_ANON_KEY') ?? ''
391
+ Deno.env.get("SUPABASE_URL") ?? "",
392
+ Deno.env.get("SUPABASE_ANON_KEY") ?? ""
359
393
  );
360
-
394
+
361
395
  // Execute business logic
362
396
  const { data, error } = await supabaseClient
363
- .from('users')
364
- .select('*')
365
- .eq('name', name);
366
-
367
- if (error) throw error;
368
-
397
+ .from("users")
398
+ .select("*")
399
+ .eq("name", name);
400
+
401
+ if (error) {
402
+ throw new Error(error.message);
403
+ }
404
+
369
405
  return new Response(
370
- JSON.stringify({ data }),
371
- {
372
- headers: { 'Content-Type': 'application/json' },
373
- status: 200
406
+ JSON.stringify({ success: true, data }),
407
+ {
408
+ status: 200,
409
+ headers: { "Content-Type": "application/json", ...corsHeaders },
374
410
  }
375
411
  );
376
- } catch (error) {
412
+ } catch (error: any) {
413
+ console.error("Error querying users:", error);
377
414
  return new Response(
378
415
  JSON.stringify({ error: error.message }),
379
- {
380
- headers: { 'Content-Type': 'application/json' },
381
- status: 400
416
+ {
417
+ status: 500,
418
+ headers: { "Content-Type": "application/json", ...corsHeaders },
382
419
  }
383
420
  );
384
421
  }
385
- });
422
+ };
423
+
424
+ serve(handler);
386
425
  ```
387
426
 
388
427
  ### Invoking from Client
@@ -391,16 +430,38 @@ serve(async (req) => {
391
430
  const { data, error } = await supabase.functions.invoke('function-name', {
392
431
  body: { name: 'John' }
393
432
  });
433
+
434
+ if (error) {
435
+ console.error('Function invocation error:', error);
436
+ } else {
437
+ console.log('Response:', data);
438
+ }
394
439
  ```
395
440
 
396
441
  ### Common Use Cases
397
442
 
398
- - Webhook handlers
399
- - Scheduled jobs
400
- - Third-party API calls
401
- - Complex business logic
402
- - Payment processing
403
- - Email sending
443
+ - **Webhook handlers**: Process incoming webhooks from third-party services
444
+ - **Scheduled jobs**: Execute periodic tasks (with cron triggers)
445
+ - **Third-party API calls**: Integrate with external services
446
+ - **Complex business logic**: Server-side computations and validations
447
+ - **Payment processing**: Handle payment transactions securely
448
+ - **Email sending**: Send transactional emails (e.g., with Resend)
449
+ - **Authentication flows**: Custom authentication logic
450
+ - **Data transformations**: Process and transform data before storage
451
+
452
+ ### Environment Variables in Edge Functions
453
+
454
+ Store sensitive data in environment variables (managed via Needware `add-secret` tool):
455
+
456
+ ```typescript
457
+ // Common environment variables
458
+ const SUPABASE_URL = Deno.env.get("SUPABASE_URL");
459
+ const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY");
460
+ const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
461
+ const CUSTOM_API_KEY = Deno.env.get("CUSTOM_API_KEY");
462
+ ```
463
+
464
+ **Note**: Use `add-secret` tool to securely add environment variables to your Edge Functions.
404
465
 
405
466
  ## Security
406
467