@clize/clize 0.1.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/cli.js +197 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.js +52 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/context.js +51 -0
  8. package/dist/context.js.map +1 -0
  9. package/dist/core/addresses.js +84 -0
  10. package/dist/core/addresses.js.map +1 -0
  11. package/dist/core/dns.js +10 -0
  12. package/dist/core/dns.js.map +1 -0
  13. package/dist/core/domains.js +190 -0
  14. package/dist/core/domains.js.map +1 -0
  15. package/dist/core/email.js +100 -0
  16. package/dist/core/email.js.map +1 -0
  17. package/dist/core/handle.js +136 -0
  18. package/dist/core/handle.js.map +1 -0
  19. package/dist/core/setup.js +49 -0
  20. package/dist/core/setup.js.map +1 -0
  21. package/dist/core/site.js +57 -0
  22. package/dist/core/site.js.map +1 -0
  23. package/dist/core/sites.js +148 -0
  24. package/dist/core/sites.js.map +1 -0
  25. package/dist/index.js +54 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib/cloudflare.js +112 -0
  28. package/dist/lib/cloudflare.js.map +1 -0
  29. package/dist/lib/result.js +25 -0
  30. package/dist/lib/result.js.map +1 -0
  31. package/dist/lib/zone.js +36 -0
  32. package/dist/lib/zone.js.map +1 -0
  33. package/dist/providers/deploy/cloudflare.js +71 -0
  34. package/dist/providers/deploy/cloudflare.js.map +1 -0
  35. package/dist/providers/dns/cloudflare.js +43 -0
  36. package/dist/providers/dns/cloudflare.js.map +1 -0
  37. package/dist/providers/email/cloudflare-inbound.js +216 -0
  38. package/dist/providers/email/cloudflare-inbound.js.map +1 -0
  39. package/dist/providers/email/cloudflare-outbound.js +65 -0
  40. package/dist/providers/email/cloudflare-outbound.js.map +1 -0
  41. package/dist/providers/email/resend.js +62 -0
  42. package/dist/providers/email/resend.js.map +1 -0
  43. package/dist/providers/email/ses.js +74 -0
  44. package/dist/providers/email/ses.js.map +1 -0
  45. package/dist/providers/index.js +33 -0
  46. package/dist/providers/index.js.map +1 -0
  47. package/dist/providers/registrar/cloudflare.js +78 -0
  48. package/dist/providers/registrar/cloudflare.js.map +1 -0
  49. package/dist/selfcheck.js +49 -0
  50. package/dist/selfcheck.js.map +1 -0
  51. package/dist/state/file-store.js +194 -0
  52. package/dist/state/file-store.js.map +1 -0
  53. package/dist/state/store.js +2 -0
  54. package/dist/state/store.js.map +1 -0
  55. package/dist/tools/deploy.js +25 -0
  56. package/dist/tools/deploy.js.map +1 -0
  57. package/dist/tools/dns.js +25 -0
  58. package/dist/tools/dns.js.map +1 -0
  59. package/dist/tools/domains.js +58 -0
  60. package/dist/tools/domains.js.map +1 -0
  61. package/dist/tools/email.js +34 -0
  62. package/dist/tools/email.js.map +1 -0
  63. package/dist/types/index.js +7 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/package.json +45 -0
