sayren 0.6.1 → 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.
Files changed (2) hide show
  1. package/dist/index.mjs +65 -14
  2. 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 `매니페스트 events에 있지만 이벤트 함수가 없습니다: ${i?.events} (HANDLER_MISSING)`;
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.6.1";
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 missing = manifest.events.filter((event) => !meta.events.includes(event));
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;
@@ -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
- if (url.pathname.startsWith("/_http/")) {
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: `${url.pathname.slice(6)}${url.search}`,
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} ${url.pathname}`, result, io);
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.6.1",
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.11"
17
+ "@sayren/store-sdk": "^0.30.12"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@biomejs/biome": "^2.5.14",