sayren 0.16.1 → 0.17.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.mjs +411 -50
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { APP_DEPLOY_KEY_PATTERN, APP_README_MAX_BYTES, APP_SECRET_PATTERNS, APP_SERVER_BUNDLE_MAX_GZIP_BYTES, APP_UI_MAX_BYTES, APP_UI_MAX_FILES, ApiError, ApprovalPendingError, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_SOURCE_BLOB_BATCH_MAX, STORE_CODE_PATTERN, appIconProblem, appManifestProblems, appManifestSchema, createStoreClient, normalizeStorefrontPath } from "@sayren/store-sdk";
|
|
6
6
|
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { createServer } from "node:http";
|
|
8
|
-
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
8
|
+
import { delimiter, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
import { gzipSync } from "node:zlib";
|
|
11
11
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -6621,6 +6621,42 @@ const cli_edit_session_open = ((inputs = {}, options = {}) => {
|
|
|
6621
6621
|
return ko_cli_edit_session_open(inputs);
|
|
6622
6622
|
});
|
|
6623
6623
|
|
|
6624
|
+
//#endregion
|
|
6625
|
+
//#region src/paraglide/messages/cli_env_approval.js
|
|
6626
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6627
|
+
/** @typedef {{}} Cli_Env_ApprovalInputs */
|
|
6628
|
+
const ko_cli_env_approval = () => {
|
|
6629
|
+
return `env 변경은 승인 대기입니다. 상점 플랫폼에서 승인하면 반영됩니다`;
|
|
6630
|
+
};
|
|
6631
|
+
/**
|
|
6632
|
+
|
|
6633
|
+
* @param {Cli_Env_ApprovalInputs} inputs
|
|
6634
|
+
* @param {{ locale?: "ko" }} options
|
|
6635
|
+
* @returns {LocalizedString}
|
|
6636
|
+
*/
|
|
6637
|
+
const cli_env_approval = ((inputs = {}, options = {}) => {
|
|
6638
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6639
|
+
return ko_cli_env_approval(inputs);
|
|
6640
|
+
});
|
|
6641
|
+
|
|
6642
|
+
//#endregion
|
|
6643
|
+
//#region src/paraglide/messages/cli_env_empty.js
|
|
6644
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6645
|
+
/** @typedef {{}} Cli_Env_EmptyInputs */
|
|
6646
|
+
const ko_cli_env_empty = () => {
|
|
6647
|
+
return `설정한 env가 없습니다. npx sayren env add <키> <값>으로 추가하십시오`;
|
|
6648
|
+
};
|
|
6649
|
+
/**
|
|
6650
|
+
|
|
6651
|
+
* @param {Cli_Env_EmptyInputs} inputs
|
|
6652
|
+
* @param {{ locale?: "ko" }} options
|
|
6653
|
+
* @returns {LocalizedString}
|
|
6654
|
+
*/
|
|
6655
|
+
const cli_env_empty = ((inputs = {}, options = {}) => {
|
|
6656
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6657
|
+
return ko_cli_env_empty(inputs);
|
|
6658
|
+
});
|
|
6659
|
+
|
|
6624
6660
|
//#endregion
|
|
6625
6661
|
//#region src/paraglide/messages/cli_env_not_linked.js
|
|
6626
6662
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -6639,6 +6675,24 @@ const cli_env_not_linked = ((inputs, options = {}) => {
|
|
|
6639
6675
|
return ko_cli_env_not_linked(inputs);
|
|
6640
6676
|
});
|
|
6641
6677
|
|
|
6678
|
+
//#endregion
|
|
6679
|
+
//#region src/paraglide/messages/cli_env_pull_skipped.js
|
|
6680
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6681
|
+
/** @typedef {{ keys: NonNullable<unknown> }} Cli_Env_Pull_SkippedInputs */
|
|
6682
|
+
const ko_cli_env_pull_skipped = (i) => {
|
|
6683
|
+
return `.env 한 줄로 같은 값을 쓸 수 없어 건너뛴 env가 있습니다: ${i?.keys}. 로컬에서는 직접 넣으십시오`;
|
|
6684
|
+
};
|
|
6685
|
+
/**
|
|
6686
|
+
|
|
6687
|
+
* @param {Cli_Env_Pull_SkippedInputs} inputs
|
|
6688
|
+
* @param {{ locale?: "ko" }} options
|
|
6689
|
+
* @returns {LocalizedString}
|
|
6690
|
+
*/
|
|
6691
|
+
const cli_env_pull_skipped = ((inputs, options = {}) => {
|
|
6692
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6693
|
+
return ko_cli_env_pull_skipped(inputs);
|
|
6694
|
+
});
|
|
6695
|
+
|
|
6642
6696
|
//#endregion
|
|
6643
6697
|
//#region src/paraglide/messages/cli_env_pulled.js
|
|
6644
6698
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -6657,6 +6711,78 @@ const cli_env_pulled = ((inputs, options = {}) => {
|
|
|
6657
6711
|
return ko_cli_env_pulled(inputs);
|
|
6658
6712
|
});
|
|
6659
6713
|
|
|
6714
|
+
//#endregion
|
|
6715
|
+
//#region src/paraglide/messages/cli_env_pulled_vars.js
|
|
6716
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6717
|
+
/** @typedef {{ file: NonNullable<unknown>, count: NonNullable<unknown> }} Cli_Env_Pulled_VarsInputs */
|
|
6718
|
+
const ko_cli_env_pulled_vars = (i) => {
|
|
6719
|
+
return `${i?.file}에 일반 env ${i?.count}개를 썼습니다(비밀 값은 배포된 사이트에만 들어가 받지 않습니다)`;
|
|
6720
|
+
};
|
|
6721
|
+
/**
|
|
6722
|
+
|
|
6723
|
+
* @param {Cli_Env_Pulled_VarsInputs} inputs
|
|
6724
|
+
* @param {{ locale?: "ko" }} options
|
|
6725
|
+
* @returns {LocalizedString}
|
|
6726
|
+
*/
|
|
6727
|
+
const cli_env_pulled_vars = ((inputs, options = {}) => {
|
|
6728
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6729
|
+
return ko_cli_env_pulled_vars(inputs);
|
|
6730
|
+
});
|
|
6731
|
+
|
|
6732
|
+
//#endregion
|
|
6733
|
+
//#region src/paraglide/messages/cli_env_removed.js
|
|
6734
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6735
|
+
/** @typedef {{ key: NonNullable<unknown> }} Cli_Env_RemovedInputs */
|
|
6736
|
+
const ko_cli_env_removed = (i) => {
|
|
6737
|
+
return `env ${i?.key}을(를) 지웠습니다. 올라가 있는 버전에서도 곧바로 빠집니다`;
|
|
6738
|
+
};
|
|
6739
|
+
/**
|
|
6740
|
+
|
|
6741
|
+
* @param {Cli_Env_RemovedInputs} inputs
|
|
6742
|
+
* @param {{ locale?: "ko" }} options
|
|
6743
|
+
* @returns {LocalizedString}
|
|
6744
|
+
*/
|
|
6745
|
+
const cli_env_removed = ((inputs, options = {}) => {
|
|
6746
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6747
|
+
return ko_cli_env_removed(inputs);
|
|
6748
|
+
});
|
|
6749
|
+
|
|
6750
|
+
//#endregion
|
|
6751
|
+
//#region src/paraglide/messages/cli_env_secret_mask.js
|
|
6752
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6753
|
+
/** @typedef {{}} Cli_Env_Secret_MaskInputs */
|
|
6754
|
+
const ko_cli_env_secret_mask = () => {
|
|
6755
|
+
return `(비밀 값)`;
|
|
6756
|
+
};
|
|
6757
|
+
/**
|
|
6758
|
+
|
|
6759
|
+
* @param {Cli_Env_Secret_MaskInputs} inputs
|
|
6760
|
+
* @param {{ locale?: "ko" }} options
|
|
6761
|
+
* @returns {LocalizedString}
|
|
6762
|
+
*/
|
|
6763
|
+
const cli_env_secret_mask = ((inputs = {}, options = {}) => {
|
|
6764
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6765
|
+
return ko_cli_env_secret_mask(inputs);
|
|
6766
|
+
});
|
|
6767
|
+
|
|
6768
|
+
//#endregion
|
|
6769
|
+
//#region src/paraglide/messages/cli_env_set.js
|
|
6770
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6771
|
+
/** @typedef {{ key: NonNullable<unknown> }} Cli_Env_SetInputs */
|
|
6772
|
+
const ko_cli_env_set = (i) => {
|
|
6773
|
+
return `env ${i?.key}을(를) 설정했습니다. 올라가 있는 버전에 곧바로 반영됩니다`;
|
|
6774
|
+
};
|
|
6775
|
+
/**
|
|
6776
|
+
|
|
6777
|
+
* @param {Cli_Env_SetInputs} inputs
|
|
6778
|
+
* @param {{ locale?: "ko" }} options
|
|
6779
|
+
* @returns {LocalizedString}
|
|
6780
|
+
*/
|
|
6781
|
+
const cli_env_set = ((inputs, options = {}) => {
|
|
6782
|
+
experimentalStaticLocale ?? options.locale ?? getLocale();
|
|
6783
|
+
return ko_cli_env_set(inputs);
|
|
6784
|
+
});
|
|
6785
|
+
|
|
6660
6786
|
//#endregion
|
|
6661
6787
|
//#region src/paraglide/messages/cli_env_store_code_unknown.js
|
|
6662
6788
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
@@ -6680,7 +6806,7 @@ const cli_env_store_code_unknown = ((inputs = {}, options = {}) => {
|
|
|
6680
6806
|
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
6681
6807
|
/** @typedef {{}} Cli_Env_UsageInputs */
|
|
6682
6808
|
const ko_cli_env_usage = () => {
|
|
6683
|
-
return `사용법: npx sayren env pull [--dir <폴더>]
|
|
6809
|
+
return `사용법: npx sayren env pull [--dir <폴더>] · env ls · env add <키> <값> [--secret] · env rm <키>`;
|
|
6684
6810
|
};
|
|
6685
6811
|
/**
|
|
6686
6812
|
|
|
@@ -9101,7 +9227,8 @@ const ko_cli_usage = (i) => {
|
|
|
9101
9227
|
npx sayren whoami
|
|
9102
9228
|
npx sayren create <이름> [--template <템플릿>] # 시작 코드를 새 폴더에 만듭니다(파일만, 로그인 없음)
|
|
9103
9229
|
npx sayren link [폴더] [--store <storeId>] # 폴더를 상점 사이트에 연결합니다(없으면 사이트를 만듭니다)
|
|
9104
|
-
npx sayren env pull # 연결된 상점
|
|
9230
|
+
npx sayren env pull # 연결된 상점 값·일반 환경 변수를 .env에 씁니다
|
|
9231
|
+
npx sayren env ls | add <키> <값> [--secret] | rm <키> # 사이트 환경 변수
|
|
9105
9232
|
npx sayren pull --project <storeId> [폴더] [--force [--yes]]
|
|
9106
9233
|
npx sayren deploy [--no-publish] [--skip-build] [--message <메모>] [--force [--yes]]
|
|
9107
9234
|
npx sayren status
|
|
@@ -9115,7 +9242,7 @@ const ko_cli_usage = (i) => {
|
|
|
9115
9242
|
whoami 쓰는 계정·토큰 종류·상점 범위
|
|
9116
9243
|
create 시작 코드(상점 이름·상품 목록 홈, SDK·문구·분석·에이전트 안내)를 <이름> 폴더에 만듭니다. 상점을 묻지 않고 호스팅 사이트도 만들지 않습니다
|
|
9117
9244
|
link 상점을 고르고(--store → 토큰 범위 → 선택) 사이트가 없으면 만든 뒤 .sayren/project.json·.env·.mcp.json을 씁니다
|
|
9118
|
-
env
|
|
9245
|
+
env pull: 연결된 상점의 SAYREN_STORE_CODE·SAYREN_API_URL과 일반 환경 변수를 .env에 씁니다(비밀 값 제외). ls·add·rm: 사이트 환경 변수(바꾸면 올라가 있는 버전에 곧바로 반영)
|
|
9119
9246
|
pull 호스팅 중인 사이트의 작업 트리를 폴더(기본: 상점 코드)로 받고 .mcp.json·.env·.sayren/project.json을 씁니다
|
|
9120
9247
|
deploy 로컬 검사 → 로컬 빌드 → 작업 트리 올리기 → 빌드 → 발행. 연결이 없으면 link부터 하고 다음 버전으로 교체합니다. 원격이 마지막 받기·올리기 이후 바뀌었으면 덮어쓰지 않습니다
|
|
9121
9248
|
status 사이트·발행 버전·최근 빌드·편집 환경
|
|
@@ -9136,6 +9263,7 @@ const ko_cli_usage = (i) => {
|
|
|
9136
9263
|
--force pull: 같은 사이트를 받은 폴더에 원격을 다시 받습니다(지울·덮을 파일을 보여 주고 묻습니다)
|
|
9137
9264
|
deploy: 원격 변경·열린 편집 환경을 무시하고 덮어씁니다(확인을 묻습니다)
|
|
9138
9265
|
--yes --force의 확인을 묻지 않습니다(비대화형)
|
|
9266
|
+
--secret env add: 비밀 값으로 둡니다(배포된 사이트에만 들어가고 값을 다시 볼 수 없습니다)
|
|
9139
9267
|
--no-publish deploy: 버전까지만 만들고 발행하지 않습니다
|
|
9140
9268
|
--skip-build deploy: 올리기 전 로컬 빌드(npm run build)를 건너뜁니다
|
|
9141
9269
|
--message <메모> deploy: 버전 메모
|
|
@@ -10070,7 +10198,7 @@ async function selectStore(auth, client, io, candidates, folderStore = null) {
|
|
|
10070
10198
|
|
|
10071
10199
|
//#endregion
|
|
10072
10200
|
//#region package.json
|
|
10073
|
-
var version = "0.
|
|
10201
|
+
var version = "0.17.1";
|
|
10074
10202
|
|
|
10075
10203
|
//#endregion
|
|
10076
10204
|
//#region src/client.ts
|
|
@@ -11086,11 +11214,114 @@ async function appMain(positional, flags, io) {
|
|
|
11086
11214
|
}
|
|
11087
11215
|
}
|
|
11088
11216
|
|
|
11217
|
+
//#endregion
|
|
11218
|
+
//#region ../../apps/hosting-sandbox/src/project-config.ts
|
|
11219
|
+
/**
|
|
11220
|
+
* 프로젝트 설정 `sayren.json`(#298 P1). 셀러 계약은 `package.json` 스크립트와 이 파일뿐이다 — 플랫폼은 프레임워크를 감지하거나 빌드를 보정하지 않는다.
|
|
11221
|
+
* - `buildCommand`: 빌드 명령. 없으면 `package.json`의 `build` 스크립트, 그것도 없으면 빌드하지 않는다(순수 HTML)
|
|
11222
|
+
* - `output.static`: 정적 파일 폴더(작업 트리 기준). `.`은 루트
|
|
11223
|
+
* - `output.server`: Web 표준 fetch 핸들러(`export default { fetch(request, env) }`)를 내보내는 ES 모듈. 없으면 정적 전용 사이트다
|
|
11224
|
+
*
|
|
11225
|
+
* 파일이 없으면 #298 이전 사이트(옛 basic·시작 코드)의 경로를 기본값으로 쓴다. 파일이 있으면 적힌 그대로다(`output.server`를 빼면 정적 전용).
|
|
11226
|
+
* 런타임 이름(Workers·wrangler)은 이 계약에 넣지 않는다 — 런타임을 옮겨도(#333) 이 파일은 그대로 둔다.
|
|
11227
|
+
* Node API를 쓰지 않는다(Sandbox Worker·CLI가 같이 쓴다).
|
|
11228
|
+
*/
|
|
11229
|
+
const PROJECT_CONFIG_FILE = "sayren.json";
|
|
11230
|
+
/** `sayren.json`이 없는 사이트의 기본값 — #298 이전 빌드가 쓰던 TanStack Start 출력 경로 */
|
|
11231
|
+
const LEGACY_PROJECT_CONFIG = {
|
|
11232
|
+
buildCommand: null,
|
|
11233
|
+
output: {
|
|
11234
|
+
static: "dist/client",
|
|
11235
|
+
server: "dist/server/server.js"
|
|
11236
|
+
}
|
|
11237
|
+
};
|
|
11238
|
+
const MAX_COMMAND_LENGTH = 1e3;
|
|
11239
|
+
const MAX_OUTPUT_PATH_LENGTH = 256;
|
|
11240
|
+
const OUTPUT_SEGMENT_RE = /^[A-Za-z0-9._@+-]+$/;
|
|
11241
|
+
/** 출력 경로 문제(없으면 null). 작업 트리 안의 상대 경로만 — 설치물·비밀 값 파일은 가리킬 수 없다 */
|
|
11242
|
+
function outputPathProblem(path, allowRoot) {
|
|
11243
|
+
if (path === ".") return allowRoot ? null : "루트(.)는 쓸 수 없습니다";
|
|
11244
|
+
if (path.length === 0 || path.length > MAX_OUTPUT_PATH_LENGTH) return "길이가 맞지 않습니다";
|
|
11245
|
+
if (path.startsWith("/")) return "작업 트리 기준 상대 경로여야 합니다";
|
|
11246
|
+
for (const seg of path.split("/")) {
|
|
11247
|
+
if (seg === "" || seg === "." || seg === "..") return "빈 조각·.·..은 쓸 수 없습니다";
|
|
11248
|
+
if (!OUTPUT_SEGMENT_RE.test(seg)) return "영문·숫자·._@+-만 쓸 수 있습니다";
|
|
11249
|
+
if (seg === "node_modules" || seg.startsWith(".env")) return "설치물·.env 파일은 가리킬 수 없습니다";
|
|
11250
|
+
}
|
|
11251
|
+
return null;
|
|
11252
|
+
}
|
|
11253
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11254
|
+
/** 트리(경로 → 내용)에서 `sayren.json` 내용을 꺼낸다. 없으면 null, UTF-8이 아니면 JSON 오류로 보이게 빈 문자열 */
|
|
11255
|
+
function projectConfigOf(files) {
|
|
11256
|
+
const bytes = files.get(PROJECT_CONFIG_FILE);
|
|
11257
|
+
if (!bytes) return null;
|
|
11258
|
+
try {
|
|
11259
|
+
return new TextDecoder("utf-8", {
|
|
11260
|
+
fatal: true,
|
|
11261
|
+
ignoreBOM: false
|
|
11262
|
+
}).decode(bytes);
|
|
11263
|
+
} catch {
|
|
11264
|
+
return "";
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
/** `sayren.json` 내용(없으면 null)을 읽는다. 모르는 키는 거절한다(오타가 조용히 기본값으로 빌드되지 않게) */
|
|
11268
|
+
function parseProjectConfig(content) {
|
|
11269
|
+
if (content === null) return {
|
|
11270
|
+
ok: true,
|
|
11271
|
+
config: LEGACY_PROJECT_CONFIG
|
|
11272
|
+
};
|
|
11273
|
+
const fail = (error) => ({
|
|
11274
|
+
ok: false,
|
|
11275
|
+
error: `${PROJECT_CONFIG_FILE}: ${error}`
|
|
11276
|
+
});
|
|
11277
|
+
let raw;
|
|
11278
|
+
try {
|
|
11279
|
+
raw = JSON.parse(content);
|
|
11280
|
+
} catch {
|
|
11281
|
+
return fail("JSON 형식이 아닙니다");
|
|
11282
|
+
}
|
|
11283
|
+
if (!isRecord(raw)) return fail("객체여야 합니다");
|
|
11284
|
+
const unknownKey = Object.keys(raw).find((k) => k !== "buildCommand" && k !== "output");
|
|
11285
|
+
if (unknownKey !== void 0) return fail(`모르는 키입니다: ${unknownKey}`);
|
|
11286
|
+
let buildCommand = null;
|
|
11287
|
+
if (raw.buildCommand !== void 0) {
|
|
11288
|
+
const cmd = raw.buildCommand;
|
|
11289
|
+
if (typeof cmd !== "string" || !cmd.trim()) return fail("buildCommand는 빈 문자열이 아니어야 합니다");
|
|
11290
|
+
if (cmd.length > MAX_COMMAND_LENGTH || cmd.includes("\0")) return fail(`buildCommand는 ${MAX_COMMAND_LENGTH}자 이하여야 합니다`);
|
|
11291
|
+
buildCommand = cmd;
|
|
11292
|
+
}
|
|
11293
|
+
const output = raw.output;
|
|
11294
|
+
if (!isRecord(output)) return fail("output.static이 필요합니다");
|
|
11295
|
+
const outKey = Object.keys(output).find((k) => k !== "static" && k !== "server");
|
|
11296
|
+
if (outKey !== void 0) return fail(`모르는 키입니다: output.${outKey}`);
|
|
11297
|
+
if (typeof output.static !== "string") return fail("output.static이 필요합니다");
|
|
11298
|
+
const staticProblem = outputPathProblem(output.static, true);
|
|
11299
|
+
if (staticProblem) return fail(`output.static — ${staticProblem}`);
|
|
11300
|
+
let server = null;
|
|
11301
|
+
if (output.server !== void 0) {
|
|
11302
|
+
if (typeof output.server !== "string") return fail("output.server는 문자열이어야 합니다");
|
|
11303
|
+
const serverProblem = outputPathProblem(output.server, false);
|
|
11304
|
+
if (serverProblem) return fail(`output.server — ${serverProblem}`);
|
|
11305
|
+
if (!/\.m?js$/.test(output.server)) return fail("output.server는 .js·.mjs 모듈이어야 합니다");
|
|
11306
|
+
server = output.server;
|
|
11307
|
+
}
|
|
11308
|
+
return {
|
|
11309
|
+
ok: true,
|
|
11310
|
+
config: {
|
|
11311
|
+
buildCommand,
|
|
11312
|
+
output: {
|
|
11313
|
+
static: output.static,
|
|
11314
|
+
server
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
};
|
|
11318
|
+
}
|
|
11319
|
+
|
|
11089
11320
|
//#endregion
|
|
11090
11321
|
//#region ../mcp/src/rules.ts
|
|
11091
|
-
/** 규칙의 심각도 — 없으면 `
|
|
11322
|
+
/** 규칙의 심각도 — 없으면 `warning` */
|
|
11092
11323
|
function severityOf(rule) {
|
|
11093
|
-
return rule?.severity ?? "
|
|
11324
|
+
return rule?.severity ?? "warning";
|
|
11094
11325
|
}
|
|
11095
11326
|
/** 문서 사이트 — `doc` 경로를 절대 주소로 만들 때 쓴다 */
|
|
11096
11327
|
const DOCS_BASE_URL = "https://docs.sayren.io";
|
|
@@ -11131,12 +11362,13 @@ const RULES = [
|
|
|
11131
11362
|
}),
|
|
11132
11363
|
defineRule({
|
|
11133
11364
|
id: "no-module-token",
|
|
11365
|
+
severity: "error",
|
|
11134
11366
|
title: mcp_rule_no_module_token_title,
|
|
11135
11367
|
why: mcp_rule_no_module_token_why,
|
|
11136
11368
|
fix: mcp_rule_no_module_token_fix,
|
|
11137
11369
|
example: mcp_rule_no_module_token_example,
|
|
11138
11370
|
doc: "/guides/storefront-auth",
|
|
11139
|
-
templateFile: "src/lib/
|
|
11371
|
+
templateFile: "src/lib/api.server.ts"
|
|
11140
11372
|
}),
|
|
11141
11373
|
defineRule({
|
|
11142
11374
|
id: "loader-first",
|
|
@@ -11145,7 +11377,7 @@ const RULES = [
|
|
|
11145
11377
|
fix: mcp_rule_loader_first_fix,
|
|
11146
11378
|
example: mcp_rule_loader_first_example,
|
|
11147
11379
|
doc: "/guides/mcp-customize",
|
|
11148
|
-
templateFile: "src/routes/
|
|
11380
|
+
templateFile: "src/routes/index.tsx"
|
|
11149
11381
|
}),
|
|
11150
11382
|
defineRule({
|
|
11151
11383
|
id: "payment-browser-launch",
|
|
@@ -11153,8 +11385,7 @@ const RULES = [
|
|
|
11153
11385
|
why: mcp_rule_payment_browser_launch_why,
|
|
11154
11386
|
fix: mcp_rule_payment_browser_launch_fix,
|
|
11155
11387
|
example: mcp_rule_payment_browser_launch_example,
|
|
11156
|
-
doc: "/guides/checkout-flow"
|
|
11157
|
-
templateFile: "src/lib/payments.ts"
|
|
11388
|
+
doc: "/guides/checkout-flow"
|
|
11158
11389
|
}),
|
|
11159
11390
|
defineRule({
|
|
11160
11391
|
id: "payment-return-route",
|
|
@@ -11162,8 +11393,7 @@ const RULES = [
|
|
|
11162
11393
|
why: mcp_rule_payment_return_route_why,
|
|
11163
11394
|
fix: mcp_rule_payment_return_route_fix,
|
|
11164
11395
|
example: "useEffect(() => {\n void payments.result().then((result) => {\n if (result.status === \"COMPLETED\") navigate({ to: \"/checkout/complete\", search: { orderId: result.orderId } });\n });\n}, []);",
|
|
11165
|
-
doc: "/guides/checkout-flow"
|
|
11166
|
-
templateFile: "src/routes/checkout.return.tsx"
|
|
11396
|
+
doc: "/guides/checkout-flow"
|
|
11167
11397
|
}),
|
|
11168
11398
|
defineRule({
|
|
11169
11399
|
id: "payment-processing-status",
|
|
@@ -11171,8 +11401,7 @@ const RULES = [
|
|
|
11171
11401
|
why: mcp_rule_payment_processing_status_why,
|
|
11172
11402
|
fix: mcp_rule_payment_processing_status_fix,
|
|
11173
11403
|
example: mcp_rule_payment_processing_status_example,
|
|
11174
|
-
doc: "/guides/checkout-flow"
|
|
11175
|
-
templateFile: "src/routes/checkout.return.tsx"
|
|
11404
|
+
doc: "/guides/checkout-flow"
|
|
11176
11405
|
}),
|
|
11177
11406
|
defineRule({
|
|
11178
11407
|
id: "payment-amounts-final",
|
|
@@ -11180,8 +11409,7 @@ const RULES = [
|
|
|
11180
11409
|
why: mcp_rule_payment_amounts_final_why,
|
|
11181
11410
|
fix: mcp_rule_payment_amounts_final_fix,
|
|
11182
11411
|
example: mcp_rule_payment_amounts_final_example,
|
|
11183
|
-
doc: "/guides/shipping-fees"
|
|
11184
|
-
templateFile: "src/routes/checkout.index.tsx"
|
|
11412
|
+
doc: "/guides/shipping-fees"
|
|
11185
11413
|
}),
|
|
11186
11414
|
defineRule({
|
|
11187
11415
|
id: "payment-retry",
|
|
@@ -11189,8 +11417,7 @@ const RULES = [
|
|
|
11189
11417
|
why: mcp_rule_payment_retry_why,
|
|
11190
11418
|
fix: mcp_rule_payment_retry_fix,
|
|
11191
11419
|
example: mcp_rule_payment_retry_example,
|
|
11192
|
-
doc: "/guides/checkout-flow"
|
|
11193
|
-
templateFile: "src/routes/checkout.return.tsx"
|
|
11420
|
+
doc: "/guides/checkout-flow"
|
|
11194
11421
|
}),
|
|
11195
11422
|
defineRule({
|
|
11196
11423
|
id: "checkout-origin",
|
|
@@ -11198,8 +11425,7 @@ const RULES = [
|
|
|
11198
11425
|
why: mcp_rule_checkout_origin_why,
|
|
11199
11426
|
fix: mcp_rule_checkout_origin_fix,
|
|
11200
11427
|
example: "const returnUrl = new URL(PAYMENT_RETURN_PATH, new URL(getRequestUrl()).origin);\nawait api.checkout.startPayment(checkoutId, { option, returnUrl: returnUrl.toString() });",
|
|
11201
|
-
doc: "/guides/payment-settings"
|
|
11202
|
-
templateFile: "src/routes/checkout.index.tsx"
|
|
11428
|
+
doc: "/guides/payment-settings"
|
|
11203
11429
|
}),
|
|
11204
11430
|
defineRule({
|
|
11205
11431
|
id: "delivery-quote-preview",
|
|
@@ -11207,11 +11433,11 @@ const RULES = [
|
|
|
11207
11433
|
why: mcp_rule_delivery_quote_preview_why,
|
|
11208
11434
|
fix: mcp_rule_delivery_quote_preview_fix,
|
|
11209
11435
|
example: mcp_rule_delivery_quote_preview_example,
|
|
11210
|
-
doc: "/guides/shipping-fees"
|
|
11211
|
-
templateFile: "src/routes/checkout.index.tsx"
|
|
11436
|
+
doc: "/guides/shipping-fees"
|
|
11212
11437
|
}),
|
|
11213
11438
|
defineRule({
|
|
11214
11439
|
id: "buyer-token-cookie",
|
|
11440
|
+
severity: "error",
|
|
11215
11441
|
title: mcp_rule_buyer_token_cookie_title,
|
|
11216
11442
|
why: mcp_rule_buyer_token_cookie_why,
|
|
11217
11443
|
fix: mcp_rule_buyer_token_cookie_fix,
|
|
@@ -11225,8 +11451,7 @@ const RULES = [
|
|
|
11225
11451
|
why: mcp_rule_buyer_session_refresh_why,
|
|
11226
11452
|
fix: mcp_rule_buyer_session_refresh_fix,
|
|
11227
11453
|
example: mcp_rule_buyer_session_refresh_example,
|
|
11228
|
-
doc: "/guides/storefront-auth"
|
|
11229
|
-
templateFile: "src/lib/session.server.ts"
|
|
11454
|
+
doc: "/guides/storefront-auth"
|
|
11230
11455
|
}),
|
|
11231
11456
|
defineRule({
|
|
11232
11457
|
id: "buyer-reconsent",
|
|
@@ -11234,8 +11459,7 @@ const RULES = [
|
|
|
11234
11459
|
why: mcp_rule_buyer_reconsent_why,
|
|
11235
11460
|
fix: mcp_rule_buyer_reconsent_fix,
|
|
11236
11461
|
example: mcp_rule_buyer_reconsent_example,
|
|
11237
|
-
doc: "/guides/storefront-auth"
|
|
11238
|
-
templateFile: "src/routes/account.tsx"
|
|
11462
|
+
doc: "/guides/storefront-auth"
|
|
11239
11463
|
}),
|
|
11240
11464
|
defineRule({
|
|
11241
11465
|
id: "buyer-signup",
|
|
@@ -11243,8 +11467,7 @@ const RULES = [
|
|
|
11243
11467
|
why: mcp_rule_buyer_signup_why,
|
|
11244
11468
|
fix: mcp_rule_buyer_signup_fix,
|
|
11245
11469
|
example: mcp_rule_buyer_signup_example,
|
|
11246
|
-
doc: "/guides/storefront-auth"
|
|
11247
|
-
templateFile: "src/routes/signup.tsx"
|
|
11470
|
+
doc: "/guides/storefront-auth"
|
|
11248
11471
|
}),
|
|
11249
11472
|
defineRule({
|
|
11250
11473
|
id: "buyer-signout",
|
|
@@ -11252,8 +11475,7 @@ const RULES = [
|
|
|
11252
11475
|
why: mcp_rule_buyer_signout_why,
|
|
11253
11476
|
fix: mcp_rule_buyer_signout_fix,
|
|
11254
11477
|
example: "const logout = createServerFn({ method: \"POST\" }).handler(async () => {\n const accessToken = readToken();\n if (accessToken) await authFor().signOut(accessToken).catch(() => undefined);\n clearSessionCookie();\n});",
|
|
11255
|
-
doc: "/guides/storefront-auth"
|
|
11256
|
-
templateFile: "src/routes/account.tsx"
|
|
11478
|
+
doc: "/guides/storefront-auth"
|
|
11257
11479
|
}),
|
|
11258
11480
|
defineRule({
|
|
11259
11481
|
id: "analytics-start",
|
|
@@ -11275,12 +11497,12 @@ const RULES = [
|
|
|
11275
11497
|
}),
|
|
11276
11498
|
defineRule({
|
|
11277
11499
|
id: "custom-input-private",
|
|
11500
|
+
severity: "error",
|
|
11278
11501
|
title: mcp_rule_custom_input_private_title,
|
|
11279
11502
|
why: mcp_rule_custom_input_private_why,
|
|
11280
11503
|
fix: mcp_rule_custom_input_private_fix,
|
|
11281
11504
|
example: mcp_rule_custom_input_private_example,
|
|
11282
|
-
doc: "/guides/product-options"
|
|
11283
|
-
templateFile: "src/lib/direct-checkout.server.ts"
|
|
11505
|
+
doc: "/guides/product-options"
|
|
11284
11506
|
}),
|
|
11285
11507
|
defineRule({
|
|
11286
11508
|
id: "checkout-availability",
|
|
@@ -11288,9 +11510,7 @@ const RULES = [
|
|
|
11288
11510
|
why: mcp_rule_checkout_availability_why,
|
|
11289
11511
|
fix: mcp_rule_checkout_availability_fix,
|
|
11290
11512
|
example: mcp_rule_checkout_availability_example,
|
|
11291
|
-
doc: "/guides/storefront-sdk"
|
|
11292
|
-
templateFile: "src/lib/ordering.ts",
|
|
11293
|
-
severity: "warning"
|
|
11513
|
+
doc: "/guides/storefront-sdk"
|
|
11294
11514
|
}),
|
|
11295
11515
|
defineRule({
|
|
11296
11516
|
id: "product-link",
|
|
@@ -11298,9 +11518,7 @@ const RULES = [
|
|
|
11298
11518
|
why: mcp_rule_product_link_why,
|
|
11299
11519
|
fix: mcp_rule_product_link_fix,
|
|
11300
11520
|
example: mcp_rule_product_link_example,
|
|
11301
|
-
doc: "/guides/order-numbers"
|
|
11302
|
-
templateFile: "src/lib/product-path.ts",
|
|
11303
|
-
severity: "warning"
|
|
11521
|
+
doc: "/guides/order-numbers"
|
|
11304
11522
|
}),
|
|
11305
11523
|
defineRule({
|
|
11306
11524
|
id: "server-only-import",
|
|
@@ -11368,7 +11586,7 @@ function verifySources(files, options = {}) {
|
|
|
11368
11586
|
detail
|
|
11369
11587
|
});
|
|
11370
11588
|
};
|
|
11371
|
-
/** 프로젝트 전체를 보고 판정한 위반 — 고칠
|
|
11589
|
+
/** 프로젝트 전체를 보고 판정한 위반 — 고칠 방법은 규칙의 `fix`, 시작 코드에 있는 자리는 `templateFile`이 알려 준다 */
|
|
11372
11590
|
const inProject = (ruleId, detail) => {
|
|
11373
11591
|
findings.push({
|
|
11374
11592
|
ruleId,
|
|
@@ -12095,6 +12313,8 @@ function runLocalChecks(tree) {
|
|
|
12095
12313
|
const report$1 = verifyTree(map);
|
|
12096
12314
|
const blockingRules = report$1.results.filter((result) => result.status === "violation" && result.severity !== "warning");
|
|
12097
12315
|
const problems = [...limitProblems(tree)];
|
|
12316
|
+
const project = parseProjectConfig(projectConfigOf(map));
|
|
12317
|
+
if (!project.ok) problems.push(project.error);
|
|
12098
12318
|
const warnings = [];
|
|
12099
12319
|
const text = (path) => {
|
|
12100
12320
|
const bytes = map.get(path);
|
|
@@ -12201,8 +12421,20 @@ function withGuideSkill(files) {
|
|
|
12201
12421
|
const OUTPUT_TAIL_LINES = 40;
|
|
12202
12422
|
/** 모아 두는 출력 상한(문자) — 긴 빌드 로그를 다 쥐고 있지 않는다 */
|
|
12203
12423
|
const OUTPUT_KEEP_CHARS = 64 * 1024;
|
|
12204
|
-
/**
|
|
12424
|
+
/**
|
|
12425
|
+
* 빌드를 돌릴 수 있는 폴더인가 — 서버 빌드와 같은 순서로 명령을 고른다(`sayren.json` buildCommand → `build` 스크립트).
|
|
12426
|
+
* `package.json`이 있으면 설치된 의존성(`node_modules`)이 있어야 한다
|
|
12427
|
+
*/
|
|
12205
12428
|
function planLocalBuild(dir) {
|
|
12429
|
+
const hasManifest = existsSync(join(dir, "package.json"));
|
|
12430
|
+
const command = buildCommandOf(dir);
|
|
12431
|
+
if (command !== null) {
|
|
12432
|
+
if (hasManifest && !existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
|
|
12433
|
+
return {
|
|
12434
|
+
kind: "run",
|
|
12435
|
+
command
|
|
12436
|
+
};
|
|
12437
|
+
}
|
|
12206
12438
|
let scripts;
|
|
12207
12439
|
try {
|
|
12208
12440
|
scripts = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).scripts;
|
|
@@ -12212,16 +12444,33 @@ function planLocalBuild(dir) {
|
|
|
12212
12444
|
const build = scripts && typeof scripts === "object" ? scripts.build : void 0;
|
|
12213
12445
|
if (typeof build !== "string" || !build.trim()) return { kind: "no-script" };
|
|
12214
12446
|
if (!existsSync(join(dir, "node_modules"))) return { kind: "no-deps" };
|
|
12215
|
-
return {
|
|
12447
|
+
return {
|
|
12448
|
+
kind: "run",
|
|
12449
|
+
command: null
|
|
12450
|
+
};
|
|
12216
12451
|
}
|
|
12217
|
-
/** `
|
|
12218
|
-
function
|
|
12452
|
+
/** `sayren.json`의 buildCommand. 파일이 없거나 형식이 틀리면 null(형식 오류는 로컬 검사가 먼저 막는다) */
|
|
12453
|
+
function buildCommandOf(dir) {
|
|
12454
|
+
let content;
|
|
12455
|
+
try {
|
|
12456
|
+
content = readFileSync(join(dir, PROJECT_CONFIG_FILE), "utf8");
|
|
12457
|
+
} catch {
|
|
12458
|
+
return null;
|
|
12459
|
+
}
|
|
12460
|
+
const parsed = parseProjectConfig(content);
|
|
12461
|
+
return parsed.ok ? parsed.config.buildCommand : null;
|
|
12462
|
+
}
|
|
12463
|
+
/**
|
|
12464
|
+
* 빌드를 돌린다 — `command`가 없으면 `npm run build`, 있으면 그 명령을 셸로(셀러가 자기 컴퓨터에서 적은 명령이다, `node_modules/.bin`을 PATH에 얹는다).
|
|
12465
|
+
* 출력은 모아 두고(실패할 때 꼬리를 보여 준다) 화면에 흘리지 않는다
|
|
12466
|
+
*/
|
|
12467
|
+
function runNpmBuild(dir, env, command = null) {
|
|
12219
12468
|
return new Promise((resolve$1, reject) => {
|
|
12220
12469
|
let output = "";
|
|
12221
12470
|
const keep = (chunk) => {
|
|
12222
12471
|
output = (output + chunk.toString("utf8")).slice(-OUTPUT_KEEP_CHARS);
|
|
12223
12472
|
};
|
|
12224
|
-
const child = spawn("npm", ["run", "build"], {
|
|
12473
|
+
const child = command === null ? spawn("npm", ["run", "build"], {
|
|
12225
12474
|
cwd: dir,
|
|
12226
12475
|
env,
|
|
12227
12476
|
stdio: [
|
|
@@ -12230,6 +12479,18 @@ function runNpmBuild(dir, env) {
|
|
|
12230
12479
|
"pipe"
|
|
12231
12480
|
],
|
|
12232
12481
|
shell: process.platform === "win32"
|
|
12482
|
+
}) : spawn(command, {
|
|
12483
|
+
cwd: dir,
|
|
12484
|
+
env: {
|
|
12485
|
+
...env,
|
|
12486
|
+
PATH: [join(dir, "node_modules", ".bin"), env.PATH].filter(Boolean).join(delimiter)
|
|
12487
|
+
},
|
|
12488
|
+
stdio: [
|
|
12489
|
+
"ignore",
|
|
12490
|
+
"pipe",
|
|
12491
|
+
"pipe"
|
|
12492
|
+
],
|
|
12493
|
+
shell: true
|
|
12233
12494
|
});
|
|
12234
12495
|
child.stdout?.on("data", keep);
|
|
12235
12496
|
child.stderr?.on("data", keep);
|
|
@@ -12602,14 +12863,96 @@ async function envPull(flags, io) {
|
|
|
12602
12863
|
store: state.storeId
|
|
12603
12864
|
}, dir, state, io);
|
|
12604
12865
|
const storeCode = await storeCodeOf(client, state.storeId);
|
|
12866
|
+
const lines = (await client.storefront.listVars().then((r) => r.vars.filter((v) => !v.secret)).catch((error) => {
|
|
12867
|
+
if (error instanceof ApiError && (error.status === 403 || error.status === 404)) return [];
|
|
12868
|
+
throw error;
|
|
12869
|
+
})).map((v) => ({
|
|
12870
|
+
key: v.key,
|
|
12871
|
+
line: dotenvValue(v.value ?? "")
|
|
12872
|
+
}));
|
|
12873
|
+
const skipped = lines.filter((l) => l.line === null).map((l) => l.key);
|
|
12605
12874
|
writeStoreEnv(dir, auth.apiOrigin, storeCode);
|
|
12875
|
+
updateEnvFile(dir, Object.fromEntries(lines.filter((l) => l.line !== null).map((l) => [l.key, l.line])));
|
|
12606
12876
|
ensureGitignore(dir);
|
|
12607
12877
|
io.out(cli_env_pulled({
|
|
12608
12878
|
file: ENV_FILE,
|
|
12609
12879
|
storeCode
|
|
12610
12880
|
}));
|
|
12881
|
+
if (lines.length > skipped.length) io.out(cli_env_pulled_vars({
|
|
12882
|
+
file: ENV_FILE,
|
|
12883
|
+
count: lines.length - skipped.length
|
|
12884
|
+
}));
|
|
12885
|
+
if (skipped.length > 0) io.err(cli_env_pull_skipped({ keys: skipped.join(", ") }));
|
|
12886
|
+
return 0;
|
|
12887
|
+
}
|
|
12888
|
+
/**
|
|
12889
|
+
* `.env` 한 줄의 값(dotenv + Vite `loadEnv`의 dotenv-expand 규칙) — `$`는 `\$`로 막아 확장되지 않게 한다. 안전한 문자만이면 그대로,
|
|
12890
|
+
* 작은따옴표·줄바꿈이 없으면 작은따옴표(안의 문자를 그대로 읽는다), 줄바꿈만 있고 큰따옴표가 없으면 큰따옴표(`\n`). 작은따옴표와 줄바꿈이
|
|
12891
|
+
* 같이 있거나 큰따옴표와 줄바꿈이 같이 있으면 같은 값으로 읽히게 쓸 수 없어 null이다(쓰지 않고 알린다)
|
|
12892
|
+
*/
|
|
12893
|
+
function dotenvValue(value) {
|
|
12894
|
+
const escaped = value.replaceAll("$", "\\$");
|
|
12895
|
+
if (/^[A-Za-z0-9_./:@+,=-]*$/.test(value)) return value;
|
|
12896
|
+
if (!value.includes("'") && !/[\r\n]/.test(value)) return `'${escaped}'`;
|
|
12897
|
+
if (!value.includes("\"") && !value.includes("\\")) return `"${escaped.replace(/\r?\n/g, "\\n")}"`;
|
|
12898
|
+
return null;
|
|
12899
|
+
}
|
|
12900
|
+
/** 연결된 폴더의 관리 API 클라이언트 — 셀러 env 명령(`env ls·add·rm`) */
|
|
12901
|
+
async function linkedClient(flags, io) {
|
|
12902
|
+
const dir = resolve(io.cwd, flags.dir ?? ".");
|
|
12903
|
+
migrateLegacy(dir, io.out);
|
|
12904
|
+
const state = readState(dir);
|
|
12905
|
+
if (!state) throw new CliError(cli_env_not_linked({ file: STATE_FILE }));
|
|
12906
|
+
return (await apiFor({
|
|
12907
|
+
...flags,
|
|
12908
|
+
store: state.storeId
|
|
12909
|
+
}, dir, state, io)).client;
|
|
12910
|
+
}
|
|
12911
|
+
/** `sayren env ls` — 셀러 env 목록(비밀 값은 값 없이) */
|
|
12912
|
+
async function envList(flags, io) {
|
|
12913
|
+
const { vars } = await (await linkedClient(flags, io)).storefront.listVars();
|
|
12914
|
+
if (vars.length === 0) {
|
|
12915
|
+
io.out(cli_env_empty());
|
|
12916
|
+
return 0;
|
|
12917
|
+
}
|
|
12918
|
+
for (const v of vars) io.out(`${v.key}=${v.secret ? cli_env_secret_mask() : v.value ?? ""}`);
|
|
12611
12919
|
return 0;
|
|
12612
12920
|
}
|
|
12921
|
+
/** `sayren env add <키> <값> [--secret]` — 넣거나 바꾼다. 에이전트 토큰이면 승인 대기다 */
|
|
12922
|
+
async function envAdd(flags, io) {
|
|
12923
|
+
if (!flags.key || flags.value === void 0) throw new CliError(cli_env_usage());
|
|
12924
|
+
const client = await linkedClient(flags, io);
|
|
12925
|
+
try {
|
|
12926
|
+
await client.storefront.putVar(flags.key, {
|
|
12927
|
+
value: flags.value,
|
|
12928
|
+
secret: flags.secret === true
|
|
12929
|
+
});
|
|
12930
|
+
} catch (error) {
|
|
12931
|
+
if (error instanceof ApprovalPendingError) return envApprovalPending(error, io);
|
|
12932
|
+
throw error;
|
|
12933
|
+
}
|
|
12934
|
+
io.out(cli_env_set({ key: flags.key }));
|
|
12935
|
+
return 0;
|
|
12936
|
+
}
|
|
12937
|
+
/** `sayren env rm <키>` */
|
|
12938
|
+
async function envRemove(flags, io) {
|
|
12939
|
+
if (!flags.key) throw new CliError(cli_env_usage());
|
|
12940
|
+
const client = await linkedClient(flags, io);
|
|
12941
|
+
try {
|
|
12942
|
+
await client.storefront.deleteVar(flags.key);
|
|
12943
|
+
} catch (error) {
|
|
12944
|
+
if (error instanceof ApprovalPendingError) return envApprovalPending(error, io);
|
|
12945
|
+
throw error;
|
|
12946
|
+
}
|
|
12947
|
+
io.out(cli_env_removed({ key: flags.key }));
|
|
12948
|
+
return 0;
|
|
12949
|
+
}
|
|
12950
|
+
function envApprovalPending(error, io) {
|
|
12951
|
+
io.out(cli_env_approval());
|
|
12952
|
+
if (error.approval.approvalUrl) io.out(cli_approval_url({ url: error.approval.approvalUrl }));
|
|
12953
|
+
else io.out(cli_approval_id({ id: error.approvalId }));
|
|
12954
|
+
return EXIT_APPROVAL_PENDING;
|
|
12955
|
+
}
|
|
12613
12956
|
async function verify(flags, io) {
|
|
12614
12957
|
const dir = resolve(io.cwd, flags.dir ?? ".");
|
|
12615
12958
|
const tree = collectTree(dir);
|
|
@@ -12775,7 +13118,7 @@ async function localBuild(dir, skip, io) {
|
|
|
12775
13118
|
const started = io.now?.() ?? Date.now();
|
|
12776
13119
|
let result;
|
|
12777
13120
|
try {
|
|
12778
|
-
result = await (io.runBuild ?? ((at) => runNpmBuild(at, io.env)))(dir);
|
|
13121
|
+
result = await (io.runBuild ?? ((at, command) => runNpmBuild(at, io.env, command)))(dir, plan.command);
|
|
12779
13122
|
} catch (error) {
|
|
12780
13123
|
throw new CliError(cli_deploy_build_local_spawn_failed({ message: error instanceof Error ? error.message : String(error) }));
|
|
12781
13124
|
}
|
|
@@ -13384,7 +13727,8 @@ const BOOL_FLAGS = new Set([
|
|
|
13384
13727
|
"version",
|
|
13385
13728
|
"device",
|
|
13386
13729
|
"paste",
|
|
13387
|
-
"tail"
|
|
13730
|
+
"tail",
|
|
13731
|
+
"secret"
|
|
13388
13732
|
]);
|
|
13389
13733
|
function parseArgs(argv) {
|
|
13390
13734
|
const out = {
|
|
@@ -13471,12 +13815,29 @@ async function main(argv, io) {
|
|
|
13471
13815
|
template: str(parsed.flags.template)
|
|
13472
13816
|
}, io);
|
|
13473
13817
|
case "link": return link(common, io);
|
|
13474
|
-
case "env":
|
|
13475
|
-
|
|
13476
|
-
return envPull({
|
|
13818
|
+
case "env": switch (parsed.positional[0]) {
|
|
13819
|
+
case "pull": return envPull({
|
|
13477
13820
|
...common,
|
|
13478
13821
|
dir: str(parsed.flags.dir) ?? parsed.positional[1]
|
|
13479
13822
|
}, io);
|
|
13823
|
+
case "ls": return envList({
|
|
13824
|
+
...common,
|
|
13825
|
+
dir: str(parsed.flags.dir)
|
|
13826
|
+
}, io);
|
|
13827
|
+
case "add": return envAdd({
|
|
13828
|
+
...common,
|
|
13829
|
+
dir: str(parsed.flags.dir),
|
|
13830
|
+
key: parsed.positional[1],
|
|
13831
|
+
value: parsed.positional[2],
|
|
13832
|
+
secret: parsed.flags.secret === true
|
|
13833
|
+
}, io);
|
|
13834
|
+
case "rm": return envRemove({
|
|
13835
|
+
...common,
|
|
13836
|
+
dir: str(parsed.flags.dir),
|
|
13837
|
+
key: parsed.positional[1]
|
|
13838
|
+
}, io);
|
|
13839
|
+
default: throw new CliError(cli_env_usage());
|
|
13840
|
+
}
|
|
13480
13841
|
case "init": return init();
|
|
13481
13842
|
case "pull": return pull({
|
|
13482
13843
|
...common,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sayren",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"imports": {
|
|
6
6
|
"#paraglide/*": "./src/paraglide/*"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"esbuild": "^0.28.2",
|
|
20
|
-
"@sayren/store-sdk": "^0.
|
|
20
|
+
"@sayren/store-sdk": "^0.59.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@biomejs/biome": "^2.5.14",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"tsx": "^4.20.3",
|
|
29
29
|
"typescript": "^6.0.3",
|
|
30
30
|
"vitest": "^5.0.1",
|
|
31
|
-
"@sayren/
|
|
32
|
-
"@sayren/
|
|
31
|
+
"@sayren/app": "0.9.0",
|
|
32
|
+
"@sayren/mcp": "0.31.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|