@@ -0,0 +1,190 @@
1
+ export function searchDomains(ctx, domains) {
2
+ return ctx.providers.registrar.checkAvailability(domains);
3
+ }
4
+ export function listDomains(ctx) {
5
+ return ctx.store.listIdentities(ctx.tenantId);
6
+ }
7
+ export async function getDomain(ctx, domain) {
8
+ const id = await ctx.store.getIdentity(ctx.tenantId, domain);
9
+ if (!id)
10
+ throw new Error(`未找到域名 ${domain}(尚未注册或纳入管理)`);
11
+ return id;
12
+ }
13
+ export async function registerDomain(ctx, domain, opts = {}) {
14
+ // 默认开启自动续费:规避 Cloudflare Registrar(beta)暂无「续费 API」的问题,
15
+ // 由 Cloudflare 在到期前自动续费(扣账户支付方式),避免域名过期。
16
+ const autoRenew = opts.autoRenew ?? true;
17
+ // 1) 查价 + 可用性
18
+ const [check] = await ctx.providers.registrar.checkAvailability([domain]);
19
+ if (!check?.available)
20
+ throw new Error(`域名 ${domain} 不可注册${check?.reason ? `(${check.reason})` : ""}`);
21
+ const cost = check.registrationCost;
22
+ // 2) 月度开销上限(按租户)
23
+ if (ctx.tenant.monthlySpendLimitUsd != null && cost != null) {
24
+ const already = await ctx.store.spentThisMonth(ctx.tenantId);
25
+ if (already + cost > ctx.tenant.monthlySpendLimitUsd)
26
+ throw new Error(`超出月度开销上限:本月已花 $${already.toFixed(2)},本次需 $${cost.toFixed(2)},上限 $${ctx.tenant.monthlySpendLimitUsd}。可调整 MONTHLY_SPEND_LIMIT_USD。`);
27
+ }
28
+ // 3) 演练模式
29
+ if (ctx.tenant.dryRun) {
30
+ await ctx.store.appendAudit(ctx.tenantId, {
31
+ action: "domain_register",
32
+ detail: { domain, autoRenew },
33
+ cost,
34
+ dryRun: true,
35
+ });
36
+ await ctx.store.upsertIdentity(ctx.tenantId, {
37
+ domain,
38
+ status: "pending",
39
+ notes: "DRY_RUN 演练,未真正注册",
40
+ });
41
+ return {
42
+ dryRun: true,
43
+ domain,
44
+ wouldCost: cost,
45
+ currency: check.currency,
46
+ autoRenew,
47
+ message: "演练模式:未真正注册。设 DRY_RUN=false 后执行。",
48
+ };
49
+ }
50
+ // 4) 真实注册
51
+ const result = await ctx.providers.registrar.register(domain, { autoRenew });
52
+ await ctx.store.appendAudit(ctx.tenantId, {
53
+ action: "domain_register",
54
+ detail: { domain, autoRenew, state: result.state },
55
+ cost,
56
+ });
57
+ // 5) 注册成功后查 zoneId 并写入注册表
58
+ const zoneId = await ctx.cf.findZoneId(domain).catch(() => undefined);
59
+ await ctx.store.upsertIdentity(ctx.tenantId, {
60
+ domain,
61
+ status: result.completed ? "registered" : "pending",
62
+ zoneId,
63
+ registeredAt: new Date().toISOString(),
64
+ expiresAt: result.expiresAt,
65
+ autoRenew,
66
+ });
67
+ return { ...result, cost, currency: check.currency, zoneId, autoRenew };
68
+ }
69
+ /**
70
+ * 接入「已有域名」—— 迁 NS 模式(BYOD)。
71
+ * 在平台 CF 账户为该域名建 zone,把 CF 分配的 nameserver 交给用户去原注册商修改;
72
+ * NS 生效(zone active)后,收发信 / 部署全部复用现有能力。
73
+ * 幂等:可反复运行推进状态(未建→建 zone 给 NS;pending→提示等待;active→完成)。
74
+ *
75
+ * ⚠️ 迁 NS 会接管该域名「全部 DNS」,非仅邮箱。建 full zone 时 CF 会尽量自动导入现有记录,
76
+ * 但用户仍应在改 NS 前用 `dns list` 核对网站 / 邮箱 / 验证记录是否齐全(轻量 MVP:扫描 + 强提示)。
77
+ */
78
+ export async function importDomain(ctx, domain) {
79
+ // 1) 是否已在本 CF 账户托管
80
+ const existingZoneId = await ctx.cf.findZoneId(domain);
81
+ if (existingZoneId) {
82
+ const zone = await ctx.cf.getZone(existingZoneId);
83
+ await ctx.store.upsertIdentity(ctx.tenantId, {
84
+ domain,
85
+ zoneId: zone.id,
86
+ status: "external",
87
+ nameservers: zone.name_servers,
88
+ });
89
+ if (zone.status === "active") {
90
+ return {
91
+ domain,
92
+ zoneId: zone.id,
93
+ status: "active",
94
+ message: `✅ 该域名已在 Cloudflare 且 NS 已生效,可直接 \`clize email setup ${domain}\` 配收发信、\`clize site deploy\` 部署。`,
95
+ };
96
+ }
97
+ return {
98
+ domain,
99
+ zoneId: zone.id,
100
+ status: zone.status,
101
+ nameservers: zone.name_servers,
102
+ actionRequired: `去你的域名注册商把 NS 改成上面的 nameservers,改完反复跑 \`clize domain import ${domain}\` 直到 status 变 active。`,
103
+ warning: `迁 NS 会接管该域名全部 DNS。改 NS 前先 \`clize dns list ${domain}\` 核对网站 / 邮箱 / 验证记录是否齐全,缺的用 \`clize dns set\` 补。`,
104
+ };
105
+ }
106
+ // 2) 不在 CF:需新建 zone 接入(演练模式只说明,不真正建)
107
+ if (ctx.tenant.dryRun) {
108
+ return {
109
+ dryRun: true,
110
+ domain,
111
+ message: "演练模式:将为该域名在 CF 创建 zone 并返回需配置的 nameserver。设 DRY_RUN=false 后执行。",
112
+ };
113
+ }
114
+ const zone = await ctx.cf.createZone(domain);
115
+ await ctx.store.appendAudit(ctx.tenantId, {
116
+ action: "domain_import",
117
+ detail: { domain, zoneId: zone.id },
118
+ });
119
+ await ctx.store.upsertIdentity(ctx.tenantId, {
120
+ domain,
121
+ zoneId: zone.id,
122
+ status: "external",
123
+ nameservers: zone.name_servers,
124
+ });
125
+ return {
126
+ domain,
127
+ zoneId: zone.id,
128
+ status: zone.status, // 通常 "pending"(等 NS 生效)
129
+ nameservers: zone.name_servers,
130
+ nextSteps: [
131
+ `1) 到你的域名注册商(GoDaddy / 阿里云 / Namecheap …)把 NS 改成上面的 nameservers。`,
132
+ `2) ⚠️ 迁 NS 会接管该域名全部 DNS。CF 已尝试自动导入现有记录,但请先 \`clize dns list ${domain}\` 核对网站 / 邮箱 / 验证记录是否齐全,缺的用 \`clize dns set\` 补,确认无误再改 NS。`,
133
+ `3) 改完 NS 后反复跑 \`clize domain import ${domain}\` 直到 status 变 active,即可 \`clize email setup ${domain}\` 配收发信。`,
134
+ ],
135
+ };
136
+ }
137
+ export async function workspaceStatus(ctx) {
138
+ const identities = await ctx.store.listIdentities(ctx.tenantId);
139
+ return {
140
+ config: {
141
+ cloudflareConfigured: ctx.cf.isConfigured(),
142
+ outboundProvider: ctx.tenant.outboundProvider,
143
+ resendConfigured: Boolean(ctx.platform.resend.apiKey),
144
+ dryRun: ctx.tenant.dryRun,
145
+ monthlySpendLimitUsd: ctx.tenant.monthlySpendLimitUsd,
146
+ },
147
+ spentThisMonthUsd: await ctx.store.spentThisMonth(ctx.tenantId),
148
+ domainCount: identities.length,
149
+ domains: identities.map((i) => ({
150
+ domain: i.domain,
151
+ status: i.status,
152
+ email: Boolean(i.email?.inboundReady || i.email?.outboundReady),
153
+ site: Boolean(i.site?.url),
154
+ })),
155
+ };
156
+ }
157
+ export function auditLog(ctx, limit = 20) {
158
+ return ctx.store.recentAudit(ctx.tenantId, limit);
159
+ }
160
+ /** 开启 / 关闭某域名的自动续费(防止到期失效)。 */
161
+ export async function setAutoRenew(ctx, domain, enabled) {
162
+ if (ctx.tenant.dryRun) {
163
+ await ctx.store.appendAudit(ctx.tenantId, {
164
+ action: "domain_autorenew",
165
+ detail: { domain, enabled },
166
+ dryRun: true,
167
+ });
168
+ return { dryRun: true, domain, autoRenew: enabled, message: "演练模式:未真正修改。" };
169
+ }
170
+ await ctx.providers.registrar.setAutoRenew(domain, enabled);
171
+ await ctx.store.appendAudit(ctx.tenantId, {
172
+ action: "domain_autorenew",
173
+ detail: { domain, enabled },
174
+ });
175
+ await ctx.store.upsertIdentity(ctx.tenantId, { domain, autoRenew: enabled });
176
+ return { domain, autoRenew: enabled, message: enabled ? "已开启自动续费" : "已关闭自动续费" };
177
+ }
178
+ /** 从注册商拉取真实已注册域名列表(含到期时间,用于防过期巡检)。 */
179
+ export function listRegistered(ctx) {
180
+ return ctx.providers.registrar.listRegistered();
181
+ }
182
+ /** 资产健康:工作空间总览 + 注册商处真实到期列表(防过期)。`status --assets` 用。 */
183
+ export async function assetsHealth(ctx) {
184
+ const [overview, registered] = await Promise.all([
185
+ workspaceStatus(ctx),
186
+ listRegistered(ctx).catch(() => []),
187
+ ]);
188
+ return { ...overview, registered };
189
+ }
190
+ //# sourceMappingURL=domains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.js","sourceRoot":"","sources":["../../src/core/domains.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { resolveZone } from "../lib/zone.js";
2
+ export async function setupEmail(ctx, domain) {
3
+ const zoneId = await resolveZone(ctx, domain);
4
+ // 收信:启用 Cloudflare Email Routing + 写入 MX/SPF
5
+ await ctx.providers.emailInbound.setup(domain, zoneId);
6
+ // 发信:创建并验证发信域名(zoneId 复用,避免 provider 自行解析)
7
+ let sending;
8
+ try {
9
+ sending = await ctx.providers.emailOutbound().setupSendingDomain(domain, zoneId);
10
+ }
11
+ catch (e) {
12
+ sending = { error: e instanceof Error ? e.message : String(e) };
13
+ }
14
+ await ctx.store.upsertIdentity(ctx.tenantId, {
15
+ domain,
16
+ email: {
17
+ inboundProvider: "cloudflare",
18
+ outboundProvider: ctx.tenant.outboundProvider,
19
+ inboundReady: true,
20
+ outboundReady: "verified" in sending ? sending.verified : false,
21
+ },
22
+ });
23
+ return {
24
+ domain,
25
+ inbound: "Cloudflare Email Routing 已启用(MX/SPF 已写入)",
26
+ outbound: sending,
27
+ note: "发信记录写入 DNS 后需等待验证;可稍后再次运行以触发验证。",
28
+ };
29
+ }
30
+ export async function sendEmail(ctx, msg) {
31
+ if (!msg.text && !msg.html)
32
+ throw new Error("text 或 html 至少提供一个");
33
+ if (ctx.tenant.dryRun) {
34
+ await ctx.store.appendAudit(ctx.tenantId, {
35
+ action: "email_send",
36
+ detail: { from: msg.from, to: msg.to, subject: msg.subject },
37
+ dryRun: true,
38
+ });
39
+ return { dryRun: true, message: "演练模式:未真正发送。设 DRY_RUN=false 后执行。" };
40
+ }
41
+ const res = await ctx.providers.emailOutbound().send(msg);
42
+ await ctx.store.appendAudit(ctx.tenantId, {
43
+ action: "email_send",
44
+ detail: { from: msg.from, to: msg.to, subject: msg.subject, id: res.id },
45
+ });
46
+ return res;
47
+ }
48
+ export function checkInbox(ctx, domain, limit) {
49
+ return ctx.providers.emailInbound.checkInbox(domain, { limit });
50
+ }
51
+ /** 读取单封邮件全文(含正文 text/html)。 */
52
+ export function getMessage(ctx, domain, id) {
53
+ return ctx.providers.emailInbound.getMessage(domain, id);
54
+ }
55
+ /** 配置转发规则:把发往 from(如 support@hello.clize.app)的邮件转发到 to。 */
56
+ export async function routeEmail(ctx, from, to) {
57
+ const domain = from.split("@")[1];
58
+ if (!domain)
59
+ throw new Error(`无效地址:${from}(应形如 support@hello.clize.app)`);
60
+ const zoneId = await resolveZone(ctx, domain);
61
+ const result = await ctx.providers.emailInbound.addRoute({ zoneId, from, to });
62
+ await ctx.store.appendAudit(ctx.tenantId, {
63
+ action: "email_route",
64
+ detail: { from, to, ruleId: result.ruleId },
65
+ });
66
+ return { ...result };
67
+ }
68
+ /** 读某联系人的往来:从该域收件箱里筛出与 contact(对方地址)相关的信,按时间正序。
69
+ * 注:目前是入站为主(出站 send 暂未单独存档);"整条对话"的我方已发部分待 sent 存储补齐。 */
70
+ export async function emailThread(ctx, domain, contact) {
71
+ const all = await checkInbox(ctx, domain, 200);
72
+ const c = contact.toLowerCase();
73
+ return all
74
+ .filter((m) => (m.from || "").toLowerCase().includes(c) || (m.to || "").toLowerCase().includes(c))
75
+ .sort((a, b) => (a.receivedAt || "").localeCompare(b.receivedAt || ""));
76
+ }
77
+ /** 配置某地址的入站 webhook(来信 POST 到 url)。重部署含 webhook 逻辑的 worker + 写 KV 配置。 */
78
+ export async function setEmailWebhook(ctx, address, url) {
79
+ await setupInbox(ctx, address); // 确保(重)部署含 webhook 逻辑的 worker + 路由
80
+ await ctx.providers.emailInbound.setWebhook(address, url);
81
+ await ctx.store.appendAudit(ctx.tenantId, { action: "email_webhook", detail: { address, url } });
82
+ return { address, webhook: url, message: `来信 ${address} 将 POST 信封到 ${url}` };
83
+ }
84
+ /** 开启 agent 自持收信:把发往 address 的邮件捕获到 agent 收件箱(Email Worker + KV),可选同时转发。 */
85
+ export async function setupInbox(ctx, address, forwardTo) {
86
+ const domain = address.split("@")[1];
87
+ if (!domain)
88
+ throw new Error(`无效地址:${address}(应形如 support@hello.clize.app)`);
89
+ const zoneId = await resolveZone(ctx, domain);
90
+ const result = await ctx.providers.emailInbound.setupInbox({ address, zoneId, forwardTo });
91
+ await ctx.store.appendAudit(ctx.tenantId, {
92
+ action: "email_inbox_setup",
93
+ detail: { address, forwardTo },
94
+ });
95
+ return {
96
+ ...result,
97
+ message: `已开启 agent 收信:发往 ${address} 的邮件将存入收件箱,用 \`clize email inbox ${domain}\` 读取${forwardTo ? `;同时转发到 ${forwardTo}` : ""}`,
98
+ };
99
+ }
100
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/core/email.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import { setupInbox } from "./email.js";
2
+ import { bindHandleSite } from "./sites.js";
3
+ /** 免费 handle 的根域。 */
4
+ const HANDLE_ROOT = process.env.CLIZE_HANDLE_ROOT || "clize.app";
5
+ /** slug 规则:小写字母数字 + 连字符,不首尾连字符,≤63。 */
6
+ function slugValid(s) {
7
+ return /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(s);
8
+ }
9
+ function recordsAt(ctx, zoneId, name) {
10
+ return ctx.cf.request("GET", `/zones/${zoneId}/dns_records?name=${encodeURIComponent(name)}`);
11
+ }
12
+ /**
13
+ * 给子域写 CF Email Routing 收信 DNS(MX + SPF)。
14
+ * 复制本 zone 现有的 `route*.mx.cloudflare.net` MX(目标/优先级全 zone 一致),
15
+ * **避开 `GET /email/routing/dns` 这个 API token 不认、只认 global key 的管理端点**。
16
+ * Email Routing 在 zone 级已启用(enable 是 zone 级,一次性),子域收信 = 子域 MX + 路由规则。
17
+ */
18
+ async function provisionSubdomainEmailDns(ctx, zoneId, fqdn) {
19
+ const all = await ctx.cf.request("GET", `/zones/${zoneId}/dns_records?type=MX&per_page=100`);
20
+ const seen = new Set();
21
+ const targets = all
22
+ .filter((r) => /\.mx\.cloudflare\.net$/i.test(r.content))
23
+ .filter((r) => {
24
+ const k = `${r.content}:${r.priority}`;
25
+ if (seen.has(k))
26
+ return false;
27
+ seen.add(k);
28
+ return true;
29
+ });
30
+ if (!targets.length)
31
+ throw new Error("该 zone 没有现成 CF Email Routing MX 可复制(可能未在 zone 级启用 Email Routing)");
32
+ for (const t of targets) {
33
+ await ctx.cf
34
+ .request("POST", `/zones/${zoneId}/dns_records`, {
35
+ type: "MX",
36
+ name: fqdn,
37
+ content: t.content,
38
+ priority: t.priority ?? 10,
39
+ ttl: 1,
40
+ })
41
+ .catch(() => { }); // 已存在忽略
42
+ }
43
+ await ctx.cf
44
+ .request("POST", `/zones/${zoneId}/dns_records`, {
45
+ type: "TXT",
46
+ name: fqdn,
47
+ content: "v=spf1 include:_spf.mx.cloudflare.net ~all",
48
+ ttl: 1,
49
+ })
50
+ .catch(() => { });
51
+ }
52
+ /** 占一个免费 handle。先到先得:站点名或 claim 标记已存在即被占。默认自动开 hi@ 收信。 */
53
+ export async function claimHandle(ctx, slug, opts) {
54
+ const s = slug.toLowerCase();
55
+ if (!slugValid(s))
56
+ throw new Error(`无效 handle:${slug}(只能小写字母 / 数字 / 连字符,且不首尾连字符)`);
57
+ const fqdn = `${s}.${HANDLE_ROOT}`;
58
+ const marker = `_clize-handle.${fqdn}`; // 标记放在子名,避免与未来站点 CNAME 冲突
59
+ const zoneId = await ctx.cf.findZoneId(HANDLE_ROOT);
60
+ if (!zoneId)
61
+ throw new Error(`${HANDLE_ROOT} 不在当前 CF 账户,无法分配免费 handle`);
62
+ const [siteRecs, markerRecs] = await Promise.all([
63
+ recordsAt(ctx, zoneId, fqdn),
64
+ recordsAt(ctx, zoneId, marker),
65
+ ]);
66
+ if (siteRecs.length || markerRecs.length) {
67
+ const mine = markerRecs.find((r) => r.content.includes(`owner:${ctx.tenantId}`));
68
+ if (mine)
69
+ return { handle: fqdn, status: "already-yours", site: `https://${fqdn}`, email: `hi@${fqdn}` };
70
+ const alts = [`${s}-hq`, `get${s}`, `${s}app`, `${s}io`];
71
+ throw new Error(`${fqdn} 已被占。可用替代试试:${alts.join(" / ")}`);
72
+ }
73
+ if (ctx.tenant.dryRun) {
74
+ return {
75
+ dryRun: true,
76
+ handle: fqdn,
77
+ message: "演练:可占用。设 DRY_RUN=false 后执行。",
78
+ site: `https://${fqdn}`,
79
+ email: `hi@${fqdn}`,
80
+ };
81
+ }
82
+ // 占用:写 claim 标记 TXT;记一条免费 handle 身份。
83
+ await ctx.cf.request("POST", `/zones/${zoneId}/dns_records`, {
84
+ type: "TXT",
85
+ name: marker,
86
+ content: `clize-handle owner:${ctx.tenantId} ts:${new Date().toISOString()}`,
87
+ ttl: 3600,
88
+ });
89
+ await ctx.store.upsertIdentity(ctx.tenantId, {
90
+ domain: fqdn,
91
+ status: "external",
92
+ notes: "免费 handle(clize.app 子域)",
93
+ });
94
+ await ctx.store.appendAudit(ctx.tenantId, { action: "handle_claim", detail: { handle: fqdn } });
95
+ // 自动开邮箱:子域收信链路(MX/SPF)+ agent 自持收件箱(共享 worker + 该地址路由)。
96
+ // best-effort —— 失败不影响"占名"已成功。
97
+ const emailAddress = `hi@${fqdn}`;
98
+ const email = { address: emailAddress };
99
+ if (opts?.provisionEmail === false) {
100
+ email.status = "skipped";
101
+ }
102
+ else {
103
+ try {
104
+ await provisionSubdomainEmailDns(ctx, zoneId, fqdn);
105
+ await setupInbox(ctx, emailAddress);
106
+ email.status = "ready";
107
+ }
108
+ catch (e) {
109
+ email.status = "failed";
110
+ email.error = e instanceof Error ? e.message : String(e);
111
+ }
112
+ }
113
+ // 绑站点 worker → handle 立刻有占位页(没 deploy 也能访问);best-effort,失败不影响占名。
114
+ const site = { url: `https://${fqdn}` };
115
+ try {
116
+ await bindHandleSite(ctx, fqdn);
117
+ site.status = "ready(占位页;deploy 后即换成你的站)";
118
+ }
119
+ catch (e) {
120
+ site.status = "failed";
121
+ site.error = e instanceof Error ? e.message : String(e);
122
+ }
123
+ return {
124
+ handle: fqdn,
125
+ status: "claimed",
126
+ site,
127
+ email,
128
+ nextSteps: [
129
+ `部署站点:clize deploy <name> --domain ${fqdn}`,
130
+ email.status === "ready"
131
+ ? `收信已开:外部发到 ${emailAddress} → clize email inbox ${fqdn} 读取`
132
+ : `手动开收信:clize email setup ${fqdn} && clize email inbox-setup ${emailAddress}`,
133
+ ],
134
+ };
135
+ }
136
+ //# sourceMappingURL=handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/core/handle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ // 上手:login(保存凭证到本地)+ init(绑定当前项目)。
2
+ // 鉴权 M1 走"自带 token";login 把它存进 ~/.clize/config.json,免去每次配环境变量,
3
+ // 也让官网的 `clize login` / `clize init` 成为真命令。context.createLocalContext 会读这个文件。
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ export function configPath() {
8
+ return path.join(os.homedir(), ".clize", "config.json");
9
+ }
10
+ /** 保存访问凭证到 ~/.clize/config.json(token + 账户 id),仅本机可读。 */
11
+ export function saveLogin(token, account) {
12
+ const p = configPath();
13
+ fs.mkdirSync(path.dirname(p), { recursive: true });
14
+ let cfg = {};
15
+ try {
16
+ cfg = JSON.parse(fs.readFileSync(p, "utf8"));
17
+ }
18
+ catch {
19
+ /* 首次,新建 */
20
+ }
21
+ cfg.CLOUDFLARE_API_TOKEN = token;
22
+ if (account)
23
+ cfg.CLOUDFLARE_ACCOUNT_ID = account;
24
+ fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
25
+ try {
26
+ fs.chmodSync(p, 0o600);
27
+ }
28
+ catch {
29
+ /* 平台不支持则忽略 */
30
+ }
31
+ return { saved: p, message: "凭证已保存(仅本机可读)。跑 `clize check` 验证连通性。" };
32
+ }
33
+ /** 在当前目录绑定一个 clize 身份(写 ./clize.json)。 */
34
+ export function initProject(opts) {
35
+ const p = path.resolve("clize.json");
36
+ const proj = {
37
+ handle: opts.handle,
38
+ domain: opts.domain,
39
+ createdAt: new Date().toISOString(),
40
+ };
41
+ fs.writeFileSync(p, JSON.stringify(proj, null, 2));
42
+ const next = [];
43
+ if (!opts.handle && !opts.domain)
44
+ next.push("占个免费 handle:clize claim <slug>");
45
+ next.push("发上线:clize deploy ./site --domain <handle 或自定义域>");
46
+ next.push("搭客服:clize email address add support@<域> --tag 客服 --knowledge ./docs");
47
+ return { project: p, ...proj, nextSteps: next };
48
+ }
49
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/core/setup.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,+DAA+D;AAC/D,8EAA8E;AAC9E,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAC1D,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,OAAgB;IACvD,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,IAAI,GAAG,GAA2B,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAA2B,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,WAAW;IACb,CAAC;IACD,GAAG,CAAC,oBAAoB,GAAG,KAAK,CAAC;IACjC,IAAI,OAAO;QAAE,GAAG,CAAC,qBAAqB,GAAG,OAAO,CAAC;IACjD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC;AACtE,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,IAA0C;IACpE,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC9E,IAAI,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAC7D,IAAI,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;IACjF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { resolveZone } from "../lib/zone.js";
2
+ /** 部署站点(可选同时绑定自定义域名)。 */
3
+ export async function deploySite(ctx, opts) {
4
+ const result = await ctx.providers.deploy.deploySite(opts);
5
+ let customDomain;
6
+ if (opts.domain) {
7
+ const zoneId = await resolveZone(ctx, opts.domain);
8
+ await ctx.providers.deploy.bindDomain({
9
+ workerName: result.workerName,
10
+ hostname: opts.domain,
11
+ zoneId,
12
+ });
13
+ customDomain = opts.domain;
14
+ }
15
+ await ctx.store.appendAudit(ctx.tenantId, {
16
+ action: "site_deploy",
17
+ detail: { workerName: result.workerName, url: result.url, customDomain },
18
+ });
19
+ if (opts.domain) {
20
+ await ctx.store.upsertIdentity(ctx.tenantId, {
21
+ domain: opts.domain,
22
+ site: {
23
+ provider: "cloudflare",
24
+ workerName: result.workerName,
25
+ url: result.url,
26
+ customDomain,
27
+ deployedAt: result.deployedAt,
28
+ },
29
+ });
30
+ }
31
+ return {
32
+ ...result,
33
+ customDomain,
34
+ customDomainUrl: customDomain ? `https://${customDomain}` : undefined,
35
+ };
36
+ }
37
+ /** 给已部署服务绑定自定义域名。 */
38
+ export async function bindSite(ctx, workerName, domain) {
39
+ const zoneId = await resolveZone(ctx, domain);
40
+ await ctx.providers.deploy.bindDomain({ workerName, hostname: domain, zoneId });
41
+ await ctx.store.appendAudit(ctx.tenantId, { action: "site_bind", detail: { workerName, domain } });
42
+ const existing = (await ctx.store.getIdentity(ctx.tenantId, domain))?.site;
43
+ await ctx.store.upsertIdentity(ctx.tenantId, {
44
+ domain,
45
+ site: { provider: "cloudflare", ...existing, workerName, customDomain: domain },
46
+ });
47
+ return {
48
+ workerName,
49
+ domain,
50
+ url: `https://${domain}`,
51
+ message: "已绑定;DNS + TLS 证书自动配置中(可能需几分钟生效)",
52
+ };
53
+ }
54
+ export function siteLogs(ctx, workerName, limit) {
55
+ return ctx.providers.deploy.logs(workerName, { limit });
56
+ }
57
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../../src/core/site.ts"],"names":[],"mappings":"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"}