create-devoplus-one-app 0.1.0-next.181 → 0.1.0-next.185
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/LICENSE.md +11 -0
- package/README.md +2 -1
- package/dist/cli.mjs +1 -1
- package/package.json +4 -3
- package/templates/AGENTS.md.tpl +8 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Lisans / License
|
|
2
|
+
|
|
3
|
+
Telif hakkı © 2026 Devoplus. Tüm hakları saklıdır.
|
|
4
|
+
|
|
5
|
+
Bu yazılımın kullanımı, Devoplus ile yapılmış geçerli bir sözleşmeye tabidir. Sözleşmede açıkça izin verilmedikçe
|
|
6
|
+
yazılımın kopyalanması, değiştirilmesi ve dağıtılması yasaktır.
|
|
7
|
+
|
|
8
|
+
Copyright © 2026 Devoplus. All rights reserved.
|
|
9
|
+
|
|
10
|
+
Use of this software is subject to a valid agreement with Devoplus. Unless expressly permitted by that agreement,
|
|
11
|
+
copying, modifying or distributing this software is prohibited.
|
package/README.md
CHANGED
|
@@ -60,4 +60,5 @@ geliştirici belgeleri ön sürüm kanalı için https://test-one.devoplus.dev/d
|
|
|
60
60
|
## Sürümler ve lisans
|
|
61
61
|
|
|
62
62
|
Paket `@devoplus/one-sdk` ile aynı iki kanaldan yayımlanır: `next` (ön sürüm, `<sürüm>-next.<n>`) ve `latest`
|
|
63
|
-
(kararlı). Lisans:
|
|
63
|
+
(kararlı). Lisans: ticari, açık kaynak değildir. Kullanım Devoplus ile yapılmış geçerli bir sözleşmeye tabidir; koşullar
|
|
64
|
+
paketin içindeki `LICENSE.md` dosyasındadır.
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import{mkdir,readdir,stat,writeFile}from"node:fs/promises";import{basename,dirna
|
|
|
3
3
|
- ${issues.join("\n- ")}`);this.name="DataModelError";this.issues=issues}};var isRecord=v=>typeof v==="object"&&v!==null&&!Array.isArray(v);function nameIssue(name,kind){if(!DATA_NAME_RE.test(name))return`${kind} adı "${name}" geçersiz: küçük harfle başlamalı, yalnız küçük harf (a-z), rakam ve alt çizgi içermeli, en çok 40 karakter olmalı.`;if(name.includes("__")||name.endsWith("_"))return`${kind} adı "${name}" çift alt çizgi içeremez ve alt çizgiyle bitemez.`;if(SQL_KEYWORDS.has(name))return`${kind} adı "${name}" veritabanı dilinde ayrılmış bir sözcüktür; başka bir ad seçin (ör. "${name}_value").`;return null}function titleIssue(raw,where){if(!isRecord(raw))return`${where}: "title" { "tr": "…", "en": "…" } biçiminde olmalı.`;for(const lang of["tr","en"]){const v=raw[lang];if(typeof v!=="string"||v.trim()==="")return`${where}: "title.${lang}" boş olamaz (Türkçe ve İngilizce ad zorunlu).`;if(v.length>DATA_TITLE_MAX_LENGTH)return`${where}: "title.${lang}" en çok ${DATA_TITLE_MAX_LENGTH} karakter olabilir.`}for(const k of Object.keys(raw))if(k!=="tr"&&k!=="en")return`${where}: "title" içinde bilinmeyen dil "${k}" (yalnız "tr" ve "en").`;return null}var FIELD_KEYS=new Set(["type","title","required","unique","index","default","choices","maxLength","min","max"]);var ENTITY_KEYS=new Set(["title","fields","softDelete"]);var NOT_UNIQUE=new Set(["longText","boolean"]);var NOT_INDEXED=new Set(["longText"]);var NO_DEFAULT=new Set(["person","file"]);function moneyToMinor(raw){let text;if(typeof raw==="number"){if(!Number.isFinite(raw))return null;text=String(raw);if(/e/i.test(text))return null}else text=raw.trim();const m=/^(-)?(\d{1,16})(?:[.,](\d{1,2}))?$/.exec(text);if(!m)return null;const minor=Number(m[2])*100+Number((m[3]??"").padEnd(2,"0")||"0");if(!Number.isSafeInteger(minor))return null;return m[1]&&minor!==0?-minor:minor}function minorToMoney(minor){const abs=Math.abs(minor);const major=Math.floor(abs/100);const cents=String(abs%100).padStart(2,"0");return`${minor<0?"-":""}${major}.${cents}`}var DATE_RE=/^(\d{4})-(\d{2})-(\d{2})$/;var DATETIME_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/;function isCalendarDate(text){const m=DATE_RE.exec(text);if(!m)return false;const[y,mo,d]=[Number(m[1]),Number(m[2]),Number(m[3])];const date=new Date(Date.UTC(y,mo-1,d));return y>=1&&date.getUTCFullYear()===y&&date.getUTCMonth()===mo-1&&date.getUTCDate()===d}function normalizeDateTime(text){if(!DATETIME_RE.test(text)||!isCalendarDate(text.slice(0,10)))return null;const t=Date.parse(text);if(!Number.isFinite(t))return null;return new Date(t).toISOString()}function defaultIssue(field2,value2,where){const bad=expected=>`${where}: "default" ${expected} olmalı.`;switch(field2.type){case"text":case"longText":return typeof value2==="string"?null:bad("metin");case"number":return typeof value2==="number"&&Number.isFinite(value2)?null:bad("sayı");case"money":return(typeof value2==="number"||typeof value2==="string")&&moneyToMinor(value2)!==null?null:bad("en çok iki ondalık basamaklı tutar (ör. 12.50)");case"boolean":return typeof value2==="boolean"?null:bad("true ya da false");case"date":return typeof value2==="string"&&isCalendarDate(value2)?null:bad("YYYY-AA-GG biçiminde tarih");case"datetime":return typeof value2==="string"&&normalizeDateTime(value2)!==null?null:bad("bölgeli ISO tarih-saat (ör. 2026-01-31T09:00:00Z)");case"choice":return typeof value2==="string"&&field2.choices.some(c=>c.value===value2)?null:bad('seçeneklerden birinin "value" değeri');default:return`${where}: bu alan türünde varsayılan verilemez.`}}function parseDataModel(raw){const issues=[];if(!isRecord(raw))throw new DataModelError(['one.data.json bir JSON nesnesi olmalı: { "version": 1, "entities": { … } }.']);if(raw.version!==1)issues.push('"version" 1 olmalı.');for(const k of Object.keys(raw))if(k!=="version"&&k!=="entities"&&k!=="$schema")issues.push(`Bilinmeyen üst alan "${k}" (yalnız "version" ve "entities").`);if(!isRecord(raw.entities)){issues.push('"entities" bir nesne olmalı: { "<varlık adı>": { "title": …, "fields": { … } } }.');throw new DataModelError(issues)}const entityNames=Object.keys(raw.entities);if(entityNames.length>DATA_MAX_ENTITIES)issues.push(`En çok ${DATA_MAX_ENTITIES} varlık tanımlanabilir (${entityNames.length}).`);const entities=[];for(const entityName of entityNames){const where=`"${entityName}"`;const ni=nameIssue(entityName,"varlık");if(ni)issues.push(ni);else if(RESERVED_TABLE_PREFIXES.some(p=>entityName.startsWith(p)))issues.push(`varlık adı "${entityName}" platform için ayrılmış bir önekle başlıyor (${RESERVED_TABLE_PREFIXES.join(", ")}).`);const e=raw.entities[entityName];if(!isRecord(e)){issues.push(`${where}: varlık bir nesne olmalı.`);continue}for(const k of Object.keys(e))if(!ENTITY_KEYS.has(k))issues.push(`${where}: bilinmeyen alan "${k}" (title · fields · softDelete).`);const ti=titleIssue(e.title,where);if(ti)issues.push(ti);if(e.softDelete!==void 0&&typeof e.softDelete!=="boolean")issues.push(`${where}: "softDelete" true ya da false olmalı.`);if(!isRecord(e.fields)){issues.push(`${where}: "fields" bir nesne olmalı.`);continue}const fieldNames=Object.keys(e.fields);if(fieldNames.length===0)issues.push(`${where}: en az bir alan tanımlanmalı.`);if(fieldNames.length>DATA_MAX_FIELDS)issues.push(`${where}: bir varlıkta en çok ${DATA_MAX_FIELDS} alan olabilir (${fieldNames.length}).`);const fields=[];const columns=new Set(SYSTEM_COLUMNS);for(const fieldName of fieldNames){const fw=`${where}.${fieldName}`;const fni=nameIssue(fieldName,"alan");if(fni)issues.push(fni);else if(SYSTEM_COLUMNS.includes(fieldName))issues.push(`alan adı "${fieldName}" platformun otomatik kolonudur (${SYSTEM_COLUMNS.join(", ")}); başka bir ad seçin.`);else if(fieldName.endsWith("_minor"))issues.push(`alan adı "${fieldName}" "_minor" ile bitemez (para alanlarının kolon eki).`);else if(fieldName.endsWith(FOLD_COLUMN_SUFFIX))issues.push(`alan adı "${fieldName}" "${FOLD_COLUMN_SUFFIX}" ile bitemez (metin alanlarının arama kolonu eki).`);const f=e.fields[fieldName];if(!isRecord(f)){issues.push(`${fw}: alan bir nesne olmalı.`);continue}for(const k of Object.keys(f))if(!FIELD_KEYS.has(k))issues.push(`${fw}: bilinmeyen özellik "${k}".`);const type=f.type;if(typeof type!=="string"||!DATA_FIELD_TYPES.includes(type)){issues.push(`${fw}: "type" şunlardan biri olmalı: ${DATA_FIELD_TYPES.join(", ")}.`);continue}const t=type;const fti=titleIssue(f.title,fw);if(fti)issues.push(fti);for(const flag of["required","unique","index"])if(f[flag]!==void 0&&typeof f[flag]!=="boolean")issues.push(`${fw}: "${flag}" true ya da false olmalı.`);const unique=f.unique===true;if(unique&&NOT_UNIQUE.has(t))issues.push(`${fw}: "${t}" türündeki alan tekil (unique) olamaz.`);if(f.index===true&&NOT_INDEXED.has(t))issues.push(`${fw}: "${t}" türündeki alana dizin (index) eklenemez.`);let choices=[];if(t==="choice"){if(!Array.isArray(f.choices)||f.choices.length===0)issues.push(`${fw}: seçim alanında "choices" en az bir seçenek içermeli.`);else if(f.choices.length>DATA_MAX_CHOICES)issues.push(`${fw}: en çok ${DATA_MAX_CHOICES} seçenek olabilir.`);else{const seen=new Set;f.choices.forEach((c,i)=>{const cw=`${fw}.choices[${i}]`;if(!isRecord(c)||typeof c.value!=="string"||!DATA_CHOICE_VALUE_RE.test(c.value)){issues.push(`${cw}: "value" küçük harf, rakam, "_" ya da "-" içeren en çok 40 karakterlik bir kod olmalı (ör. "beklemede").`);return}if(seen.has(c.value))issues.push(`${cw}: "${c.value}" değeri tekrar ediyor.`);seen.add(c.value);const cti=titleIssue(c.title,cw);if(cti)issues.push(cti);for(const k of Object.keys(c))if(k!=="value"&&k!=="title")issues.push(`${cw}: bilinmeyen özellik "${k}".`);if(!cti)choices.push({value:c.value,title:{tr:c.title.tr,en:c.title.en}})})}}else if(f.choices!==void 0)issues.push(`${fw}: "choices" yalnız seçim (choice) alanında kullanılır.`);let maxLength;if(t==="text"||t==="longText"){const cap=t==="text"?TEXT_MAX_LENGTH:LONG_TEXT_MAX_LENGTH;if(f.maxLength===void 0)maxLength=t==="text"?TEXT_DEFAULT_MAX_LENGTH:LONG_TEXT_DEFAULT_MAX_LENGTH;else if(typeof f.maxLength!=="number"||!Number.isInteger(f.maxLength)||f.maxLength<1||f.maxLength>cap)issues.push(`${fw}: "maxLength" 1 ile ${cap} arasında bir tamsayı olmalı.`);else maxLength=f.maxLength}else if(f.maxLength!==void 0)issues.push(`${fw}: "maxLength" yalnız metin alanlarında kullanılır.`);let min;let max;for(const bound of["min","max"]){const v=f[bound];if(v===void 0)continue;if(t!=="number"&&t!=="money"){issues.push(`${fw}: "${bound}" yalnız sayı ve para alanlarında kullanılır.`);continue}if(typeof v!=="number"||!Number.isFinite(v)||t==="money"&&moneyToMinor(v)===null)issues.push(`${fw}: "${bound}" geçerli bir sayı olmalı${t==="money"?" (en çok iki ondalık basamak)":""}.`);else if(bound==="min")min=v;else max=v}if(min!==void 0&&max!==void 0&&min>max)issues.push(`${fw}: "min" "max"tan büyük olamaz.`);let def;if(f.default!==void 0){if(NO_DEFAULT.has(t))issues.push(`${fw}: "${t}" türündeki alanda varsayılan verilemez.`);else if(unique)issues.push(`${fw}: tekil (unique) alanda varsayılan verilemez (her kayıt aynı değeri alırdı).`);else{const di=defaultIssue({type:t,choices},f.default,fw);if(di)issues.push(di);else if(t==="money")def=minorToMoney(moneyToMinor(f.default));else if(t==="datetime")def=normalizeDateTime(f.default);else def=f.default;if(def!==void 0&&(t==="number"||t==="money")){const n=t==="money"?moneyToMinor(def)/100:def;if(min!==void 0&&n<min||max!==void 0&&n>max)issues.push(`${fw}: "default" min/max aralığının dışında.`)}if(def!==void 0&&maxLength!==void 0&&[...String(def)].length>maxLength)issues.push(`${fw}: "default" "maxLength" sınırını aşıyor.`)}}const column=t==="money"?`${fieldName}_minor`:fieldName;if(columns.has(column))issues.push(`${fw}: kolon adı "${column}" başka bir kolonla çakışıyor.`);columns.add(column);const foldColumn=t==="text"||t==="longText"?foldColumnName(column):void 0;if(foldColumn!==void 0){if(columns.has(foldColumn))issues.push(`${fw}: arama kolonu "${foldColumn}" başka bir kolonla çakışıyor.`);columns.add(foldColumn)}if(!fti)fields.push({name:fieldName,column,type:t,title:{tr:f.title.tr,en:f.title.en},required:f.required===true,unique,index:f.index===true,default:def,choices,maxLength,foldColumn,min,max})}if(columns.size>DATA_MAX_COLUMNS)issues.push(`${where}: tabloda en çok ${DATA_MAX_COLUMNS} kolon olabilir; bu varlık ${columns.size} kolon gerektiriyor (${SYSTEM_COLUMNS.length} otomatik + alanlar + her metin alanı için bir arama kolonu). Alanları iki varlığa bölün.`);if(!ti)entities.push({name:entityName,title:{tr:e.title.tr,en:e.title.en},softDelete:e.softDelete!==false,fields})}if(issues.length>0)throw new DataModelError(issues);return{version:1,entities}}function toModelInput(model){const entities={};for(const e of model.entities){const fields={};for(const f of e.fields){const out={type:f.type,title:{...f.title}};if(f.required)out.required=true;if(f.unique)out.unique=true;if(f.index)out.index=true;if(f.default!==void 0)out.default=f.default;if(f.choices.length>0)out.choices=f.choices.map(c=>({value:c.value,title:{...c.title}}));if(f.maxLength!==void 0)out.maxLength=f.maxLength;if(f.min!==void 0)out.min=f.min;if(f.max!==void 0)out.max=f.max;fields[f.name]=out}entities[e.name]={title:{...e.title},fields,softDelete:e.softDelete}}return{version:1,entities}}var DATA_SNAPSHOT_FILE=".one-data-snapshot.json";var DATA_MODEL_FILE="one.data.json";var DATA_TYPES_FILE="src/data/types.ts";var MIGRATION_NAME_RE=/^(\d{4})_[a-z0-9_]+\.sql$/;var q=name=>`"${name}"`;function sqlType(f){switch(f.type){case"number":return"REAL";case"money":case"boolean":return"INTEGER";default:return"TEXT"}}function literal(f,value2){if(f.type==="boolean")return value2===true?"1":"0";if(f.type==="money")return String(moneyToMinor(value2));if(f.type==="number")return String(value2);return`'${String(value2).replace(/'/g,"''")}'`}function checkClause(f){if(f.type==="boolean")return` CHECK (${q(f.column)} IN (0, 1))`;return""}function foldColumnDef(f){return f.foldColumn===void 0?null:`${q(f.foldColumn)} TEXT`}function columnDef(f,adding){let sql=`${q(f.column)} ${sqlType(f)}`;if(f.required){sql+=" NOT NULL";if(adding&&f.default!==void 0)sql+=` DEFAULT ${literal(f,f.default)}`}return sql+checkClause(f)}var indexName=(kind,entity,field2)=>`${kind}_${entity}__${field2}`;function indexSql(e,f){if(f.unique)return`CREATE UNIQUE INDEX ${q(indexName("ux",e.name,f.name))} ON ${q(e.name)} (${q(f.column)}) WHERE "deleted_at" IS NULL;`;if(f.index)return`CREATE INDEX ${q(indexName("ix",e.name,f.name))} ON ${q(e.name)} (${q(f.column)});`;return null}function createEntitySql(e){const cols=['"id" TEXT NOT NULL PRIMARY KEY','"created_at" TEXT NOT NULL','"created_by" TEXT NOT NULL','"updated_at" TEXT NOT NULL','"updated_by" TEXT NOT NULL','"deleted_at" TEXT',...e.fields.flatMap(f=>{const fold=foldColumnDef(f);return fold===null?[columnDef(f,false)]:[columnDef(f,false),fold]})];const out=[`CREATE TABLE ${q(e.name)} (
|
|
4
4
|
${cols.join(",\n ")}
|
|
5
5
|
);`];out.push(`CREATE INDEX ${q(`ix_${e.name}__created_at`)} ON ${q(e.name)} ("created_at") WHERE "deleted_at" IS NULL;`);for(const f of e.fields){const ix=indexSql(e,f);if(ix)out.push(ix)}return out}var CURRENT={foldColumns:true,choiceChecks:new Set};function diffDataModels(prev,next,legacy=CURRENT){const statements=[];const changes=[];const errors=[];const prevEntities=new Map((prev?.entities??[]).map(e=>[e.name,e]));const nextNames=new Set(next.entities.map(e=>e.name));for(const old of prevEntities.values())if(!nextNames.has(old.name))errors.push(`"${old.name}" varlığı modelden kaldırılmış. Tablo ve verisi otomatik silinmez; varlığı one.data.json'a geri ekleyin (kullanmıyorsanız ekranlardan kaldırmanız yeterli).`);for(const e of next.entities){const old=prevEntities.get(e.name);if(!old){statements.push(`-- ${e.name}: ${e.title.tr}`,...createEntitySql(e));changes.push({kind:"create-table",entity:e.name});continue}const oldFields=new Map(old.fields.map(f=>[f.name,f]));const nextFieldNames=new Set(e.fields.map(f=>f.name));for(const of of old.fields)if(!nextFieldNames.has(of.name))errors.push(`"${e.name}.${of.name}" alanı kaldırılmış ya da adı değiştirilmiş. Kolon ve verisi otomatik silinmez/yeniden adlandırılmaz; alanı geri ekleyin (yeni bir ad gerekiyorsa yeni bir alan açın, eskisini kullanmayı bırakın).`);const added=[];for(const f of e.fields){const of=oldFields.get(f.name);const where=`"${e.name}.${f.name}"`;if(!of){if(f.required&&f.default===void 0)errors.push(`${where} zorunlu bir alan olarak eklenmiş ama varsayılanı yok: var olan kayıtlar bu alanı boş bırakamaz. Ya "default" verin ya da alanı isteğe bağlı ekleyin.`);if(f.unique&&f.default!==void 0)errors.push(`${where} tekil ve varsayılanlı eklenemez (var olan kayıtlar aynı değeri alırdı).`);if(f.unique&&f.required)errors.push(`${where} var olan bir tabloya tekil VE zorunlu eklenemez; isteğe bağlı tekil ekleyin.`);added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${columnDef(f,true)};`);const fold=foldColumnDef(f);if(fold)added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${fold};`);const ix=indexSql(e,f);if(ix)added.push(ix);changes.push({kind:"add-column",entity:e.name,field:f.name});continue}if(of.type!==f.type)errors.push(`${where} alanının türü "${of.type}" → "${f.type}" değiştirilmiş; tür değişikliği desteklenmez (yeni bir alan açın).`);if(of.required!==f.required)errors.push(`${where} alanı ${f.required?"zorunlu yapılmış":"isteğe bağlı yapılmış"}; var olan kolonun bu kuralı değiştirilemez (yeni bir alan açın ya da eski hâle döndürün).`);if(of.unique!==f.unique)errors.push(`${where} alanının tekillik (unique) kuralı değiştirilmiş; var olan kolonda desteklenmez.`);if(of.index&&!f.index)errors.push(`${where} alanının dizini (index) kaldırılmış; dizin silme desteklenmez, "index": true kalmalı.`);if(!of.index&&f.index&&!f.unique){added.push(indexSql(e,f));changes.push({kind:"add-index",entity:e.name,field:f.name})}if(of.type===f.type&&f.type==="choice"){const nextValues=new Set(f.choices.map(c=>c.value));const removed=of.choices.map(c=>c.value).filter(v=>!nextValues.has(v));const oldValues=new Set(of.choices.map(c=>c.value));const addedValues=f.choices.map(c=>c.value).filter(v=>!oldValues.has(v));if(removed.length>0)errors.push(`${where} seçim alanından değer kaldırılmış (${removed.join(", ")}). Var olan kayıtlar bu değeri taşıyabilir; değeri listede bırakın (artık kullanılmayacaksa ekranlardan gizleyin). Yalnız görünen adlar (title) ve sıra değişebilir.`);else if(addedValues.length>0&&legacy.choiceChecks.has(`${e.name}.${f.name}`))errors.push(`${where} seçim alanına değer eklenmiş (${addedValues.join(", ")}), ama bu alanın tablosu eski bir sürümle kurulmuş ve veritabanında değerleri bir kısıtla sabitlenmiş; kısıt ekleme yapan bir migration ile kaldırılamaz. Yeni değerler için yeni bir seçim alanı açın ya da platform ekibine danışın.`)}if(of.type===f.type&&f.foldColumn!==void 0&&!legacy.foldColumns){added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${foldColumnDef(f)};`);changes.push({kind:"add-search-column",entity:e.name,field:f.name})}}if(added.length>0)statements.push(`-- ${e.name}: ${e.title.tr}`,...added)}return{statements,changes,errors}}var DATA_SNAPSHOT_VERSION=2;var isLegacyKey=v=>typeof v==="string"&&/^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/.test(v);function readSnapshot(raw){if(raw===null)return null;const broken=why=>new DataModelError([`migrations/${DATA_SNAPSHOT_FILE} ${why} — dosya elle düzenlenmez; git'teki son hâline döndürün.`]);let json;try{json=JSON.parse(raw)}catch{throw new DataModelError([`migrations/${DATA_SNAPSHOT_FILE} okunamadı (geçerli JSON değil). Bu dosya elle düzenlenmez; git'teki son hâline döndürün.`])}const doc2=json??{};let model;try{model=parseDataModel(doc2.model)}catch(e){if(e instanceof DataModelError)throw broken(`geçersiz: ${e.issues.join(" · ")}`);throw e}if(doc2.v===void 0||doc2.v===1){const checks=new Set;for(const e of model.entities)for(const f of e.fields)if(f.type==="choice")checks.add(`${e.name}.${f.name}`);return{model,legacy:{foldColumns:false,choiceChecks:checks}}}if(doc2.v!==DATA_SNAPSHOT_VERSION)throw broken(`bu SDK'nın tanımadığı bir sürümde (v: ${JSON.stringify(doc2.v)}); SDK'yı güncelleyin`);const raw2=doc2.choiceChecks??[];if(!Array.isArray(raw2)||!raw2.every(isLegacyKey))throw broken('geçersiz: "choiceChecks" "<varlık>.<alan>" listesi olmalı');return{model,legacy:{foldColumns:true,choiceChecks:new Set(raw2)}}}function renderSnapshot(model,legacy=CURRENT){const present=new Set(model.entities.flatMap(e=>e.fields.filter(f=>f.type==="choice").map(f=>`${e.name}.${f.name}`)));const choiceChecks=[...legacy.choiceChecks].filter(k=>present.has(k)).sort();return JSON.stringify({note:"one data generate tarafından yazılır; elle düzenlemeyin. Bir sonraki üretimin farkı bu modele göre alınır.",v:DATA_SNAPSHOT_VERSION,...choiceChecks.length>0?{choiceChecks}:{},model:toModelInput(model)},null,2)+"\n"}function nextMigrationName(existing2){let max=0;for(const name of existing2){const m=MIGRATION_NAME_RE.exec(name);if(m)max=Math.max(max,Number(m[1]))}if(max>=9999)throw new DataModelError(["migrations/ altında 9999 numarasına ulaşılmış; yeni numara verilemiyor."]);return`${String(max+1).padStart(4,"0")}_one_data.sql`}function tablesCreatedIn(sql){const text=sql.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ");const out=new Set;const re=/\bcreate\s+(?:temp\s+|temporary\s+|virtual\s+)?table\s+(?:if\s+not\s+exists\s+)?(?:main\s*\.\s*)?["`[]?([A-Za-z0-9_]+)["`\]]?/gi;for(const m of text.matchAll(re))out.add(m[1].toLowerCase());return out}function planDataGeneration(input){const next=parseDataModel(input.model);const prev=readSnapshot(input.snapshot);const legacy=prev?.legacy??CURRENT;const diff=diffDataModels(prev?.model??null,next,legacy);const issues=[...diff.errors];const handwritten=new Set;for(const m of input.existing)for(const t of tablesCreatedIn(m.sql))handwritten.add(t);for(const c of diff.changes)if(c.kind==="create-table"&&handwritten.has(c.entity))issues.push(`"${c.entity}" adında bir tablo elle yazılmış bir migration'da zaten kuruluyor; varlığa başka bir ad verin.`);if(issues.length>0)throw new DataModelError(issues);const migration=diff.statements.length===0?null:{name:nextMigrationName(input.existing.map(m=>m.name)),sql:"-- one data generate tarafından one.data.json'dan üretildi.\n-- ⛔ Uygulandıktan sonra bu dosya DEĞİŞTİRİLMEZ: model değişince bir sonraki numarayla yeni dosya üretilir.\n-- Her kurulumun kendi veritabanı vardır; tablolarda portal kolonu yoktur. Kişi = kullanıcı kimliği (sub).\n"+diff.statements.join("\n")+"\n"};return{migration,types:renderTypes(next),snapshot:renderSnapshot(next,legacy),changes:diff.changes}}var pascal=name=>name.split("_").filter(Boolean).map(p=>p[0].toUpperCase()+p.slice(1)).join("");function tsValueType(f){switch(f.type){case"number":return"number";case"boolean":return"boolean";case"choice":return f.choices.map(c=>JSON.stringify(c.value)).join(" | ");default:return"string"}}function tsInputType(f){if(f.type==="money")return"string | number";if(f.type==="number")return"number | string";return tsValueType(f)}var doc=f=>{const unit=f.type==="money"?' — tutar, ondalıklı metin ("1234.50")':f.type==="date"?" — YYYY-AA-GG":f.type==="datetime"?" — ISO tarih-saat (UTC)":f.type==="person"?" — kişi (kullanıcı kimliği, sub)":f.type==="file"?" — dosya anahtarı":"";return` /** ${f.title.tr}${unit} */
|
|
6
|
-
`};function renderTypes(model){const lines=["// one data generate tarafından one.data.json'dan üretilir; elle düzenlemeyin (değişiklik one.data.json'a yazılır).","import type { OneDataModel } from '@devoplus/one-sdk'","",`export type EntityName = ${model.entities.length>0?model.entities.map(e=>JSON.stringify(e.name)).join(" | "):"never"}`,""];for(const e of model.entities){const name=pascal(e.name);lines.push(`/** ${e.title.tr} — API'nin döndürdüğü kayıt. */`,`export interface ${name}Record {`);lines.push(" id: string"," created_at: string"," created_by: string"," updated_at: string"," updated_by: string");for(const f of e.fields)lines.push(doc(f).trimEnd(),` ${f.name}: ${tsValueType(f)}${f.required?"":" | null"}`);lines.push("}","",`/** ${e.title.tr} — kayıt oluştururken gönderilen alanlar (güncellemede hepsi isteğe bağlıdır). */`,`export interface ${name}Input {`);for(const f of e.fields){const optional=!f.required||f.default!==void 0;lines.push(` ${f.name}${optional?"?":""}: ${tsInputType(f)}${f.required?"":" | null"}`)}lines.push("}","")}lines.push("/** Doğrulanmış veri modeli — createCrudHandler({ model: dataModel, … }) ile kullanılır. */",`export const dataModel: OneDataModel = ${JSON.stringify(toModelInput(model),null,2)}`,"");return lines.join("\n")}var SERVICES=["database","files","cache"];var DEFAULT_SDK_SPEC="^0.2.0-next.0";var DEFAULT_VERSION="0.1.0";var DEV_DEPENDENCIES={typescript:"^7.0.2",vitest:"^5.0.
|
|
6
|
+
`};function renderTypes(model){const lines=["// one data generate tarafından one.data.json'dan üretilir; elle düzenlemeyin (değişiklik one.data.json'a yazılır).","import type { OneDataModel } from '@devoplus/one-sdk'","",`export type EntityName = ${model.entities.length>0?model.entities.map(e=>JSON.stringify(e.name)).join(" | "):"never"}`,""];for(const e of model.entities){const name=pascal(e.name);lines.push(`/** ${e.title.tr} — API'nin döndürdüğü kayıt. */`,`export interface ${name}Record {`);lines.push(" id: string"," created_at: string"," created_by: string"," updated_at: string"," updated_by: string");for(const f of e.fields)lines.push(doc(f).trimEnd(),` ${f.name}: ${tsValueType(f)}${f.required?"":" | null"}`);lines.push("}","",`/** ${e.title.tr} — kayıt oluştururken gönderilen alanlar (güncellemede hepsi isteğe bağlıdır). */`,`export interface ${name}Input {`);for(const f of e.fields){const optional=!f.required||f.default!==void 0;lines.push(` ${f.name}${optional?"?":""}: ${tsInputType(f)}${f.required?"":" | null"}`)}lines.push("}","")}lines.push("/** Doğrulanmış veri modeli — createCrudHandler({ model: dataModel, … }) ile kullanılır. */",`export const dataModel: OneDataModel = ${JSON.stringify(toModelInput(model),null,2)}`,"");return lines.join("\n")}var SERVICES=["database","files","cache"];var DEFAULT_SDK_SPEC="^0.2.0-next.0";var DEFAULT_VERSION="0.1.0";var DEV_DEPENDENCIES={typescript:"^7.0.2",vitest:"^5.0.5"};var SEMVER_RE=/^\d+\.\d+\.\d+$/;var LOCALIZED_TEXT_MAX=120;var DESCRIPTION_SUFFIX_TR=" — Devoplus One uygulaması";var DESCRIPTION_SUFFIX_EN=" — Devoplus One app";var PERMISSION_NAME_SUFFIX_TR=" uygulamasını kullan";var PERMISSION_NAME_PREFIX_EN="Use the ";var PERMISSION_NAME_SUFFIX_EN=" app";var NAME_MAX=LOCALIZED_TEXT_MAX-Math.max(DESCRIPTION_SUFFIX_TR.length,DESCRIPTION_SUFFIX_EN.length,PERMISSION_NAME_SUFFIX_TR.length,PERMISSION_NAME_PREFIX_EN.length+PERMISSION_NAME_SUFFIX_EN.length);var TSCONFIG={compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"Bundler",lib:["ES2022","WebWorker"],types:[],strict:true,noUncheckedIndexedAccess:true,exactOptionalPropertyTypes:true,noImplicitOverride:true,noFallthroughCasesInSwitch:true,isolatedModules:true,verbatimModuleSyntax:true,skipLibCheck:true,noEmit:true,allowImportingTsExtensions:true,resolveJsonModule:true},include:["src"]};var GITIGNORE="node_modules/\ndist/\n.one/\n.dev.vars*\n.env*\n";function template(name){return readFileSync(new URL(`../templates/${name}`,import.meta.url),"utf8")}function pascalCase(slug2){return slug2.split("-").filter(Boolean).map((part,index)=>{if(index>0&&(/^[0-9]/.test(part)||part.startsWith("x")))return`X${part}`;return part.charAt(0).toUpperCase()+part.slice(1)}).join("")}function titleCase(slug2){return slug2.split("-").filter(Boolean).map(part=>part.charAt(0).toUpperCase()+part.slice(1)).join(" ")}function permissionCodeFor(slug2){return`One.${pascalCase(slug2)}.Use`}function requireText(value2,label,max){const trimmed=value2.trim();if(trimmed.length===0)throw new Error(`${label} boş olamaz`);if(trimmed.length>max)throw new Error(`${label} en çok ${max} karakter olabilir (${trimmed.length})`);return trimmed}function buildScaffold(options){const slug2=options.slug.trim();if(!isValidSlug(slug2))throw new Error(`slug geçersiz: "${slug2}" — küçük harf/rakam ve tek tire, harfle başlar, en çok ${SLUG_MAX} karakter`);const nameTr2=requireText(options.nameTr,"ad (TR)",NAME_MAX);const nameEn2=options.nameEn===void 0?void 0:requireText(options.nameEn,"ad (EN)",NAME_MAX);const version2=options.version??DEFAULT_VERSION;if(!SEMVER_RE.test(version2))throw new Error(`sürüm semver (x.y.z) olmalı: "${version2}"`);const sdkSpec2=options.sdkSpec?.trim()||DEFAULT_SDK_SPEC;const services=new Set(["database",...options.services??[]]);for(const s of services)if(!SERVICES.includes(s))throw new Error(`bilinmeyen servis: ${s} (database · files · cache)`);const withFiles=services.has("files");const withCache=services.has("cache");const scopes2=[...new Set((options.scopes??[]).map(s=>s.trim()).filter(Boolean))];for(const scope of scopes2)if(!SCOPE_RE.test(scope))throw new Error(`scope biçimi geçersiz: "${scope}" (<modül>.<read|write|manage|execute>)`);if(scopes2.includes(DEXA_READ_SCOPE))throw new Error(`"${DEXA_READ_SCOPE}" scope'u iskeletle verilemez: one.manifest.json'da services.dexa.queryKeys ile birlikte beyan edilir ve anahtarlar uydurulmaz. İskeleti bu scope olmadan üretin; sonra manifest'e scope'u ve Devoplus Console kayıt defterindeki anahtarları ekleyin.`);if(scopes2.includes(DEXA_WRITE_SCOPE))throw new Error(`"${DEXA_WRITE_SCOPE}" scope'u iskeletle verilemez: one.manifest.json'da services.dexa.commandKeys ile birlikte beyan edilir ve ERP işlem anahtarları uydurulmaz. İskeleti bu scope olmadan üretin; sonra manifest'e scope'u ve Devoplus Console kayıt defterindeki işlem anahtarlarını ekleyin.`);const permissionCode3=options.permissionCode?.trim()||permissionCodeFor(slug2);if(!PERMISSION_CODE_RE.test(permissionCode3))throw new Error(`izin kodu geçersiz: "${permissionCode3}" — biçim One.<Ad>.<Ad> (büyük harfle başlayan harf/rakam parçaları), örnek ${permissionCodeFor(slug2)}`);const packageName=`one-app-${slug2}`;const templateName=options.template??DEFAULT_TEMPLATE;if(!isTemplateName(templateName))throw new Error(`bilinmeyen şablon: ${String(templateName)} (${TEMPLATE_NAMES.join(" · ")})`);if(templateName!=="blank")return buildTemplateApp({templateName,slug:slug2,nameTr:nameTr2,nameEn:nameEn2,version:version2,sdkSpec:sdkSpec2,services,scopes:scopes2,permissionCode:permissionCode3,packageName,ownerEmail:options.ownerEmail.trim()});const manifest={schemaVersion:1,app:{slug:slug2,name:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},version:version2,description:nameEn2?{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,en:`${nameEn2}${DESCRIPTION_SUFFIX_EN}`}:{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`},owner:{email:options.ownerEmail.trim()}},entry:"src/index.ts",scopes:scopes2,services:{database:true,files:withFiles,cache:withCache},menu:{title:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},icon:"pi pi-box"},navigation:[{path:"/",label:{tr:"Ana sayfa",en:"Home"}}],actingSessions:"read",permission:{code:permissionCode3,name:nameEn2?{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`,en:`${PERMISSION_NAME_PREFIX_EN}${nameEn2}${PERMISSION_NAME_SUFFIX_EN}`}:{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`}},limits:{cpuMs:50,subRequests:10},agent:{summary:nameEn2?{tr:`${nameTr2}: kullanıcının kişisel notları.`,en:`${nameEn2}: the user's personal notes.`}:{tr:`${nameTr2}: kullanıcının kişisel notları.`},tools:[{name:"list-notes",title:{tr:"Notlarımı listele",en:"List my notes"},description:{tr:"Kullanıcının kendi notlarını en yenisi önce döndürür. Salt okunur; hiçbir şeyi değiştirmez.",en:"Returns the user's own notes, newest first. Read-only; changes nothing."},readOnly:true,permission:permissionCode3,input:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:50,description:"En çok kaç not (varsayılan 10)"}}}}]}};const validation=validateManifest(manifest);if(!validation.ok){const detail=validation.errors.map(e=>`${e.path}: ${e.message}`).join("; ");throw new Error(`üretilen manifest geçersiz — ${detail}`)}const vars={slug:slug2,nameTr:nameTr2,nameEn:nameEn2??"",nameTrJs:JSON.stringify(nameTr2),nameEnJs:JSON.stringify(nameEn2??nameTr2),packageName,permissionCode:permissionCode3,ownerEmail:options.ownerEmail.trim(),scopesList:scopes2.length>0?scopes2.map(s=>`\`${s}\``).join(", "):"",files:withFiles,cache:withCache,hasScopes:scopes2.length>0};const packageJson={name:packageName,version:version2,private:true,description:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,type:"module",scripts:{"type-check":"tsc -p tsconfig.json",test:"vitest run",build:"one build",migrate:"one migrate",dev:"one dev"},dependencies:{"@devoplus/one-sdk":sdkSpec2},devDependencies:{...DEV_DEPENDENCIES}};const json=value2=>JSON.stringify(value2,null,2)+"\n";const files=[{path:"one.manifest.json",content:json(manifest)},{path:"package.json",content:json(packageJson)},{path:"tsconfig.json",content:json(TSCONFIG)},{path:".gitignore",content:GITIGNORE},{path:"src/index.ts",content:renderTemplate(template("index.ts.tpl"),vars)},{path:"src/shell.ts",content:renderTemplate(template("shell.ts.tpl"),vars)},{path:"src/agent.ts",content:renderTemplate(template("agent.ts.tpl"),vars)},{path:"src/markdown.d.ts",content:renderTemplate(template("markdown.d.ts.tpl"),vars)},{path:"AGENT_GUIDE.md",content:renderTemplate(template("AGENT_GUIDE.md.tpl"),vars)},{path:"src/shell.test.ts",content:renderTemplate(template("shell.test.ts.tpl"),vars)},{path:"migrations/0001_init.sql",content:renderTemplate(template("0001_init.sql.tpl"),vars)},{path:"AGENTS.md",content:renderTemplate(template("AGENTS.md.tpl"),vars)},{path:"README.md",content:renderTemplate(template("README.md.tpl"),vars)}];return{files,manifest,packageName,permissionCode:permissionCode3,template:"blank"}}var TEMPLATES_ROOT=new URL("../templates/",import.meta.url);function walk(dir,prefix=""){const out=[];for(const name of readdirSync(dir).sort()){const rel=prefix?`${prefix}/${name}`:name;if(statSync(new URL(name,dir)).isDirectory())out.push(...walk(new URL(`${name}/`,dir),rel));else out.push(rel)}return out}function templateFiles(dir,vars,skip){return walk(dir).filter(rel=>!skip(rel)).map(rel=>{const raw=readFileSync(new URL(rel,dir),"utf8");return rel.endsWith(".tpl")?{path:rel.slice(0,-".tpl".length),content:renderTemplate(raw,vars)}:{path:rel,content:raw}})}function composeAgents(common,section){const at=common.indexOf("\n## ");if(at<0)throw new Error("AGENTS.md şablonunda ikinci düzey başlık yok");return`${common.slice(0,at+1)}${section.trimEnd()}
|
|
7
7
|
|
|
8
8
|
${common.slice(at+1)}`}function buildTemplateApp(input){const{templateName,slug:slug2,nameTr:nameTr2,nameEn:nameEn2,version:version2,sdkSpec:sdkSpec2,permissionCode:permissionCode3,packageName,ownerEmail:ownerEmail2}=input;const permissionPrefix=permissionCode3.slice(0,permissionCode3.lastIndexOf(".")+1);const baseVars={slug:slug2,nameTr:nameTr2,nameEn:nameEn2??"",nameTrJs:JSON.stringify(nameTr2),nameEnJs:JSON.stringify(nameEn2??nameTr2),packageName,permissionCode:permissionCode3,permissionPrefix,ownerEmail:ownerEmail2,templateName};const dir=new URL(`${templateName}/`,TEMPLATES_ROOT);const fragment=JSON.parse(renderTemplate(readFileSync(new URL("manifest.json.tpl",dir),"utf8"),baseVars));const services=new Set(input.services);for(const s of SERVICES)if(fragment.services[s])services.add(s);const scopes2=[...new Set([...fragment.scopes,...input.scopes])];const withFiles=services.has("files");const withCache=services.has("cache");const withMail=scopes2.includes("mail.execute");const manifest={schemaVersion:1,app:{slug:slug2,name:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},version:version2,description:nameEn2?{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,en:`${nameEn2}${DESCRIPTION_SUFFIX_EN}`}:{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`},owner:{email:ownerEmail2}},entry:"src/index.ts",scopes:scopes2,services:{database:true,files:withFiles,cache:withCache},menu:{title:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},icon:fragment.menuIcon},navigation:fragment.navigation,actingSessions:"read",permission:{code:permissionCode3,name:nameEn2?{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`,en:`${PERMISSION_NAME_PREFIX_EN}${nameEn2}${PERMISSION_NAME_SUFFIX_EN}`}:{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`}},...fragment.permissions?{permissions:fragment.permissions}:{},limits:{cpuMs:50,subRequests:10},agent:fragment.agent,...fragment.jobs?{jobs:fragment.jobs}:{}};const validation=validateManifest(manifest);if(!validation.ok){const detail=validation.errors.map(e=>`${e.path}: ${e.message}`).join("; ");throw new Error(`üretilen manifest geçersiz (${templateName}) — ${detail}`)}const vars={...baseVars,scopesList:scopes2.length>0?scopes2.map(s=>`\`${s}\``).join(", "):"",files:withFiles,cache:withCache,hasScopes:scopes2.length>0};const shared=templateFiles(new URL("_shared/files/",TEMPLATES_ROOT),vars,rel=>rel==="src/lib/mail.ts.tpl"&&!withMail||rel==="src/lib/files.ts.tpl"&&!withFiles);const own=templateFiles(new URL("files/",dir),vars,()=>false);const dataModel=own.find(f=>f.path===DATA_MODEL_FILE);if(!dataModel)throw new Error(`${templateName} şablonunda ${DATA_MODEL_FILE} yok`);const plan=planDataGeneration({model:JSON.parse(dataModel.content),snapshot:null,existing:[]});if(!plan.migration)throw new Error(`${templateName} veri modeli migration üretmedi`);const packageJson={name:packageName,version:version2,private:true,description:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,type:"module",scripts:{"type-check":"tsc -p tsconfig.json",test:"vitest run",build:"one build",migrate:"one migrate",dev:"one dev","data:generate":"one data generate"},dependencies:{"@devoplus/one-sdk":sdkSpec2},devDependencies:{...DEV_DEPENDENCIES}};const json=value2=>JSON.stringify(value2,null,2)+"\n";const files=[{path:"one.manifest.json",content:json(manifest)},{path:"package.json",content:json(packageJson)},{path:"tsconfig.json",content:json(TSCONFIG)},{path:".gitignore",content:GITIGNORE},{path:"src/markdown.d.ts",content:renderTemplate(template("markdown.d.ts.tpl"),vars)},...shared,...own,{path:`migrations/${plan.migration.name}`,content:plan.migration.sql},{path:`migrations/${DATA_SNAPSHOT_FILE}`,content:plan.snapshot},{path:DATA_TYPES_FILE,content:plan.types},{path:"AGENTS.md",content:composeAgents(renderTemplate(template("AGENTS.md.tpl"),vars),`${renderTemplate(readFileSync(new URL("AGENTS.template.md.tpl",dir),"utf8"),vars).trimEnd()}
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-devoplus-one-app",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.185",
|
|
4
4
|
"description": "Yeni bir Devoplus One uygulaması iskeleti üretir: manifest, SDK'lı giriş noktası, Portal köprülü kabuk, veritabanı migration'ı ve AGENTS.md",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"create-devoplus-one-app": "./dist/cli.mjs"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"templates",
|
|
13
|
-
"README.md"
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE.md"
|
|
14
15
|
],
|
|
15
16
|
"publishConfig": {
|
|
16
17
|
"access": "public"
|
package/templates/AGENTS.md.tpl
CHANGED
|
@@ -285,6 +285,14 @@ Her Devoplus One uygulaması Portal'ın bir parçası gibi görünür. Kurallar
|
|
|
285
285
|
|
|
286
286
|
## Çalışma kuralları
|
|
287
287
|
|
|
288
|
+
- **İşe başlamadan SDK sürümünü denetleyin.** Kurulu sürümü `pnpm list @devoplus/one-sdk`, kanaldaki en yeni sürümü
|
|
289
|
+
`npm view @devoplus/one-sdk dist-tags` gösterir (ön sürüm kuruluysa `next`, kararlı kuruluysa `latest` etiketine
|
|
290
|
+
bakılır). `one` komutu da kurulu SDK eskiyse hata akışına "Devoplus One SDK'nın daha yeni bir sürümü var" uyarısını ve
|
|
291
|
+
güncelleme komutunu yazar; komut durmaz. Bu uyarıyı görürseniz başka bir işe geçmeden SDK'yı güncelleyin
|
|
292
|
+
(`pnpm add @devoplus/one-sdk@next`), `pnpm type-check && pnpm test && pnpm build` çalıştırın ve `package.json` ile
|
|
293
|
+
`pnpm-lock.yaml`'ı kodla birlikte yükleyin. Yayın hattı (`one-ci validate`) asgari sürümden eski ya da sürümü okunamayan
|
|
294
|
+
SDK için `⚠ SDK:` ile başlayan bir uyarı yazar; bugün yayını durdurmaz, platform ileride durdurabilir. Uyarıyı kalıcı
|
|
295
|
+
bırakmayın.
|
|
288
296
|
- `pnpm type-check && pnpm test && pnpm build` yeşil olmadan commit yapılmaz.
|
|
289
297
|
- Hata yolu tek yerdedir: `catch (error) { return problemResponse(error) }`. `OneError` RFC 9457 gövdeye çevrilir,
|
|
290
298
|
bilinmeyen hata 500 döner ve ayrıntı sızdırılmaz. `OneError.retryAfterSeconds` pozitif tam sayıysa (ör. 409
|