@giteeteam/apps-cli 1.0.0 → 1.0.1
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/dist/index.js +1 -1
- package/dist/templates/skill/SKILL.md +21 -16
- package/dist/templates/skill/guides/01-overview.md +19 -3
- package/dist/templates/skill/guides/02-extension-points.md +13 -2
- package/dist/templates/skill/guides/03-api-routing-rules.md +14 -13
- package/dist/templates/skill/guides/04-deploy-and-subscribe.md +17 -10
- package/dist/templates/skill/guides/05-agent-prompt-template.md +13 -6
- package/dist/templates/skill/guides/06-data-access-and-tables.md +58 -0
- package/dist/templates/skill/references/README.md +10 -3
- package/package.json +1 -1
- package/templates/skill/SKILL.md +21 -16
- package/templates/skill/guides/01-overview.md +19 -3
- package/templates/skill/guides/02-extension-points.md +13 -2
- package/templates/skill/guides/03-api-routing-rules.md +14 -13
- package/templates/skill/guides/04-deploy-and-subscribe.md +17 -10
- package/templates/skill/guides/05-agent-prompt-template.md +13 -6
- package/templates/skill/guides/06-data-access-and-tables.md +58 -0
- package/templates/skill/references/README.md +10 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var fr=Object.create;var pe=Object.defineProperty;var dr=Object.getOwnPropertyDescriptor;var gr=Object.getOwnPropertyNames;var yr=Object.getPrototypeOf,hr=Object.prototype.hasOwnProperty;var vr=(e,t)=>()=>(e&&(t=e(e=0)),t);var wr=(e,t)=>{for(var r in t)pe(e,r,{get:t[r],enumerable:!0})},br=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of gr(t))!hr.call(e,o)&&o!==r&&pe(e,o,{get:()=>t[o],enumerable:!(n=dr(t,o))||n.enumerable});return e};var c=(e,t,r)=>(r=e!=null?fr(yr(e)):{},br(t||!e||!e.__esModule?pe(r,"default",{value:e,enumerable:!0}):r,e));var We={};wr(We,{default:()=>Sr});function Sr({environment:e,appKey:t,distOutputPath:r="dist"}){let n=e==="production",o=e==="development",s=Tr(),i=Ve.default.existsSync(R.default.resolve("locales")),a=D=>D?{filename:"js/[name].[chunkhash].min.js",path:R.default.resolve(r),publicPath:"./",library:{type:"umd",name:t},clean:!0}:{filename:"main.js",path:R.default.resolve(r),publicPath:"/",library:{type:"umd",name:t}},p=({resourcePath:D},U,w)=>w===t||/\.global\.(css|less)$/.test(D)||/node_modules/.test(D)?w:`${w}__${(0,He.default)(`${D}${w}`,{algorithm:"md5"}).slice(0,8)}`,m={loader:"@ecomfe/class-names-loader",options:{classNamesModule:require.resolve("classnames")}},l=n?L.rspack.CssExtractRspackPlugin.loader:"style-loader",u={loader:"postcss-loader",options:{postcssOptions:{plugins:[ze.default]}}},g={loader:"css-loader",options:{modules:{getLocalIdent:p},importLoaders:1}},v={loader:"less-loader",options:{lessOptions:{javascriptEnabled:!0,modifyVars:{"ant-prefix":t}}}};return{mode:e,entry:"./src/index.tsx",output:a(n),devtool:n?!1:"source-map",resolve:{alias:{"@":R.default.join(process.cwd(),"src/")},extensions:[".js",".ts",".jsx",".tsx",".css",".less"]},optimization:{minimize:n},plugins:[new L.rspack.DefinePlugin({"process.env":JSON.stringify(process.env),"process.env.appKey":JSON.stringify(t)}),new L.rspack.HtmlRspackPlugin({template:R.default.resolve("public/index.html"),filename:"index.html",inject:!0,appKey:t}),n&&new L.rspack.CssExtractRspackPlugin({filename:"[name].[contenthash].css",chunkFilename:"[name].[contenthash].chunk.css"}),o&&new L.rspack.HotModuleReplacementPlugin,o&&new qe.default({overlay:!1})].filter(Boolean),module:{rules:[{oneOf:[{test:/\.(js|mjs|jsx|ts|tsx)$/,exclude:/node_modules/,loader:"builtin:swc-loader",options:{sourceMaps:Or,jsc:{parser:{syntax:"typescript",tsx:!0},transform:{react:{runtime:"automatic",development:!n,refresh:!n}}},env:{targets:"chrome >= 72",mode:"usage",coreJs:"3.38"}},type:"javascript/auto"},s&&i&&{test:/locales/,loader:s,include:[R.default.resolve("locales")]},{test:/\.css$/,use:[m,l,"css-loader",u]},{test:/\.less$/,use:[m,l,g,u,v]},{test:/\.(png|jpg|gif)$/i,type:"asset",parser:{dataUrlCondition:{maxSize:2400}},generator:{filename:"[name]_[hash][ext]"}},{test:/\.svg$/i,use:["@svgr/webpack"]}]}]}}}var Ve,R,L,qe,ze,He,Or,Tr,Je=vr(()=>{"use strict";Ve=c(require("fs")),R=c(require("path")),L=require("@rspack/core"),qe=c(require("@rspack/plugin-react-refresh")),ze=c(require("autoprefixer")),He=c(require("hasha")),Or=process.env.GENERATE_SOURCEMAP==="true",Tr=()=>{try{return require.resolve("@alienfast/i18next-loader")}catch{return null}}});var ys=require("dotenv/config"),Te=c(require("chalk")),cr=require("commander");var E="1.0.
|
|
2
|
+
"use strict";var fr=Object.create;var pe=Object.defineProperty;var dr=Object.getOwnPropertyDescriptor;var gr=Object.getOwnPropertyNames;var yr=Object.getPrototypeOf,hr=Object.prototype.hasOwnProperty;var vr=(e,t)=>()=>(e&&(t=e(e=0)),t);var wr=(e,t)=>{for(var r in t)pe(e,r,{get:t[r],enumerable:!0})},br=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of gr(t))!hr.call(e,o)&&o!==r&&pe(e,o,{get:()=>t[o],enumerable:!(n=dr(t,o))||n.enumerable});return e};var c=(e,t,r)=>(r=e!=null?fr(yr(e)):{},br(t||!e||!e.__esModule?pe(r,"default",{value:e,enumerable:!0}):r,e));var We={};wr(We,{default:()=>Sr});function Sr({environment:e,appKey:t,distOutputPath:r="dist"}){let n=e==="production",o=e==="development",s=Tr(),i=Ve.default.existsSync(R.default.resolve("locales")),a=D=>D?{filename:"js/[name].[chunkhash].min.js",path:R.default.resolve(r),publicPath:"./",library:{type:"umd",name:t},clean:!0}:{filename:"main.js",path:R.default.resolve(r),publicPath:"/",library:{type:"umd",name:t}},p=({resourcePath:D},U,w)=>w===t||/\.global\.(css|less)$/.test(D)||/node_modules/.test(D)?w:`${w}__${(0,He.default)(`${D}${w}`,{algorithm:"md5"}).slice(0,8)}`,m={loader:"@ecomfe/class-names-loader",options:{classNamesModule:require.resolve("classnames")}},l=n?L.rspack.CssExtractRspackPlugin.loader:"style-loader",u={loader:"postcss-loader",options:{postcssOptions:{plugins:[ze.default]}}},g={loader:"css-loader",options:{modules:{getLocalIdent:p},importLoaders:1}},v={loader:"less-loader",options:{lessOptions:{javascriptEnabled:!0,modifyVars:{"ant-prefix":t}}}};return{mode:e,entry:"./src/index.tsx",output:a(n),devtool:n?!1:"source-map",resolve:{alias:{"@":R.default.join(process.cwd(),"src/")},extensions:[".js",".ts",".jsx",".tsx",".css",".less"]},optimization:{minimize:n},plugins:[new L.rspack.DefinePlugin({"process.env":JSON.stringify(process.env),"process.env.appKey":JSON.stringify(t)}),new L.rspack.HtmlRspackPlugin({template:R.default.resolve("public/index.html"),filename:"index.html",inject:!0,appKey:t}),n&&new L.rspack.CssExtractRspackPlugin({filename:"[name].[contenthash].css",chunkFilename:"[name].[contenthash].chunk.css"}),o&&new L.rspack.HotModuleReplacementPlugin,o&&new qe.default({overlay:!1})].filter(Boolean),module:{rules:[{oneOf:[{test:/\.(js|mjs|jsx|ts|tsx)$/,exclude:/node_modules/,loader:"builtin:swc-loader",options:{sourceMaps:Or,jsc:{parser:{syntax:"typescript",tsx:!0},transform:{react:{runtime:"automatic",development:!n,refresh:!n}}},env:{targets:"chrome >= 72",mode:"usage",coreJs:"3.38"}},type:"javascript/auto"},s&&i&&{test:/locales/,loader:s,include:[R.default.resolve("locales")]},{test:/\.css$/,use:[m,l,"css-loader",u]},{test:/\.less$/,use:[m,l,g,u,v]},{test:/\.(png|jpg|gif)$/i,type:"asset",parser:{dataUrlCondition:{maxSize:2400}},generator:{filename:"[name]_[hash][ext]"}},{test:/\.svg$/i,use:["@svgr/webpack"]}]}]}}}var Ve,R,L,qe,ze,He,Or,Tr,Je=vr(()=>{"use strict";Ve=c(require("fs")),R=c(require("path")),L=require("@rspack/core"),qe=c(require("@rspack/plugin-react-refresh")),ze=c(require("autoprefixer")),He=c(require("hasha")),Or=process.env.GENERATE_SOURCEMAP==="true",Tr=()=>{try{return require.resolve("@alienfast/i18next-loader")}catch{return null}}});var ys=require("dotenv/config"),Te=c(require("chalk")),cr=require("commander");var E="1.0.1";var de=c(require("path"));var B=["manifest.yml","locales"],je=Buffer.from(process.env.ONE_AUTH_IV||"ABCDEFGHIJKLM_iv","utf-8"),_e=Buffer.from(process.env.ONE_AUTH_SECRET_KEY||"ABCDEFGHIJKL_key","utf-8"),Le=process.env.ONE_AUTH_ALGORITHM||"aes-128-cbc";var H=c(require("chalk")),at=c(require("fs-extra"));var Ie=c(require("fs")),me=c(require("path")),Me=c(require("@rollup/plugin-commonjs")),Fe=require("rollup");var _=require("fs"),b=require("path"),$e=require("esbuild"),Pr={treeShaking:!0,format:"esm",loader:"tsx"};var xr=({source:e,dirPath:t,mode:r})=>{let n="";if(r==="development")try{return n=require.resolve(e+"/src/index.ts",{paths:[t]}),n}catch{}try{return n=require.resolve(e,{paths:[t]}),n}catch{}return n};function le({mode:e,...t}){return t={...Pr,...t},{name:"esbuild",resolveId(r,n,o){if(!n)return r;let s=(0,b.dirname)(n),i;if((0,_.existsSync)(i=(0,b.resolve)(s,r,"index.ts")))return i;if((0,_.existsSync)(i=(0,b.resolve)(s,r,"index.tsx")))return i;if((0,_.existsSync)(i=(0,b.resolve)(s,r+".ts")))return i;if((0,_.existsSync)(i=(0,b.resolve)(s,r+".tsx")))return i;{let a=xr({source:r,dirPath:s,mode:e});if((0,_.existsSync)(a))return a;throw console.error({source:r,importer:n,options:o}),i=(0,b.relative)(".",(0,b.resolve)(n,r)),new Error(`ENOENT: ${i.replace(/\\/g,"/")}`)}},async transform(r,n){t.sourcefile=n,t.loader=n.endsWith(".json")?"json":t.loader;let{code:o,map:s}=await(0,$e.transform)(r,t);return s?{code:o,map:s}:o}}}function Er(e,t,r="development"){let n=[(0,Me.default)()];n.push(le({mode:r,sourcemap:r==="development",minify:r!=="development",target:"es2018",jsx:"transform",jsxFactory:"AppsUI.createElement",jsxFragment:"AppsUI.Fragment"}));let o={input:e,plugins:n},s={file:t,format:"es",sourcemap:r==="development"};return{inputOptions:o,outputOptions:s}}async function G(e){let{fileName:t,inputDir:r,outputDir:n,mode:o="development"}=e,s=me.default.join(r,`${t}.ts`);try{await Ie.default.promises.access(s)}catch{console.error(`${s} is not exist`);return}let i=me.default.join(n,`${t}.js`),{inputOptions:a,outputOptions:p}=Er(s,i,o);await(await(0,Fe.rollup)(a)).write(p)}var Ue=c(require("ora")),d=async(e,t)=>{var n,o;let r=(0,Ue.default)(e.start).start();try{let s=await t();return r.succeed(e.succeed),s}catch(s){throw r.fail(`${e.error}: ${((o=(n=s==null?void 0:s.response)==null?void 0:n.data)==null?void 0:o.message)||s.message}`),s}};var Ke=c(require("path")),Be=require("@giteeteam/apps-manifest"),Ge=c(require("fs-extra")),O=async()=>{let e=Ke.default.resolve("manifest.yml"),t=await Ge.default.readFile(e);return new Be.Manifest(t.toString())};var Ye=c(require("path")),Xe=require("@rspack/core");var V=async()=>(await Promise.resolve().then(()=>(Je(),We))).default;var Qe=e=>Object.prototype.toString.call(e)==="[object Object]",I=(e,t)=>{if(t===void 0)return e;if(Array.isArray(e)&&Array.isArray(t))return[...e,...t];if(Qe(e)&&Qe(t)){let r={...e};for(let[n,o]of Object.entries(t))r[n]=n in r?I(r[n],o):o;return r}return t};var Ze=async({appKey:e,rspackConfig:t})=>{let r=await V();return new Promise((n,o)=>{let s=r({environment:"production",appKey:e});if(t){let a=require(Ye.default.resolve(t));s=I(s,a)}(0,Xe.rspack)(s).run((a,p)=>{if(a)return console.error(a),o(a);if(console.info(p==null?void 0:p.toString()),p!=null&&p.hasErrors())return o(new Error("build-ui-error"));n()})})};var et=c(require("path")),tt=c(require("archiver")),ue=c(require("fs-extra")),rt=async(e,t)=>{let r=et.default.resolve(t);return await ue.default.remove(r),new Promise((n,o)=>{let s=ue.default.createWriteStream(r),i=(0,tt.default)("zip",{zlib:{level:9}});s.on("close",function(){n()}),i.on("warning",function(a){console.info(a),a.code==="ENOENT"||o(a)}),i.on("error",function(a){console.info(a),o(a)}),i.pipe(s),i.directory(e,!1),i.finalize()})};var nt=async e=>{let{manifest:t,mode:r,outputDir:n,inputDir:o}=e,i=t.getFunctionFiles().map(async a=>{await G({inputDir:o,fileName:a,outputDir:n,mode:r})});await Promise.all(i)};var z=c(require("path")),st=c(require("fs-extra"));var fe=c(require("path")),ot=c(require("fs-extra")),q=async(e,t)=>{for(let r of t)try{let n=fe.default.resolve(r),o=fe.default.join(e,r);await ot.default.copy(n,o)}catch(n){if(n.code==="ENOENT"&&r==="locales")continue;throw n}};var it=async({outputDir:e,manifest:t,filesToCopy:r})=>{let{resources:n}=t;if(n)for(let{key:o,path:s}of n){let i=z.default.resolve(z.default.join("./",s)),a=z.default.join(e,o);await st.default.copy(i,a)}await q(e,r)};var ct=async({outputDir:e,inputDir:t,prod:r,filesToCopy:n,zip:o,ui:s,rspackConfig:i})=>{let a=await O(),{version:p,key:m}=a.app;if(!m){console.error("manifest app key is required");return}if(!p){console.error("manifest app version is required");return}console.info(`App key: ${H.default.bold.green(m)}`),console.info(`App version: ${H.default.bold.green(p)}
|
|
3
3
|
`);let{pass:l,errors:u}=a.validate();if(!l){if(u!=null&&u.length){let{message:g,path:v}=u[0];console.error(`Manifest valid error: Path '${v}', ${g}`)}else console.error("Manifest valid error");return}if(await at.default.emptyDir(e),s&&await d({start:"start build ui",error:"build ui failed",succeed:"build ui success"},()=>Ze({appKey:m,rspackConfig:i})),await d({start:"start bundle",error:"bundle failed",succeed:"bundle success"},()=>nt({outputDir:e,inputDir:t,manifest:a,mode:r?"production":"development"})),await d({start:"start package",error:"package failed",succeed:"package success"},()=>it({outputDir:e,manifest:a,filesToCopy:n})),o){let g=`${m}_${p}.zip`;await d({start:"start archive",error:"archive failed",succeed:"archive success"},()=>rt(e,g)),console.info(`
|
|
4
4
|
The output file of the build is ${H.default.bold.green(g)}`)}};var pt=async e=>{let{input:t="functions",output:r="package",prod:n,dev:o,copy:s=[],zip:i,ui:a,rspack:p}=e;await ct({outputDir:de.default.resolve(r),inputDir:de.default.resolve(t),prod:n??!o,filesToCopy:[...B,...s],zip:i,ui:!!a,rspackConfig:p}).catch(()=>{})};var bt=c(require("fs")),kt=c(require("path")),J=c(require("chalk"));var lt=c(require("path")),mt=c(require("lowdb")),ut=c(require("lowdb/adapters/FileSync"));var ge,ye=()=>{if(!ge){let e=lt.default.join(__dirname,"user.json"),t=new ut.default(e);ge=(0,mt.default)(t)}return ge},ft=()=>{let e=ye(),t=e.get("version").value();(!t||t!==E)&&e.defaults({version:E,user:{}}).write()},he=e=>{ft(),ye().set("user",e).write()},T=()=>(ft(),ye().get("user").value());var gt=c(require("fs")),yt=c(require("path")),ht=c(require("form-data"));var dt=c(require("axios"));var k={GET:"GET",POST:"POST",PUT:"PUT",DELETE:"DELETE"},S=async({url:e,method:t,data:r,headers:n})=>{let{domain:o,cookie:s}=T();return(await(0,dt.default)({method:t,baseURL:o,url:e,data:r,headers:{...n,cookie:s}})).data};var vt=async(e,t)=>{let r=new ht.default;return r.append("environmentKey",t),r.append("package",gt.default.createReadStream(yt.default.resolve(e))),S({method:k.POST,url:"/apps/api/v1/apps/deploy",data:r,headers:{...r.getHeaders()}})},W=async({tenants:e,environmentKey:t,appKey:r,versionKey:n})=>S({method:k.POST,url:"/apps/api/v1/apps/install/batch",data:{tenants:e,environmentKey:t,appKey:r,versionKey:n,subscriptionSource:"manual"}});var wt=c(require("chalk")),Dr=wt.default.bold.red,f={error:e=>{console.info(Dr(e))}};var Q=(n=>(n.DEVELOPMENT="development",n.STAGING="staging",n.PRODUCTION="production",n))(Q||{}),Cr=e=>e?(Array.isArray(e)?e:[e]).flatMap(r=>r.split(",")).map(r=>r.trim()).filter(Boolean):[],Rr=e=>{let t=kt.default.basename(e,".zip"),r=t.lastIndexOf("_");return r<=0||r===t.length-1?null:{appKey:t.slice(0,r),versionKey:t.slice(r+1)}},Pt=async(e,t)=>{let{domain:r}=T(),{environment:n="development",subscribe:o,appKey:s,versionKey:i}=t,a=Cr(o);if(!Object.values(Q).includes(n)){f.error("Only support development, staging, production environment.");return}if(!r){f.error("Please login in first!");return}if(!bt.default.existsSync(e)){f.error(`File:${e} is not exist`);return}if(console.info(`Deploying to ${J.default.bold.green(r)}`),await d({start:"start deploy",error:"deploy failed",succeed:"deploy success"},()=>vt(e,n)).catch(()=>{}),a.length===0)return;let p=Rr(e),m=s||(p==null?void 0:p.appKey),l=i||(p==null?void 0:p.versionKey);if(!m||!l){f.error("subscribe needs appKey and versionKey. please pass --app-key and --version-key, or use zip name <appKey>_<versionKey>.zip");return}console.info(`Subscribing app ${J.default.bold.green(m)}:${J.default.bold.green(l)}`),await d({start:"start subscribe",error:"subscribe failed",succeed:"subscribe success"},()=>W({tenants:a,environmentKey:n,appKey:m,versionKey:l})).catch(()=>{})};var N=c(require("path")),M=c(require("fs-extra")),ke=c(require("inquirer"));var xt=c(require("readline")),Et=require("chalk"),Ot=c(require("execa")),Ar=e=>{if(!Et.supportsColor){e.write("\r");return}xt.default.cursorTo(e,0)},jr=(e,t)=>{let r=Math.min(Math.max(e/t,0),1),n=` ${e}/${t}`,o=Math.max(0,process.stderr.columns-n.length-3),s=Math.min(t,o),i=Math.round(s*r),a="#".repeat(i),p="-".repeat(s-i);Ar(process.stderr),process.stderr.write(`[${a}${p}]${n}`)},_r=(e,t,r,n)=>new Promise((o,s)=>{var a;let i=(0,Ot.default)(e,t,{cwd:r,stdio:["inherit","inherit","pipe"],env:n});(a=i.stderr)==null||a.on("data",p=>{let m=p.toString();if(/warning/.test(m))return;let l=m.match(/\[.*] (\d+)\/(\d+)/);if(l){jr(parseFloat(l[1]),parseFloat(l[2]));return}process.stderr.write(p)}),i.on("close",p=>{if(p!==0){s(new Error(`command failed: ${e} ${t.join(" ")}`));return}o()})}),Tt={executeCommand:_r};var ve=c(require("fs-extra")),we=c(require("simple-git")),St=async e=>{await(0,we.default)(e).init()},Y=async(e,t,r)=>{await ve.default.pathExists(t)||await ve.default.mkdir(t);let n=(0,we.default)(t),o=[];r!=null&&r.branch&&o.push("--branch",r.branch),r!=null&&r.singleBranch&&o.push("--single-branch"),await n.clone(e,t,o)};var P=c(require("path")),A=c(require("fs-extra"));var X=c(require("fs-extra")),Dt=process.env.APPS_TEMPLATE_REPO||"https://github.com/moriahq/apps-template.git",be=(r=>(r.micro="micro",r.uikit="uikit",r))(be||{}),Z=async(e,t)=>{if(!await X.default.pathExists(e))return;let r=await X.default.readFile(e,{encoding:"utf-8"});await X.default.writeFile(e,r.replace(/{{(\w+)}}/g,(n,o)=>typeof t[o]=="string"?t[o]:n))};var $r=async()=>{let e=[P.default.resolve(__dirname,"templates/skill"),P.default.resolve(__dirname,"../templates/skill")];for(let t of e)if(await A.default.pathExists(t))return t;return""},Ct=async(e,t)=>{let r=await A.default.readdir(e,{withFileTypes:!0});return(await Promise.all(r.map(async o=>{let s=P.default.join(e,o.name);return o.isDirectory()?Ct(s,t):o.isFile()?[P.default.relative(t,s)]:[]}))).flat()},$=async(e,t)=>{let{appName:r,appKey:n,overwrite:o=!1}=t,s=await $r();if(!s||!await A.default.pathExists(s))throw new Error(`skill templates not found: ${s}`);let i=await Ct(s,s);for(let a of i){let p=P.default.join(s,a),m=a==="SKILL.md"||a.startsWith("skill/")?a:P.default.join("skill",a),l=P.default.join(e,m);!o&&await A.default.pathExists(l)||(await A.default.ensureDir(P.default.dirname(l)),await A.default.copy(p,l),l.endsWith(".md")&&await Z(l,{appName:r,appKey:n}))}};var Nr=async()=>(await ke.default.createPromptModule()({type:"input",name:"App Name",message:"Please enter an app name",validate:r=>!!r}))["App Name"],Ir=async()=>(await ke.default.createPromptModule()({type:"input",name:"App key",message:"Please enter a valid app key",validate:r=>/^[a-z][a-z0-9_]*$/.test(r)?!0:(console.info(""),console.info("only supports letters, numbers, underscores and starts with letters"),!1)}))["App key"],Mr=()=>{if(Number(E.split(".")[0])>=1)return"v1"},Fr=async(e,t)=>{let r=Mr();if(!r){await d({start:"Start clone repository",error:"Clone repository failed!",succeed:"Clone repository success!"},()=>Y(e,t));return}try{await d({start:`Start clone repository (branch: ${r})`,error:"Clone repository failed!",succeed:"Clone repository success!"},()=>Y(e,t,{branch:r,singleBranch:!0}))}catch{console.info(`Template branch '${r}' not found, fallback to default branch.`),await d({start:"Start clone repository (default branch)",error:"Clone repository failed!",succeed:"Clone repository success!"},()=>Y(e,t))}},Ur=async e=>{await d({start:"Start install dependencies",error:"Install dependencies failed!",succeed:"Install dependencies success!"},()=>Tt.executeCommand("pnpm",["install"],e))},Rt=async e=>{let{cwd:t}=e,r="micro",n=await Nr(),o=await Ir(),s=N.default.join(t,o);if(await M.default.pathExists(s)){console.error(`Project path already exists: ${s}`);return}await Fr(Dt,s),await M.default.copy(N.default.join(s,r),s),await Promise.all(Object.values(be).map(a=>M.default.remove(N.default.join(s,a)))),await M.default.remove(N.default.join(s,"./.git"));let i=["./manifest.yml","./package.json","./rspack.config.js","./public/index.html","./src/App.tsx","./src/index.tsx","./static/App.tsx","./static/index.tsx","./SKILL.md"];await Promise.all(i.map(a=>Z(N.default.join(s,a),{appName:n,appKey:o}))),await $(s,{appName:n,appKey:o}),await Ur(s),await St(s),console.info("Project init success!"),console.info("Project path:"+s)};var At=async()=>{let e=process.cwd();await Rt({cwd:e})};var ee=c(require("inquirer"));var Pe=c(require("axios"));var jt=["/api/gateway/login","/login"],Kr=e=>{var n;let t=(n=e==null?void 0:e.response)==null?void 0:n.status,r=e==null?void 0:e.code;return[404,405,502,503,504].includes(t)||["ECONNREFUSED","ENOTFOUND","ECONNABORTED"].includes(r)},_t=async({baseUrl:e,username:t,password:r})=>{var o;let n;for(let s of jt)try{let i=await(0,Pe.default)({method:k.POST,url:`${e}${s}`,data:{username:t,password:r}});return console.info(`Login endpoint: ${s}`),{cookie:(o=i.headers)==null?void 0:o["set-cookie"]}}catch(i){if(n=i,!Kr(i))return console.error(`Login error (${s}):`,i==null?void 0:i.message),{cookie:""}}return console.error(`Login error: all endpoints failed (${jt.join(", ")})`,(n==null?void 0:n.message)||""),{cookie:""}},Lt=async e=>{try{return await(0,Pe.default)({method:k.GET,url:`${e}/apps/api/v1/tenants`}),!1}catch{return!0}};var $t=e=>{try{return new URL(e).origin}catch{return e}};var Nt=c(require("crypto"));var It=e=>{try{let t=Nt.default.createCipheriv(Le,_e,je),r=t.update(e,"utf8","base64");return r+=t.final("base64"),r.toString()}catch(t){return console.error(`Encryption error: ${t}`),null}};var Br=async()=>(await ee.default.createPromptModule()({type:"input",name:"Domain",message:"Please enter the domain address",validate:r=>{try{return new URL(r),!0}catch{return"Please enter a valid domain name"}}})).Domain,Gr=async()=>(await ee.default.createPromptModule()({type:"input",name:"Username",message:"Please enter username",validate:r=>!!r})).Username,Vr=async()=>(await ee.default.createPromptModule()({type:"password",name:"Password",message:"Please enter password",validate:r=>!!r})).Password,Mt=async({domain:e,username:t,password:r})=>{if(e||(e=await Br()),e=$t(e),await Lt(e)){t||(t=await Gr()),r||(r=await Vr());let{cookie:o}=await _t({baseUrl:e,username:t,password:It(r)||r});o?(he({domain:e,username:t,cookie:o}),console.info("Login success!")):console.error("Login failed!")}else he({domain:e}),console.info("Login success!")};var Ft=async e=>{await Mt(e)};var Ut=c(require("chalk"));var qr=e=>e?(Array.isArray(e)?e:[e]).flatMap(r=>r.split(",")).map(r=>r.trim()).filter(Boolean):[],Kt=async(e,t,r)=>{let{domain:n}=T(),{environment:o="development",tenants:s}=r,i=qr(s);if(!n){f.error("Please login in first!");return}if(!Object.values(Q).includes(o)){f.error("Only support development, staging, production environment.");return}if(!e||!t){f.error("Please provide appKey and versionKey.");return}if(i.length===0){f.error("Please provide subscribe tenants by -t/--tenants.");return}console.info(`Subscribing to ${Ut.default.bold.green(n)}`),await d({start:"start subscribe",error:"subscribe failed",succeed:"subscribe success"},()=>W({tenants:i,environmentKey:o,appKey:e,versionKey:t})).catch(()=>{})};var te=c(require("path")),re=c(require("fs-extra"));var zr=async e=>{var a,p,m,l;let t=te.default.join(e,"manifest.yml"),r=te.default.basename(e),n=r;if(!await re.default.pathExists(t))return{appName:r,appKey:n};let o=await re.default.readFile(t,"utf8"),s=((p=(a=o.match(/^\s*name:\s*(.+)\s*$/m))==null?void 0:a[1])==null?void 0:p.trim())||r,i=((l=(m=o.match(/^\s*key:\s*(.+)\s*$/m))==null?void 0:m[1])==null?void 0:l.trim())||n;return{appName:s,appKey:i}},Bt=async e=>{let t=te.default.resolve(e||process.cwd());if(!await re.default.pathExists(t)){f.error(`project path not found: ${t}`);return}let{appName:r,appKey:n}=await zr(t);await $(t,{appName:r,appKey:n,overwrite:!0}),console.info("sync-skill finished."),console.info(`Project path: ${t}`)};var ie=c(require("path"));var tr=c(require("path")),se=c(require("chalk")),rr=c(require("debug")),Ee=c(require("fs-extra"));var Gt=async()=>{var t;return((t=(await S({method:k.GET,url:"/apps/api/v1/config"}).catch(r=>{var n,o;console.info(`getRuntimeConfig error ${((o=(n=r==null?void 0:r.response)==null?void 0:n.data)==null?void 0:o.message)||(r==null?void 0:r.message)}`)})).data)==null?void 0:t.runtime)||{}};var ne=require("child_process"),F=class{constructor(t,r){this.imageName=t,this.containerName=r}async downloadImage(){console.info(`Downloading image(${this.imageName}) ...`),await this.execPromise(`docker pull ${this.imageName}`),console.info(`Download image(${this.imageName}) success!`)}async runContainer({port:t,env:r,volume:n=[]}){await this.removeContainer();let o=Object.keys(r).filter(a=>r[a]!==void 0).map(a=>`-e ${a}=${r[a]}`).join(" "),s=n==null?void 0:n.map(a=>`-v ${a}`).join(" "),i=`docker run -d -p ${t}:${t} ${o} ${s} --name ${this.containerName} ${this.imageName}`;await this.execPromise(i)}async removeContainer(){await this.execPromise(`docker rm -f ${this.containerName}`).catch(()=>{})}logs(){let t=(0,ne.spawn)("docker",["logs","-f",this.containerName]);t.stdout.on("data",r=>{console.info(`${r}`)}),t.stderr.on("data",r=>{console.info(`${r}`)})}execPromise(t){return new Promise((r,n)=>{(0,ne.exec)(t,(o,s,i)=>{if(o)return n(o);r({stdout:s,stderr:i})})})}};var Vt=c(require("localtunnel"));var qt=async({appKey:e,runtimeHost:t,resources:r,resourcePort:n})=>{r!=null&&r.length&&r.forEach(o=>{o.path=`http://127.0.0.1:${n}`}),await S({method:k.POST,url:`/apps/api/v1/apps/tunnel/${e}`,data:{resources:r,runtimeHost:t}}).catch(o=>{var s,i;console.info(`addTunnel error ${((i=(s=o==null?void 0:o.response)==null?void 0:s.data)==null?void 0:i.message)||(o==null?void 0:o.message)}`)})},zt=async e=>{await S({method:"DELETE",url:`/apps/api/v1/apps/tunnel/${e}`}).catch(()=>{})},Ht=async e=>{var t,r;try{return(await(0,Vt.default)({port:e,host:((t=process.env)==null?void 0:t.TUNNEL_HOST)==="OFFICIAL"?void 0:((r=process.env)==null?void 0:r.TUNNEL_HOST)||"http://tunnel.gitee.work:1777"})).url}catch(n){return console.info("runTunnel error",n==null?void 0:n.message),""}};var xe=c(require("path")),Wt=require("@rspack/core"),Jt=require("@rspack/dev-middleware"),Qt=c(require("cors")),Yt=c(require("express"));var oe=async({appKey:e,rspackConfig:t})=>{let n=(await V())({environment:"development",appKey:e});if(t){let p=require(xe.default.resolve(t));n=I(n,p)}let o=(0,Wt.rspack)(n),s=(0,Yt.default)(),i=Number(process.env.UI_PORT)||8e3,a=(0,Jt.devMiddleware)(o);s.use((0,Qt.default)()),s.use(a),s.get("*",(p,m)=>{var l,u;(u=o.outputFileSystem)==null||u.readFile(xe.default.join(((l=n.output)==null?void 0:l.path)||"","index.html"),(g,v)=>{if(g||!v){m.sendStatus(404);return}m.send(v.toString())})}),s.listen(i,"0.0.0.0",()=>{console.info(`The React app is listening on port ${i}!
|
|
5
5
|
`)})};var Zt=c(require("chokidar"));var Xt=(e,t,r)=>{for(let n of e)G({inputDir:t,outputDir:r,fileName:n,mode:"development"})},er=async e=>{let{inputDir:t,outputDir:r,watchFilePaths:n}=e,s=(await O()).getFunctionFiles();Xt(s,t,r);let i,a=[t,...n],p=Zt.default.watch(a);console.info(`watch: ${a.join(", ")}`),p.on("change",m=>{clearTimeout(i),i=setTimeout(()=>{console.info(`${m} has change`),Xt(s,t,r)},1e3)})};var Hr=(0,rr.default)("cli:tunnel"),Wr=async()=>{let e=await Gt(),t=process.env.RUNTIME_IMAGE||e.image||"docker-hub.gitee.work/gitee-prod/proxima-apps-runtime-server:4.4.0-alpha-86",r=Number(process.env.CONTAINER_PORT)||8455,n=process.env.CONTAINER_NAME||"cli-dev-apps-runtime-server",o=process.env.PULL_IMAGE!=="false",s={PORT:r,APPS_DIR:"/usr/src/app/apps",LRU_TTL:1,FS_MODE:"local",DEBUG:process.env.DEBUG||"runtime*",LOG_LEVEL:process.env.LOG_LEVEL,APPS_DEFAULT_LOCALE:process.env.APPS_DEFAULT_LOCALE||"zh-CN",PARSE_PUBLIC_SERVER_URL:process.env.PARSE_PUBLIC_SERVER_URL||e.parsePublicServerUrl,PARSE_SERVER_MASTER_KEY:process.env.PARSE_SERVER_MASTER_KEY||e.parseServerMasterKey,PROXIMA_CORE_URL:process.env.PROXIMA_CORE_URL||e.proximaCoreUrl,APPS_SERVER_URL:process.env.APPS_SERVER_URL||e.appsServerUrl,APPS_PARSE_SERVER_URL:process.env.APPS_PARSE_SERVER_URL||e.appsParseServerUrl,PGSQL_CLIENT_HOST:process.env.PGSQL_CLIENT_HOST,PGSQL_CLIENT_PORT:process.env.PGSQL_CLIENT_PORT,PGSQL_CLIENT_USER:process.env.PGSQL_CLIENT_USER,PGSQL_CLIENT_PASSWORD:process.env.PGSQL_CLIENT_PASSWORD,APPS_RUNTIME_IOREDIS_OPTIONS:process.env.APPS_RUNTIME_IOREDIS_OPTIONS?`'${process.env.APPS_RUNTIME_IOREDIS_OPTIONS}'`:void 0};return{runtimeImage:t,containerPort:r,containerName:n,containerEnv:s,pullImage:o}},nr=async e=>{var Ce,Re,Ae;let{inputDir:t,outputDir:r,server:n,filesToCopy:o,resourcePort:s,ui:i,rspackConfig:a,noProxy:p,watchFilePaths:m=[]}=e;await Ee.default.emptyDir(r);let l=await O(),u=l.app.key,g=l.app.version,v=l.getFunctionFiles(),D=l.resources||[];if(!u){f.error("appKey is required!");return}if(!g){f.error("version is required!");return}console.info(`App key: ${se.default.bold.green(u)}`),console.info(`App version: ${se.default.bold.green(g)}
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
# {{appName}} 插件开发 Skill
|
|
2
2
|
|
|
3
|
-
这个 Skill 面向插件研发与 AI 代理协作,目标是让 Agent
|
|
3
|
+
这个 Skill 面向插件研发与 AI 代理协作,目标是让 Agent 在当前插件仓库内稳定完成:
|
|
4
4
|
|
|
5
5
|
- 功能实现(前端模块、trigger、webtrigger)
|
|
6
6
|
- API 适配(`/forge/` 到运行时路径的转换)
|
|
7
7
|
- 打包、发布与订阅(CLI 一体化)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 文档导航
|
|
10
10
|
|
|
11
|
-
- `skill/guides/01-overview.md
|
|
12
|
-
- `skill/guides/02-extension-points.md
|
|
13
|
-
- `skill/guides/03-api-routing-rules.md`:`/forge/`
|
|
14
|
-
- `skill/guides/04-deploy-and-subscribe.md`:CLI
|
|
15
|
-
- `skill/guides/05-agent-prompt-template.md`:给 Agent
|
|
16
|
-
- `skill/
|
|
11
|
+
- `skill/guides/01-overview.md`:开发总览、交付标准与执行节奏
|
|
12
|
+
- `skill/guides/02-extension-points.md`:manifest 扩展点与声明约束
|
|
13
|
+
- `skill/guides/03-api-routing-rules.md`:`/forge/` 路径转换与 `isInOne` 规则
|
|
14
|
+
- `skill/guides/04-deploy-and-subscribe.md`:CLI 登录、构建、部署、订阅
|
|
15
|
+
- `skill/guides/05-agent-prompt-template.md`:给 Agent 的标准任务模板
|
|
16
|
+
- `skill/guides/06-data-access-and-tables.md`:数据表设计与 Parse 访问规范
|
|
17
|
+
- `skill/references/README.md`:平台文档来源与使用策略
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## 推荐执行顺序
|
|
19
20
|
|
|
20
|
-
1.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
21
|
+
1. 阅读 `01-overview` 和 `02-extension-points`,明确能力边界和 manifest 变更。
|
|
22
|
+
2. 查接口前先看 `references/README.md`,仅使用指定来源检索原始路径。
|
|
23
|
+
3. 按 `03-api-routing-rules` 完成前后端路径转换与 API 封装。
|
|
24
|
+
4. 涉及数据表时,执行 `06-data-access-and-tables` 中的建模与评审清单。
|
|
25
|
+
5. 需要发布验证时,执行 `04-deploy-and-subscribe`。
|
|
26
|
+
6. 让 Agent 批量产出代码时,使用 `05-agent-prompt-template`。
|
|
24
27
|
|
|
25
|
-
##
|
|
28
|
+
## 文档来源约束
|
|
26
29
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
30
|
+
- 接口事实来源:`https://team.gitee.cn/llms-full-team-api.txt`
|
|
31
|
+
- 插件开发规范来源:`https://team.gitee.cn/llms-full-developer.txt`
|
|
32
|
+
- 数据表结构来源:`https://team.gitee.cn/llms-full-table-schema.txt`
|
|
33
|
+
- 禁止用其它页面(包括历史笔记、二手转述)替代以上三类事实来源
|
|
29
34
|
|
|
30
35
|
## 项目标识
|
|
31
36
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
在保持可运行最小骨架的前提下,快速迭代插件能力,并保证以下交付完整:
|
|
6
6
|
|
|
7
|
-
- `manifest.yml`
|
|
7
|
+
- `manifest.yml` 能力声明完整且可校验
|
|
8
8
|
- 前端入口与路由可运行
|
|
9
9
|
- 后端 trigger/webtrigger 代码可调用
|
|
10
10
|
- 打包产物可通过 CLI 上传发布并订阅到目标租户
|
|
@@ -15,13 +15,29 @@
|
|
|
15
15
|
- 后端:`trigger/*.ts` 与 `functions/*.ts` 按 manifest 声明
|
|
16
16
|
- 构建:`giteeteam-apps` + `rspack`(通过 `rspack.config.js` 扩展)
|
|
17
17
|
|
|
18
|
+
## 标准执行流程
|
|
19
|
+
|
|
20
|
+
1. 明确本次模块范围:`itemPanel/workspacePage/adminPage/webtrigger/trigger`。
|
|
21
|
+
2. 核对 manifest 变更:模块声明、resolver/function 映射、资源声明。
|
|
22
|
+
3. 检索接口原始路径:按 `SKILL.md` 的文档来源约束执行(优先 `https://team.gitee.cn/llms-full-team-api.txt`)。
|
|
23
|
+
4. 进行路径转换:前后端分别按 `03-api-routing-rules` 落地。
|
|
24
|
+
5. 若涉及数据表:按 `06-data-access-and-tables` 完成建模与访问封装。
|
|
25
|
+
6. 构建并发布验证:按 `04-deploy-and-subscribe` 执行 CLI 流程。
|
|
26
|
+
|
|
18
27
|
## 接口检索流程(强制)
|
|
19
28
|
|
|
20
|
-
1.
|
|
29
|
+
1. 文档来源约束遵循 `SKILL.md` 的“文档来源约束”。
|
|
21
30
|
2. 先定位“请求链接”原文,再提取 `/forge/...` 原始路径。
|
|
22
|
-
3.
|
|
31
|
+
3. 前端接口使用 `isInOne` 做环境分支;后端接口不使用该分支。
|
|
23
32
|
4. 提交说明写清楚“原始路径”和“转换后路径”。
|
|
24
33
|
|
|
34
|
+
## 完成标准(DoD)
|
|
35
|
+
|
|
36
|
+
1. manifest、前端、后端三者声明一致。
|
|
37
|
+
2. 新增接口已集中到 API 层,不在页面里散落拼接路径。
|
|
38
|
+
3. 新增数据结构有字段语义与演进说明(如涉及表)。
|
|
39
|
+
4. 发布命令可执行,订阅命令可执行。
|
|
40
|
+
|
|
25
41
|
## 目录建议
|
|
26
42
|
|
|
27
43
|
- `manifest.yml`:插件能力声明
|
|
@@ -11,10 +11,21 @@
|
|
|
11
11
|
- `trigger`
|
|
12
12
|
- `function`
|
|
13
13
|
- `resources`
|
|
14
|
+
- `tables`
|
|
14
15
|
|
|
15
16
|
## 实现要求
|
|
16
17
|
|
|
17
18
|
1. 每新增一个可执行逻辑,都要在 `modules.function` 声明 handler。
|
|
18
19
|
2. 每新增一个前端页面模块,都要在 `resources` 中声明静态资源路径。
|
|
19
|
-
3. `
|
|
20
|
-
4.
|
|
20
|
+
3. 需要前端调用后端函数时,在页面模块(如 `itemPanel`)配置 `resolver.function`,并在函数中使用 `Resolver` 定义 method。
|
|
21
|
+
4. 需要插件表时,先在 `manifest.tables` 定义结构,再在函数中通过 `getParseQuery/getParseObject` 访问。
|
|
22
|
+
5. `webtrigger` 与 `trigger` 的 `key/function` 必须一一对应。
|
|
23
|
+
6. 修改模块定义后,优先执行构建并检查 manifest 校验报错。
|
|
24
|
+
7. 对已发布应用,禁止直接修改已有字段类型;使用新增字段 + 数据迁移方式演进。
|
|
25
|
+
|
|
26
|
+
## 常见自检
|
|
27
|
+
|
|
28
|
+
1. 页面模块是否有对应资源声明。
|
|
29
|
+
2. resolver 的 function key 与 `modules.function` 是否一致。
|
|
30
|
+
3. trigger/webtrigger 的入参与返回结构是否和调用方约定一致。
|
|
31
|
+
4. 表结构是否包含查询高频字段的索引。
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# /forge/ 接口转换规则
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
接口来源遵循 `SKILL.md` 的“文档来源约束”。`/forge/` 原始路径应从 `https://team.gitee.cn/llms-full-team-api.txt` 检索,路径按调用场景转换。
|
|
4
|
+
|
|
5
|
+
## 规则总览
|
|
6
|
+
|
|
7
|
+
1. 文档原始路径:`/forge/xxx`
|
|
8
|
+
2. 前端默认路径:`/api/team/parse/xxx`
|
|
9
|
+
3. 后端默认路径:`/parse/xxx`
|
|
10
|
+
4. 前端允许按 `isInOne` 做环境分支;后端不做该分支。
|
|
4
11
|
|
|
5
12
|
## 前端调用(浏览器)
|
|
6
13
|
|
|
@@ -8,7 +15,7 @@
|
|
|
8
15
|
|
|
9
16
|
- `/forge/xxx`
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
前端实际请求改为:
|
|
12
19
|
|
|
13
20
|
- `/api/team/parse/xxx`
|
|
14
21
|
|
|
@@ -20,20 +27,20 @@
|
|
|
20
27
|
|
|
21
28
|
- 集成环境(`isInOne === true`)优先:`/parse/xxx`
|
|
22
29
|
- 开发环境(`isInOne === false`)优先:`/api/team/parse/xxx`
|
|
23
|
-
-
|
|
30
|
+
- 为兼容网关差异,可保留另一条路径作为回退
|
|
24
31
|
|
|
25
32
|
## Trigger 调用(服务端)
|
|
26
33
|
|
|
27
|
-
当在 trigger/webtrigger
|
|
34
|
+
当在 trigger/webtrigger 中调用同类接口时:
|
|
28
35
|
|
|
29
|
-
- `/parse/xxx
|
|
36
|
+
- 使用 `/parse/xxx`,或走项目内 parse client 对应能力
|
|
30
37
|
- 后端不使用 `isInOne` 做路径分支
|
|
31
38
|
|
|
32
39
|
示例:
|
|
33
40
|
|
|
34
41
|
- 文档 `/forge/api/search` => 后端 `/parse/api/search`
|
|
35
42
|
|
|
36
|
-
## isInOne
|
|
43
|
+
## `isInOne` 判断示例
|
|
37
44
|
|
|
38
45
|
```ts
|
|
39
46
|
export const isInOne = (gateway: string): boolean => {
|
|
@@ -47,14 +54,8 @@ export const isInOne = (gateway: string): boolean => {
|
|
|
47
54
|
};
|
|
48
55
|
```
|
|
49
56
|
|
|
50
|
-
示例输入:
|
|
51
|
-
|
|
52
|
-
- `https://osc.gitee.work/api/team` => `true`(集成环境)
|
|
53
|
-
- `https://osc.gitee.work` => `false`(开发环境)
|
|
54
|
-
- `''` 或非法 URL => `false`
|
|
55
|
-
|
|
56
57
|
## 统一约束
|
|
57
58
|
|
|
58
59
|
1. 不要在前端直接请求裸 `/forge/*`。
|
|
59
60
|
2. 抽一层 API client(例如 `src/services/api.ts`)统一做路径转换。
|
|
60
|
-
3.
|
|
61
|
+
3. 在提交说明中标注“原始路径 -> 实际路径”的映射关系。
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
# 插件发布与订阅(CLI)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
以下流程通过 `giteeteam-apps` 完成登录、构建、部署和租户订阅。
|
|
4
4
|
|
|
5
|
-
## 0)
|
|
5
|
+
## 0) 登录(必须)
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
giteeteam-apps login --domain http://<host> -u <username> -p <password>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## 1)
|
|
11
|
+
## 1) 构建产物
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
giteeteam-apps build --prod
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## 2)
|
|
17
|
+
## 2) 部署到目标环境
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
giteeteam-apps deploy <zip-file> -e production
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
常见环境值:`development`、`staging`、`production`(默认 `development`)。
|
|
24
|
+
|
|
23
25
|
## 3) 订阅到租户
|
|
24
26
|
|
|
25
|
-
方式 A
|
|
27
|
+
方式 A:独立订阅
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
giteeteam-apps subscribe <appKey> <versionKey> -t osc -e production
|
|
@@ -34,10 +36,15 @@ giteeteam-apps subscribe <appKey> <versionKey> -t osc -e production
|
|
|
34
36
|
giteeteam-apps deploy <zip-file> -e production --subscribe osc --app-key <appKey> --version-key <versionKey>
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
## 参数说明
|
|
38
40
|
|
|
39
41
|
1. `<zip-file>` 使用 `build` 产出的压缩包路径。
|
|
40
|
-
2. `-
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
|
|
42
|
+
2. `-t/--tenants` 与 `--subscribe` 支持逗号分隔,例如 `osc,test`。
|
|
43
|
+
3. 若 zip 命名为 `<appKey>_<versionKey>.zip`,`deploy --subscribe` 可省略 `--app-key/--version-key`。
|
|
44
|
+
4. 发布订阅流程不需要再手动调用 `curl`。
|
|
45
|
+
|
|
46
|
+
## 故障排查
|
|
47
|
+
|
|
48
|
+
1. `503` 或 `socket hang up`:优先检查环境服务状态,再重试 deploy。
|
|
49
|
+
2. `Please login in first!`:先执行 `giteeteam-apps login`。
|
|
50
|
+
3. 订阅失败且提示缺少 app/version:补充 `--app-key` 与 `--version-key`。
|
|
@@ -11,12 +11,19 @@
|
|
|
11
11
|
3. <功能 3>
|
|
12
12
|
|
|
13
13
|
约束:
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- 前端接口遵循 /forge/ 路径转换规则
|
|
14
|
+
- 接口检索仅使用 `https://team.gitee.cn/llms-full-team-api.txt`
|
|
15
|
+
- 插件开发规范仅使用 `https://team.gitee.cn/llms-full-developer.txt`
|
|
16
|
+
- 数据表结构仅使用 `https://team.gitee.cn/llms-full-table-schema.txt`
|
|
17
|
+
- 前端接口使用 `isInOne` 环境判断并执行 `/forge/` 转换;后端不使用该判断
|
|
19
18
|
- 路径映射必须满足:前端 `/forge/xxx` => `/api/team/parse/xxx`;后端 `/forge/xxx` => `/parse/xxx`
|
|
19
|
+
- 涉及数据表访问时:`getParseQuery(false, 'Item')` 查核心表,`getParseQuery(true, 'xxx')` 查插件表
|
|
20
|
+
- 默认采用 `manifest.tables + getParseQuery/getParseObject`;`storage.entities` 仅用于 Storage API
|
|
21
|
+
- 已发布应用禁止直接修改已有字段类型;只能新增字段并给出迁移方案
|
|
22
|
+
- `manifest.yml` 定义完整且可校验
|
|
20
23
|
- trigger 与 webtrigger 的 function 声明完整
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
输出要求:
|
|
26
|
+
- 列出修改文件
|
|
27
|
+
- 列出“原始接口路径 -> 实际调用路径”映射
|
|
28
|
+
- 列出验证步骤与结果
|
|
22
29
|
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 数据访问与表设计
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
在插件中实现“核心表 + 插件表”的统一访问方式,并支持资源负载等场景。
|
|
6
|
+
|
|
7
|
+
## 文档来源
|
|
8
|
+
|
|
9
|
+
1. 表结构与字段定义:`https://team.gitee.cn/llms-full-table-schema.txt`。
|
|
10
|
+
2. 插件开发规范与能力边界:`https://team.gitee.cn/llms-full-developer.txt`。
|
|
11
|
+
3. 接口路径与调用约定:`https://team.gitee.cn/llms-full-team-api.txt`。
|
|
12
|
+
|
|
13
|
+
## 核心规则
|
|
14
|
+
|
|
15
|
+
1. `getParseQuery(false, 'Item')` / `getParseObject(false, 'Item')`:访问 Team 核心表。
|
|
16
|
+
2. `getParseQuery(true, 'Resource')` / `getParseObject(true, 'Resource')`:访问插件自有表。
|
|
17
|
+
3. 默认采用 `manifest.tables + getParseQuery/getParseObject`。
|
|
18
|
+
4. `manifest.storage.entities` 仅在使用 Storage API(`storage.entity(...)`)时启用,不与 `getParseQuery` 混用。
|
|
19
|
+
|
|
20
|
+
## 建表前置设计(高优先级)
|
|
21
|
+
|
|
22
|
+
1. 建表前先定义字段语义、关联关系、索引、扩展方向。
|
|
23
|
+
2. 应用发布后,不直接修改已有字段 `type`。
|
|
24
|
+
3. 字段演进采用“新增字段 + 数据迁移 + 切换读写”。
|
|
25
|
+
4. 变更说明必须包含迁移方案、回滚方案和影响范围。
|
|
26
|
+
|
|
27
|
+
## 表定义建议(资源负载场景)
|
|
28
|
+
|
|
29
|
+
- `Resource`
|
|
30
|
+
- 字段:`name`(String), `type`(String: human|hardware), `workspaceKey`(String), `description`(String)
|
|
31
|
+
- 索引:`type`、`workspaceKey`
|
|
32
|
+
|
|
33
|
+
- `ResourceBooking`
|
|
34
|
+
- 字段:`resource`(Pointer->Resource), `itemId`(String), `workspaceKey`(String), `startDate`(Date), `endDate`(Date), `initiatedBy`(Pointer->_User)
|
|
35
|
+
- 索引:`resource,startDate,endDate`、`itemId,workspaceKey`
|
|
36
|
+
|
|
37
|
+
## 推荐实现方式
|
|
38
|
+
|
|
39
|
+
1. 前端使用 `invoke` 调 resolver method,不在前端堆叠 Parse 细节。
|
|
40
|
+
2. resolver 中统一封装 CRUD(如 `getParseQuery/getParseObject/saveAllObject/deleteParseObject`)。
|
|
41
|
+
3. ItemPanel 默认以 `itemId/workspaceKey` 作为数据作用域。
|
|
42
|
+
4. 先确定一套数据访问模型并长期保持一致。
|
|
43
|
+
|
|
44
|
+
## 提交前评审清单
|
|
45
|
+
|
|
46
|
+
1. 字段命名、类型、必填、默认值是否匹配业务语义。
|
|
47
|
+
2. 关联字段是否使用正确的 `Pointer/Relation` 与 `targetClass`。
|
|
48
|
+
3. 高频查询条件是否有对应索引。
|
|
49
|
+
4. 数据隔离维度是否明确(如 `itemId/workspaceKey/tenant`)。
|
|
50
|
+
5. 前后端读写路径是否统一且可追踪。
|
|
51
|
+
6. 演进方案是否可通过“新增字段 + 迁移”落地。
|
|
52
|
+
|
|
53
|
+
## 常见问题
|
|
54
|
+
|
|
55
|
+
1. 只声明 `modules.function`,但页面模块未配置 `resolver`,导致 `invoke` 无法命中。
|
|
56
|
+
2. 未声明 `manifest.tables` 就访问插件表,导致运行期风险。
|
|
57
|
+
3. 发布时 `deploy` 未传 zip 文件名导致命令失败。
|
|
58
|
+
4. 部署返回 `503` 或 `socket hang up` 时,优先排查环境状态,不要先改表结构。
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 参考资料
|
|
2
2
|
|
|
3
|
-
- `llms-full.txt
|
|
4
|
-
-
|
|
5
|
-
-
|
|
3
|
+
- `https://team.gitee.cn/llms-full-developer.txt`:插件开发手册
|
|
4
|
+
- `https://team.gitee.cn/llms-full-team-api.txt`:接口文档
|
|
5
|
+
- `https://team.gitee.cn/llms-full-table-schema.txt`:数据表 schema 文档
|
|
6
|
+
|
|
7
|
+
## 使用策略
|
|
8
|
+
|
|
9
|
+
1. 接口路径、参数、返回结构,仅以 `https://team.gitee.cn/llms-full-team-api.txt` 为准。
|
|
10
|
+
2. manifest、模块扩展点、开发规范,仅以 `https://team.gitee.cn/llms-full-developer.txt` 为准。
|
|
11
|
+
3. 表结构、字段定义、索引建议,仅以 `https://team.gitee.cn/llms-full-table-schema.txt` 为准。
|
|
12
|
+
4. 先在对应子文档中搜索原文,再落地实现;不要混用二手资料。
|
package/package.json
CHANGED
package/templates/skill/SKILL.md
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
# {{appName}} 插件开发 Skill
|
|
2
2
|
|
|
3
|
-
这个 Skill 面向插件研发与 AI 代理协作,目标是让 Agent
|
|
3
|
+
这个 Skill 面向插件研发与 AI 代理协作,目标是让 Agent 在当前插件仓库内稳定完成:
|
|
4
4
|
|
|
5
5
|
- 功能实现(前端模块、trigger、webtrigger)
|
|
6
6
|
- API 适配(`/forge/` 到运行时路径的转换)
|
|
7
7
|
- 打包、发布与订阅(CLI 一体化)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 文档导航
|
|
10
10
|
|
|
11
|
-
- `skill/guides/01-overview.md
|
|
12
|
-
- `skill/guides/02-extension-points.md
|
|
13
|
-
- `skill/guides/03-api-routing-rules.md`:`/forge/`
|
|
14
|
-
- `skill/guides/04-deploy-and-subscribe.md`:CLI
|
|
15
|
-
- `skill/guides/05-agent-prompt-template.md`:给 Agent
|
|
16
|
-
- `skill/
|
|
11
|
+
- `skill/guides/01-overview.md`:开发总览、交付标准与执行节奏
|
|
12
|
+
- `skill/guides/02-extension-points.md`:manifest 扩展点与声明约束
|
|
13
|
+
- `skill/guides/03-api-routing-rules.md`:`/forge/` 路径转换与 `isInOne` 规则
|
|
14
|
+
- `skill/guides/04-deploy-and-subscribe.md`:CLI 登录、构建、部署、订阅
|
|
15
|
+
- `skill/guides/05-agent-prompt-template.md`:给 Agent 的标准任务模板
|
|
16
|
+
- `skill/guides/06-data-access-and-tables.md`:数据表设计与 Parse 访问规范
|
|
17
|
+
- `skill/references/README.md`:平台文档来源与使用策略
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## 推荐执行顺序
|
|
19
20
|
|
|
20
|
-
1.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
21
|
+
1. 阅读 `01-overview` 和 `02-extension-points`,明确能力边界和 manifest 变更。
|
|
22
|
+
2. 查接口前先看 `references/README.md`,仅使用指定来源检索原始路径。
|
|
23
|
+
3. 按 `03-api-routing-rules` 完成前后端路径转换与 API 封装。
|
|
24
|
+
4. 涉及数据表时,执行 `06-data-access-and-tables` 中的建模与评审清单。
|
|
25
|
+
5. 需要发布验证时,执行 `04-deploy-and-subscribe`。
|
|
26
|
+
6. 让 Agent 批量产出代码时,使用 `05-agent-prompt-template`。
|
|
24
27
|
|
|
25
|
-
##
|
|
28
|
+
## 文档来源约束
|
|
26
29
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
30
|
+
- 接口事实来源:`https://team.gitee.cn/llms-full-team-api.txt`
|
|
31
|
+
- 插件开发规范来源:`https://team.gitee.cn/llms-full-developer.txt`
|
|
32
|
+
- 数据表结构来源:`https://team.gitee.cn/llms-full-table-schema.txt`
|
|
33
|
+
- 禁止用其它页面(包括历史笔记、二手转述)替代以上三类事实来源
|
|
29
34
|
|
|
30
35
|
## 项目标识
|
|
31
36
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
在保持可运行最小骨架的前提下,快速迭代插件能力,并保证以下交付完整:
|
|
6
6
|
|
|
7
|
-
- `manifest.yml`
|
|
7
|
+
- `manifest.yml` 能力声明完整且可校验
|
|
8
8
|
- 前端入口与路由可运行
|
|
9
9
|
- 后端 trigger/webtrigger 代码可调用
|
|
10
10
|
- 打包产物可通过 CLI 上传发布并订阅到目标租户
|
|
@@ -15,13 +15,29 @@
|
|
|
15
15
|
- 后端:`trigger/*.ts` 与 `functions/*.ts` 按 manifest 声明
|
|
16
16
|
- 构建:`giteeteam-apps` + `rspack`(通过 `rspack.config.js` 扩展)
|
|
17
17
|
|
|
18
|
+
## 标准执行流程
|
|
19
|
+
|
|
20
|
+
1. 明确本次模块范围:`itemPanel/workspacePage/adminPage/webtrigger/trigger`。
|
|
21
|
+
2. 核对 manifest 变更:模块声明、resolver/function 映射、资源声明。
|
|
22
|
+
3. 检索接口原始路径:按 `SKILL.md` 的文档来源约束执行(优先 `https://team.gitee.cn/llms-full-team-api.txt`)。
|
|
23
|
+
4. 进行路径转换:前后端分别按 `03-api-routing-rules` 落地。
|
|
24
|
+
5. 若涉及数据表:按 `06-data-access-and-tables` 完成建模与访问封装。
|
|
25
|
+
6. 构建并发布验证:按 `04-deploy-and-subscribe` 执行 CLI 流程。
|
|
26
|
+
|
|
18
27
|
## 接口检索流程(强制)
|
|
19
28
|
|
|
20
|
-
1.
|
|
29
|
+
1. 文档来源约束遵循 `SKILL.md` 的“文档来源约束”。
|
|
21
30
|
2. 先定位“请求链接”原文,再提取 `/forge/...` 原始路径。
|
|
22
|
-
3.
|
|
31
|
+
3. 前端接口使用 `isInOne` 做环境分支;后端接口不使用该分支。
|
|
23
32
|
4. 提交说明写清楚“原始路径”和“转换后路径”。
|
|
24
33
|
|
|
34
|
+
## 完成标准(DoD)
|
|
35
|
+
|
|
36
|
+
1. manifest、前端、后端三者声明一致。
|
|
37
|
+
2. 新增接口已集中到 API 层,不在页面里散落拼接路径。
|
|
38
|
+
3. 新增数据结构有字段语义与演进说明(如涉及表)。
|
|
39
|
+
4. 发布命令可执行,订阅命令可执行。
|
|
40
|
+
|
|
25
41
|
## 目录建议
|
|
26
42
|
|
|
27
43
|
- `manifest.yml`:插件能力声明
|
|
@@ -11,10 +11,21 @@
|
|
|
11
11
|
- `trigger`
|
|
12
12
|
- `function`
|
|
13
13
|
- `resources`
|
|
14
|
+
- `tables`
|
|
14
15
|
|
|
15
16
|
## 实现要求
|
|
16
17
|
|
|
17
18
|
1. 每新增一个可执行逻辑,都要在 `modules.function` 声明 handler。
|
|
18
19
|
2. 每新增一个前端页面模块,都要在 `resources` 中声明静态资源路径。
|
|
19
|
-
3. `
|
|
20
|
-
4.
|
|
20
|
+
3. 需要前端调用后端函数时,在页面模块(如 `itemPanel`)配置 `resolver.function`,并在函数中使用 `Resolver` 定义 method。
|
|
21
|
+
4. 需要插件表时,先在 `manifest.tables` 定义结构,再在函数中通过 `getParseQuery/getParseObject` 访问。
|
|
22
|
+
5. `webtrigger` 与 `trigger` 的 `key/function` 必须一一对应。
|
|
23
|
+
6. 修改模块定义后,优先执行构建并检查 manifest 校验报错。
|
|
24
|
+
7. 对已发布应用,禁止直接修改已有字段类型;使用新增字段 + 数据迁移方式演进。
|
|
25
|
+
|
|
26
|
+
## 常见自检
|
|
27
|
+
|
|
28
|
+
1. 页面模块是否有对应资源声明。
|
|
29
|
+
2. resolver 的 function key 与 `modules.function` 是否一致。
|
|
30
|
+
3. trigger/webtrigger 的入参与返回结构是否和调用方约定一致。
|
|
31
|
+
4. 表结构是否包含查询高频字段的索引。
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# /forge/ 接口转换规则
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
接口来源遵循 `SKILL.md` 的“文档来源约束”。`/forge/` 原始路径应从 `https://team.gitee.cn/llms-full-team-api.txt` 检索,路径按调用场景转换。
|
|
4
|
+
|
|
5
|
+
## 规则总览
|
|
6
|
+
|
|
7
|
+
1. 文档原始路径:`/forge/xxx`
|
|
8
|
+
2. 前端默认路径:`/api/team/parse/xxx`
|
|
9
|
+
3. 后端默认路径:`/parse/xxx`
|
|
10
|
+
4. 前端允许按 `isInOne` 做环境分支;后端不做该分支。
|
|
4
11
|
|
|
5
12
|
## 前端调用(浏览器)
|
|
6
13
|
|
|
@@ -8,7 +15,7 @@
|
|
|
8
15
|
|
|
9
16
|
- `/forge/xxx`
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
前端实际请求改为:
|
|
12
19
|
|
|
13
20
|
- `/api/team/parse/xxx`
|
|
14
21
|
|
|
@@ -20,20 +27,20 @@
|
|
|
20
27
|
|
|
21
28
|
- 集成环境(`isInOne === true`)优先:`/parse/xxx`
|
|
22
29
|
- 开发环境(`isInOne === false`)优先:`/api/team/parse/xxx`
|
|
23
|
-
-
|
|
30
|
+
- 为兼容网关差异,可保留另一条路径作为回退
|
|
24
31
|
|
|
25
32
|
## Trigger 调用(服务端)
|
|
26
33
|
|
|
27
|
-
当在 trigger/webtrigger
|
|
34
|
+
当在 trigger/webtrigger 中调用同类接口时:
|
|
28
35
|
|
|
29
|
-
- `/parse/xxx
|
|
36
|
+
- 使用 `/parse/xxx`,或走项目内 parse client 对应能力
|
|
30
37
|
- 后端不使用 `isInOne` 做路径分支
|
|
31
38
|
|
|
32
39
|
示例:
|
|
33
40
|
|
|
34
41
|
- 文档 `/forge/api/search` => 后端 `/parse/api/search`
|
|
35
42
|
|
|
36
|
-
## isInOne
|
|
43
|
+
## `isInOne` 判断示例
|
|
37
44
|
|
|
38
45
|
```ts
|
|
39
46
|
export const isInOne = (gateway: string): boolean => {
|
|
@@ -47,14 +54,8 @@ export const isInOne = (gateway: string): boolean => {
|
|
|
47
54
|
};
|
|
48
55
|
```
|
|
49
56
|
|
|
50
|
-
示例输入:
|
|
51
|
-
|
|
52
|
-
- `https://osc.gitee.work/api/team` => `true`(集成环境)
|
|
53
|
-
- `https://osc.gitee.work` => `false`(开发环境)
|
|
54
|
-
- `''` 或非法 URL => `false`
|
|
55
|
-
|
|
56
57
|
## 统一约束
|
|
57
58
|
|
|
58
59
|
1. 不要在前端直接请求裸 `/forge/*`。
|
|
59
60
|
2. 抽一层 API client(例如 `src/services/api.ts`)统一做路径转换。
|
|
60
|
-
3.
|
|
61
|
+
3. 在提交说明中标注“原始路径 -> 实际路径”的映射关系。
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
# 插件发布与订阅(CLI)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
以下流程通过 `giteeteam-apps` 完成登录、构建、部署和租户订阅。
|
|
4
4
|
|
|
5
|
-
## 0)
|
|
5
|
+
## 0) 登录(必须)
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
giteeteam-apps login --domain http://<host> -u <username> -p <password>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## 1)
|
|
11
|
+
## 1) 构建产物
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
giteeteam-apps build --prod
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## 2)
|
|
17
|
+
## 2) 部署到目标环境
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
giteeteam-apps deploy <zip-file> -e production
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
常见环境值:`development`、`staging`、`production`(默认 `development`)。
|
|
24
|
+
|
|
23
25
|
## 3) 订阅到租户
|
|
24
26
|
|
|
25
|
-
方式 A
|
|
27
|
+
方式 A:独立订阅
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
giteeteam-apps subscribe <appKey> <versionKey> -t osc -e production
|
|
@@ -34,10 +36,15 @@ giteeteam-apps subscribe <appKey> <versionKey> -t osc -e production
|
|
|
34
36
|
giteeteam-apps deploy <zip-file> -e production --subscribe osc --app-key <appKey> --version-key <versionKey>
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
## 参数说明
|
|
38
40
|
|
|
39
41
|
1. `<zip-file>` 使用 `build` 产出的压缩包路径。
|
|
40
|
-
2. `-
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
|
|
42
|
+
2. `-t/--tenants` 与 `--subscribe` 支持逗号分隔,例如 `osc,test`。
|
|
43
|
+
3. 若 zip 命名为 `<appKey>_<versionKey>.zip`,`deploy --subscribe` 可省略 `--app-key/--version-key`。
|
|
44
|
+
4. 发布订阅流程不需要再手动调用 `curl`。
|
|
45
|
+
|
|
46
|
+
## 故障排查
|
|
47
|
+
|
|
48
|
+
1. `503` 或 `socket hang up`:优先检查环境服务状态,再重试 deploy。
|
|
49
|
+
2. `Please login in first!`:先执行 `giteeteam-apps login`。
|
|
50
|
+
3. 订阅失败且提示缺少 app/version:补充 `--app-key` 与 `--version-key`。
|
|
@@ -11,12 +11,19 @@
|
|
|
11
11
|
3. <功能 3>
|
|
12
12
|
|
|
13
13
|
约束:
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- 前端接口遵循 /forge/ 路径转换规则
|
|
14
|
+
- 接口检索仅使用 `https://team.gitee.cn/llms-full-team-api.txt`
|
|
15
|
+
- 插件开发规范仅使用 `https://team.gitee.cn/llms-full-developer.txt`
|
|
16
|
+
- 数据表结构仅使用 `https://team.gitee.cn/llms-full-table-schema.txt`
|
|
17
|
+
- 前端接口使用 `isInOne` 环境判断并执行 `/forge/` 转换;后端不使用该判断
|
|
19
18
|
- 路径映射必须满足:前端 `/forge/xxx` => `/api/team/parse/xxx`;后端 `/forge/xxx` => `/parse/xxx`
|
|
19
|
+
- 涉及数据表访问时:`getParseQuery(false, 'Item')` 查核心表,`getParseQuery(true, 'xxx')` 查插件表
|
|
20
|
+
- 默认采用 `manifest.tables + getParseQuery/getParseObject`;`storage.entities` 仅用于 Storage API
|
|
21
|
+
- 已发布应用禁止直接修改已有字段类型;只能新增字段并给出迁移方案
|
|
22
|
+
- `manifest.yml` 定义完整且可校验
|
|
20
23
|
- trigger 与 webtrigger 的 function 声明完整
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
输出要求:
|
|
26
|
+
- 列出修改文件
|
|
27
|
+
- 列出“原始接口路径 -> 实际调用路径”映射
|
|
28
|
+
- 列出验证步骤与结果
|
|
22
29
|
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 数据访问与表设计
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
在插件中实现“核心表 + 插件表”的统一访问方式,并支持资源负载等场景。
|
|
6
|
+
|
|
7
|
+
## 文档来源
|
|
8
|
+
|
|
9
|
+
1. 表结构与字段定义:`https://team.gitee.cn/llms-full-table-schema.txt`。
|
|
10
|
+
2. 插件开发规范与能力边界:`https://team.gitee.cn/llms-full-developer.txt`。
|
|
11
|
+
3. 接口路径与调用约定:`https://team.gitee.cn/llms-full-team-api.txt`。
|
|
12
|
+
|
|
13
|
+
## 核心规则
|
|
14
|
+
|
|
15
|
+
1. `getParseQuery(false, 'Item')` / `getParseObject(false, 'Item')`:访问 Team 核心表。
|
|
16
|
+
2. `getParseQuery(true, 'Resource')` / `getParseObject(true, 'Resource')`:访问插件自有表。
|
|
17
|
+
3. 默认采用 `manifest.tables + getParseQuery/getParseObject`。
|
|
18
|
+
4. `manifest.storage.entities` 仅在使用 Storage API(`storage.entity(...)`)时启用,不与 `getParseQuery` 混用。
|
|
19
|
+
|
|
20
|
+
## 建表前置设计(高优先级)
|
|
21
|
+
|
|
22
|
+
1. 建表前先定义字段语义、关联关系、索引、扩展方向。
|
|
23
|
+
2. 应用发布后,不直接修改已有字段 `type`。
|
|
24
|
+
3. 字段演进采用“新增字段 + 数据迁移 + 切换读写”。
|
|
25
|
+
4. 变更说明必须包含迁移方案、回滚方案和影响范围。
|
|
26
|
+
|
|
27
|
+
## 表定义建议(资源负载场景)
|
|
28
|
+
|
|
29
|
+
- `Resource`
|
|
30
|
+
- 字段:`name`(String), `type`(String: human|hardware), `workspaceKey`(String), `description`(String)
|
|
31
|
+
- 索引:`type`、`workspaceKey`
|
|
32
|
+
|
|
33
|
+
- `ResourceBooking`
|
|
34
|
+
- 字段:`resource`(Pointer->Resource), `itemId`(String), `workspaceKey`(String), `startDate`(Date), `endDate`(Date), `initiatedBy`(Pointer->_User)
|
|
35
|
+
- 索引:`resource,startDate,endDate`、`itemId,workspaceKey`
|
|
36
|
+
|
|
37
|
+
## 推荐实现方式
|
|
38
|
+
|
|
39
|
+
1. 前端使用 `invoke` 调 resolver method,不在前端堆叠 Parse 细节。
|
|
40
|
+
2. resolver 中统一封装 CRUD(如 `getParseQuery/getParseObject/saveAllObject/deleteParseObject`)。
|
|
41
|
+
3. ItemPanel 默认以 `itemId/workspaceKey` 作为数据作用域。
|
|
42
|
+
4. 先确定一套数据访问模型并长期保持一致。
|
|
43
|
+
|
|
44
|
+
## 提交前评审清单
|
|
45
|
+
|
|
46
|
+
1. 字段命名、类型、必填、默认值是否匹配业务语义。
|
|
47
|
+
2. 关联字段是否使用正确的 `Pointer/Relation` 与 `targetClass`。
|
|
48
|
+
3. 高频查询条件是否有对应索引。
|
|
49
|
+
4. 数据隔离维度是否明确(如 `itemId/workspaceKey/tenant`)。
|
|
50
|
+
5. 前后端读写路径是否统一且可追踪。
|
|
51
|
+
6. 演进方案是否可通过“新增字段 + 迁移”落地。
|
|
52
|
+
|
|
53
|
+
## 常见问题
|
|
54
|
+
|
|
55
|
+
1. 只声明 `modules.function`,但页面模块未配置 `resolver`,导致 `invoke` 无法命中。
|
|
56
|
+
2. 未声明 `manifest.tables` 就访问插件表,导致运行期风险。
|
|
57
|
+
3. 发布时 `deploy` 未传 zip 文件名导致命令失败。
|
|
58
|
+
4. 部署返回 `503` 或 `socket hang up` 时,优先排查环境状态,不要先改表结构。
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 参考资料
|
|
2
2
|
|
|
3
|
-
- `llms-full.txt
|
|
4
|
-
-
|
|
5
|
-
-
|
|
3
|
+
- `https://team.gitee.cn/llms-full-developer.txt`:插件开发手册
|
|
4
|
+
- `https://team.gitee.cn/llms-full-team-api.txt`:接口文档
|
|
5
|
+
- `https://team.gitee.cn/llms-full-table-schema.txt`:数据表 schema 文档
|
|
6
|
+
|
|
7
|
+
## 使用策略
|
|
8
|
+
|
|
9
|
+
1. 接口路径、参数、返回结构,仅以 `https://team.gitee.cn/llms-full-team-api.txt` 为准。
|
|
10
|
+
2. manifest、模块扩展点、开发规范,仅以 `https://team.gitee.cn/llms-full-developer.txt` 为准。
|
|
11
|
+
3. 表结构、字段定义、索引建议,仅以 `https://team.gitee.cn/llms-full-table-schema.txt` 为准。
|
|
12
|
+
4. 先在对应子文档中搜索原文,再落地实现;不要混用二手资料。
|