sayren 0.6.0 → 0.7.0
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.mjs +68 -17
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -890,7 +890,8 @@ const cli_app_dev_error = ((inputs, options = {}) => {
|
|
|
890
890
|
const ko_cli_app_dev_listening = (i) => {
|
|
891
891
|
return `앱 로컬 실행 중 http://localhost:${i?.port} — 상점 ${i?.store} · 이벤트 ${i?.events}
|
|
892
892
|
이벤트: npx sayren app trigger ORDER.PAID --order <orderId>
|
|
893
|
-
HTTP: http://localhost:${i?.port}/_http/<경로>
|
|
893
|
+
HTTP: http://localhost:${i?.port}/_http/<경로> · 공개 HTTP: http://localhost:${i?.port}/hooks/<설치 id>/<경로>
|
|
894
|
+
예약 작업: npx sayren app trigger --schedule <이름>
|
|
894
895
|
설정·비밀 값: .env의 SAYREN_APP_SETTING_*·SAYREN_APP_SECRET_*
|
|
895
896
|
종료는 Ctrl-C`;
|
|
896
897
|
};
|
|
@@ -1036,7 +1037,7 @@ const cli_app_handle_mismatch = ((inputs, options = {}) => {
|
|
|
1036
1037
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1037
1038
|
/** @typedef {{ events: NonNullable<unknown> }} Cli_App_Handler_MissingInputs */
|
|
1038
1039
|
const ko_cli_app_handler_missing = (i) => {
|
|
1039
|
-
return `매니페스트
|
|
1040
|
+
return `매니페스트(events·schedules·http.public)에 있지만 코드에 함수가 없습니다: ${i?.events} (HANDLER_MISSING)`;
|
|
1040
1041
|
};
|
|
1041
1042
|
/**
|
|
1042
1043
|
|
|
@@ -1532,7 +1533,7 @@ const cli_app_trigger_failed = ((inputs, options = {}) => {
|
|
|
1532
1533
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
1533
1534
|
/** @typedef {{}} Cli_App_Trigger_Type_RequiredInputs */
|
|
1534
1535
|
const ko_cli_app_trigger_type_required = () => {
|
|
1535
|
-
return `이벤트 종류가 필요합니다. 예: npx sayren app trigger ORDER.PAID --order ord_
|
|
1536
|
+
return `이벤트 종류가 필요합니다. 예: npx sayren app trigger ORDER.PAID --order ord_… (예약 작업은 --schedule <이름>)`;
|
|
1536
1537
|
};
|
|
1537
1538
|
/**
|
|
1538
1539
|
|
|
@@ -5249,11 +5250,13 @@ const ko_cli_usage = (i) => {
|
|
|
5249
5250
|
--key <키> app: 앱 배포 키(developer.sayren.app에서 발행, SAYREN_APP_KEY). CI에서 deploy·release·status·logs·secret에 씁니다
|
|
5250
5251
|
-m, --message <메모> app deploy: 버전 메모
|
|
5251
5252
|
--name <이름> app init: 앱 이름(기본: handle)
|
|
5253
|
+
--private-store <storeId> app init: 이 상점 하나에만 설치되는 비공개 앱으로 만듭니다
|
|
5252
5254
|
--port <포트> app dev·trigger: 로컬 포트(기본 4020)
|
|
5253
5255
|
--since <기간> app logs: 최근 기간만(30m·1h·2d)
|
|
5254
5256
|
--tail app logs: 새 기록을 계속 봅니다
|
|
5255
5257
|
--data <json> app trigger: 이벤트 데이터
|
|
5256
5258
|
--order <orderId> app trigger: 주문 id를 데이터에 넣습니다
|
|
5259
|
+
--schedule <이름> app trigger: 이벤트 대신 예약 작업을 실행합니다
|
|
5257
5260
|
--lang <로케일> 메시지 언어(기본: SAYREN_LANG → 시스템 로케일 → ko)
|
|
5258
5261
|
|
|
5259
5262
|
종료 코드: 0 성공 · 1 실패 · 3 승인 대기(에이전트 토큰)`;
|
|
@@ -9177,7 +9180,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
|
|
|
9177
9180
|
|
|
9178
9181
|
//#endregion
|
|
9179
9182
|
//#region package.json
|
|
9180
|
-
var version = "0.
|
|
9183
|
+
var version = "0.7.0";
|
|
9181
9184
|
|
|
9182
9185
|
//#endregion
|
|
9183
9186
|
//#region src/client.ts
|
|
@@ -9391,7 +9394,8 @@ async function loadBundle(dir, server) {
|
|
|
9391
9394
|
app,
|
|
9392
9395
|
meta: {
|
|
9393
9396
|
events: [...meta.events],
|
|
9394
|
-
http: [...meta.http]
|
|
9397
|
+
http: [...meta.http],
|
|
9398
|
+
schedules: [...meta.schedules ?? []]
|
|
9395
9399
|
}
|
|
9396
9400
|
};
|
|
9397
9401
|
}
|
|
@@ -9439,7 +9443,12 @@ async function verifyApp(dir) {
|
|
|
9439
9443
|
const gzipBytes = gzipSync(server).byteLength;
|
|
9440
9444
|
if (gzipBytes > APP_SERVER_BUNDLE_MAX_GZIP_BYTES) throw new CliError(cli_app_bundle_too_large({ size: formatMb(gzipBytes) }));
|
|
9441
9445
|
const { app, meta } = await loadBundle(dir, server);
|
|
9442
|
-
const
|
|
9446
|
+
const httpKeys = new Set(meta.http.map((key) => key.trim().replace(/\s+/g, " ")));
|
|
9447
|
+
const missing = [
|
|
9448
|
+
...manifest.events.filter((event) => !meta.events.includes(event)),
|
|
9449
|
+
...Object.keys(manifest.schedules).filter((name) => !meta.schedules.includes(name)),
|
|
9450
|
+
...(manifest.http?.public ?? []).filter((key) => !httpKeys.has(key))
|
|
9451
|
+
];
|
|
9443
9452
|
if (missing.length) throw new CliError(cli_app_handler_missing({ events: missing.join(", ") }));
|
|
9444
9453
|
const warnings = [];
|
|
9445
9454
|
const listed = manifest.events;
|
|
@@ -9536,9 +9545,9 @@ function initFiles(handle, name) {
|
|
|
9536
9545
|
export default defineApp({
|
|
9537
9546
|
events: {
|
|
9538
9547
|
"ORDER.PAID": async (event, ctx) => {
|
|
9539
|
-
|
|
9540
|
-
const order = await ctx.api.orders.get(orderId);
|
|
9541
|
-
ctx.log.info("결제된 주문", order.orderNo, order.totalAmount);
|
|
9548
|
+
// event.data는 ORDER.PAID의 모양으로 좁혀진다 — 캐스팅하지 않는다
|
|
9549
|
+
const order = await ctx.api.orders.get(event.data.orderId);
|
|
9550
|
+
ctx.log.info("결제된 주문", order.orderNo, order.payment.totalAmount);
|
|
9542
9551
|
},
|
|
9543
9552
|
},
|
|
9544
9553
|
});
|
|
@@ -9554,9 +9563,11 @@ async function appInit(handle, flags, io) {
|
|
|
9554
9563
|
if (existsSync(join(dir, APP_MANIFEST_FILE))) throw new CliError(cli_app_dir_exists({ dir: relative(io.cwd, dir) || "." }));
|
|
9555
9564
|
const name = flags.name?.trim() || handle;
|
|
9556
9565
|
const auth = await developerAuth(flags, dir, io, { accountOnly: true });
|
|
9566
|
+
const privateStoreId = flags.privateStore?.trim();
|
|
9557
9567
|
const app = await devApi(auth, io, "POST", "/apps", {
|
|
9558
9568
|
handle,
|
|
9559
|
-
name
|
|
9569
|
+
name,
|
|
9570
|
+
...privateStoreId ? { privateStoreId } : {}
|
|
9560
9571
|
});
|
|
9561
9572
|
mkdirSync(join(dir, "src"), { recursive: true });
|
|
9562
9573
|
for (const [path, content] of Object.entries(initFiles(handle, name))) {
|
|
@@ -9803,15 +9814,17 @@ function devEnvelope(input) {
|
|
|
9803
9814
|
versionNumber: 0,
|
|
9804
9815
|
storeId: input.token?.storeId ?? "store_dev",
|
|
9805
9816
|
storeName: "(dev)",
|
|
9806
|
-
apiBaseUrl: input.apiBaseUrl
|
|
9817
|
+
apiBaseUrl: input.apiBaseUrl,
|
|
9818
|
+
publicBaseUrl: `${input.publicOrigin}/hooks/${input.token?.installationId ?? "inst_dev"}`
|
|
9807
9819
|
},
|
|
9808
9820
|
token: input.token?.token ?? null,
|
|
9809
9821
|
settings: input.settings,
|
|
9810
9822
|
secrets: input.secrets,
|
|
9811
|
-
...input.kind === "http" ? { user: {
|
|
9823
|
+
...input.kind === "http" && !input.public ? { user: {
|
|
9812
9824
|
userId: "user_dev",
|
|
9813
9825
|
role: "OWNER"
|
|
9814
9826
|
} } : {},
|
|
9827
|
+
...input.schedule ? { schedule: input.schedule } : {},
|
|
9815
9828
|
...input.event ? { event: {
|
|
9816
9829
|
id: `evt_dev_${devSeq}`,
|
|
9817
9830
|
type: input.event.type,
|
|
@@ -9880,12 +9893,30 @@ async function appDev(flags, io) {
|
|
|
9880
9893
|
await ensureToken();
|
|
9881
9894
|
const local = () => localSettingsOf(manifest, readEnvFile(dir));
|
|
9882
9895
|
const apiBaseUrl = `${state.apiOrigin}/v1`;
|
|
9896
|
+
const publicOrigin = `http://localhost:${port}`;
|
|
9883
9897
|
const uiRoot = join(dir, UI_DIR);
|
|
9884
9898
|
const handle = async (req, res) => {
|
|
9885
9899
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
9886
9900
|
try {
|
|
9887
9901
|
if (req.method === "POST" && url.pathname === "/_trigger") {
|
|
9888
9902
|
const body = JSON.parse(await readBody(req) || "{}");
|
|
9903
|
+
if (body.schedule) {
|
|
9904
|
+
const { settings: settings$1, secrets: secrets$1 } = local();
|
|
9905
|
+
const result$1 = await invokeLocal(app, devEnvelope({
|
|
9906
|
+
kind: "schedule",
|
|
9907
|
+
schedule: body.schedule,
|
|
9908
|
+
publicOrigin,
|
|
9909
|
+
token: await ensureToken(),
|
|
9910
|
+
apiBaseUrl,
|
|
9911
|
+
appId: state.appId,
|
|
9912
|
+
settings: settings$1,
|
|
9913
|
+
secrets: secrets$1
|
|
9914
|
+
}));
|
|
9915
|
+
printResult(`[schedule] ${body.schedule}`, result$1, io);
|
|
9916
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
9917
|
+
res.end(JSON.stringify(result$1));
|
|
9918
|
+
return;
|
|
9919
|
+
}
|
|
9889
9920
|
if (!body.type) {
|
|
9890
9921
|
res.writeHead(400, { "content-type": "application/json" });
|
|
9891
9922
|
res.end(JSON.stringify({ error: "TYPE_REQUIRED" }));
|
|
@@ -9894,6 +9925,7 @@ async function appDev(flags, io) {
|
|
|
9894
9925
|
const { settings, secrets } = local();
|
|
9895
9926
|
const result = await invokeLocal(app, devEnvelope({
|
|
9896
9927
|
kind: "event",
|
|
9928
|
+
publicOrigin,
|
|
9897
9929
|
token: await ensureToken(),
|
|
9898
9930
|
apiBaseUrl,
|
|
9899
9931
|
appId: state.appId,
|
|
@@ -9909,13 +9941,17 @@ async function appDev(flags, io) {
|
|
|
9909
9941
|
res.end(JSON.stringify(result));
|
|
9910
9942
|
return;
|
|
9911
9943
|
}
|
|
9912
|
-
|
|
9944
|
+
const hook = /^\/hooks\/[^/]+(\/.*)$/.exec(url.pathname);
|
|
9945
|
+
if (url.pathname.startsWith("/_http/") || hook) {
|
|
9946
|
+
const route = hook ? hook[1] : url.pathname.slice(6);
|
|
9913
9947
|
const headers = {};
|
|
9914
9948
|
for (const [key, value] of Object.entries(req.headers)) if (typeof value === "string" && key !== "host") headers[key] = value;
|
|
9915
9949
|
const method = (req.method ?? "GET").toUpperCase();
|
|
9916
9950
|
const { settings, secrets } = local();
|
|
9917
9951
|
const result = await invokeLocal(app, devEnvelope({
|
|
9918
9952
|
kind: "http",
|
|
9953
|
+
public: Boolean(hook),
|
|
9954
|
+
publicOrigin,
|
|
9919
9955
|
token: await ensureToken(),
|
|
9920
9956
|
apiBaseUrl,
|
|
9921
9957
|
appId: state.appId,
|
|
@@ -9923,12 +9959,12 @@ async function appDev(flags, io) {
|
|
|
9923
9959
|
secrets,
|
|
9924
9960
|
request: {
|
|
9925
9961
|
method,
|
|
9926
|
-
path: `${
|
|
9962
|
+
path: `${route}${url.search}`,
|
|
9927
9963
|
headers,
|
|
9928
9964
|
body: method === "GET" ? null : await readBody(req)
|
|
9929
9965
|
}
|
|
9930
9966
|
}));
|
|
9931
|
-
printResult(`[http] ${method} ${
|
|
9967
|
+
printResult(`[${hook ? "hook" : "http"}] ${method} ${route}`, result, io);
|
|
9932
9968
|
if (!result.response) {
|
|
9933
9969
|
res.writeHead(500, { "content-type": "application/json" });
|
|
9934
9970
|
res.end(JSON.stringify({
|
|
@@ -9982,6 +10018,18 @@ async function appDev(flags, io) {
|
|
|
9982
10018
|
return 0;
|
|
9983
10019
|
}
|
|
9984
10020
|
async function appTrigger(type, flags, io) {
|
|
10021
|
+
const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
|
|
10022
|
+
if (flags.schedule) {
|
|
10023
|
+
const res$1 = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
|
|
10024
|
+
method: "POST",
|
|
10025
|
+
headers: { "content-type": "application/json" },
|
|
10026
|
+
body: JSON.stringify({ schedule: flags.schedule })
|
|
10027
|
+
});
|
|
10028
|
+
if (!res$1.ok) throw new CliError(cli_app_trigger_failed({ status: String(res$1.status) }));
|
|
10029
|
+
const result$1 = await res$1.json();
|
|
10030
|
+
printResult(`[schedule] ${flags.schedule}`, result$1, io);
|
|
10031
|
+
return result$1.outcome === "OK" ? 0 : 1;
|
|
10032
|
+
}
|
|
9985
10033
|
if (!type) throw new CliError(cli_app_trigger_type_required());
|
|
9986
10034
|
let data = {};
|
|
9987
10035
|
if (flags.data) try {
|
|
@@ -9993,7 +10041,6 @@ async function appTrigger(type, flags, io) {
|
|
|
9993
10041
|
...data,
|
|
9994
10042
|
orderId: flags.order
|
|
9995
10043
|
};
|
|
9996
|
-
const port = Number(flags.port ?? io.env.SAYREN_APP_PORT ?? APP_DEV_PORT);
|
|
9997
10044
|
const res = await io.fetch(`http://127.0.0.1:${port}/_trigger`, {
|
|
9998
10045
|
method: "POST",
|
|
9999
10046
|
headers: { "content-type": "application/json" },
|
|
@@ -11905,7 +11952,9 @@ const VALUE_FLAGS = new Set([
|
|
|
11905
11952
|
"port",
|
|
11906
11953
|
"since",
|
|
11907
11954
|
"data",
|
|
11908
|
-
"order"
|
|
11955
|
+
"order",
|
|
11956
|
+
"schedule",
|
|
11957
|
+
"private-store"
|
|
11909
11958
|
]);
|
|
11910
11959
|
const BOOL_FLAGS = new Set([
|
|
11911
11960
|
"force",
|
|
@@ -11990,6 +12039,8 @@ async function main(argv, io) {
|
|
|
11990
12039
|
tail: parsed.flags.tail === true,
|
|
11991
12040
|
data: str(parsed.flags.data),
|
|
11992
12041
|
order: str(parsed.flags.order),
|
|
12042
|
+
schedule: str(parsed.flags.schedule),
|
|
12043
|
+
privateStore: str(parsed.flags["private-store"]),
|
|
11993
12044
|
yes: parsed.flags.yes === true
|
|
11994
12045
|
}, io);
|
|
11995
12046
|
case "init": return init({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sayren",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "sayren CLI — 호스팅 스토어프론트(init·pull·deploy)와 서드파티 앱(app init·dev·deploy·release)",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"esbuild": "^0.28.2",
|
|
17
|
-
"@sayren/store-sdk": "^0.30.
|
|
17
|
+
"@sayren/store-sdk": "^0.30.12"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@biomejs/biome": "^2.5.14",
|