@notionx/create-notionx-app 1.0.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/README.md +139 -0
- package/dist/answers.js +332 -0
- package/dist/answers.js.map +1 -0
- package/dist/cli-notionx.js +388 -0
- package/dist/cli-notionx.js.map +1 -0
- package/dist/cli-notionx.test.js +277 -0
- package/dist/cli-notionx.test.js.map +1 -0
- package/dist/diff.js +40 -0
- package/dist/diff.js.map +1 -0
- package/dist/diff.test.js +90 -0
- package/dist/diff.test.js.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/locale-add/apply.js +39 -0
- package/dist/locale-add/apply.js.map +1 -0
- package/dist/locale-add/format.js +38 -0
- package/dist/locale-add/format.js.map +1 -0
- package/dist/locale-add/list.js +44 -0
- package/dist/locale-add/list.js.map +1 -0
- package/dist/locale-add/list.test.js +45 -0
- package/dist/locale-add/list.test.js.map +1 -0
- package/dist/locale-add/plan.js +128 -0
- package/dist/locale-add/plan.js.map +1 -0
- package/dist/locale-add/validate.js +46 -0
- package/dist/locale-add/validate.js.map +1 -0
- package/dist/metadata.js +41 -0
- package/dist/metadata.js.map +1 -0
- package/dist/notion-translation-sources/apply.js +61 -0
- package/dist/notion-translation-sources/apply.js.map +1 -0
- package/dist/notion-translation-sources/index.js +3 -0
- package/dist/notion-translation-sources/index.js.map +1 -0
- package/dist/notion-translation-sources/plan.js +33 -0
- package/dist/notion-translation-sources/plan.js.map +1 -0
- package/dist/notionx-source.js +142 -0
- package/dist/notionx-source.js.map +1 -0
- package/dist/notionx-source.test.js +144 -0
- package/dist/notionx-source.test.js.map +1 -0
- package/dist/password.js +18 -0
- package/dist/password.js.map +1 -0
- package/dist/presets.js +83 -0
- package/dist/presets.js.map +1 -0
- package/dist/presets.test.js +50 -0
- package/dist/presets.test.js.map +1 -0
- package/dist/prompt.js +218 -0
- package/dist/prompt.js.map +1 -0
- package/dist/provision/cloudflare.js +236 -0
- package/dist/provision/cloudflare.js.map +1 -0
- package/dist/provision/dependencies.js +219 -0
- package/dist/provision/dependencies.js.map +1 -0
- package/dist/provision/index.js +681 -0
- package/dist/provision/index.js.map +1 -0
- package/dist/provision/index.test.js +54 -0
- package/dist/provision/index.test.js.map +1 -0
- package/dist/provision/inspect.js +109 -0
- package/dist/provision/inspect.js.map +1 -0
- package/dist/provision/inspect.test.js +75 -0
- package/dist/provision/inspect.test.js.map +1 -0
- package/dist/provision/notion.js +1981 -0
- package/dist/provision/notion.js.map +1 -0
- package/dist/provision/notion.test.js +542 -0
- package/dist/provision/notion.test.js.map +1 -0
- package/dist/provision/ntn-credentials.js +198 -0
- package/dist/provision/ntn-credentials.js.map +1 -0
- package/dist/provision/options.js +15 -0
- package/dist/provision/options.js.map +1 -0
- package/dist/provision/password-hash.js +78 -0
- package/dist/provision/password-hash.js.map +1 -0
- package/dist/provision/prompts.js +115 -0
- package/dist/provision/prompts.js.map +1 -0
- package/dist/provision/repair.js +48 -0
- package/dist/provision/repair.js.map +1 -0
- package/dist/provision/repair.test.js +141 -0
- package/dist/provision/repair.test.js.map +1 -0
- package/dist/provision/shell.js +84 -0
- package/dist/provision/shell.js.map +1 -0
- package/dist/provision/wire.js +78 -0
- package/dist/provision/wire.js.map +1 -0
- package/dist/registry/doctor.js +181 -0
- package/dist/registry/doctor.js.map +1 -0
- package/dist/registry/doctor.test.js +180 -0
- package/dist/registry/doctor.test.js.map +1 -0
- package/dist/registry/install.js +217 -0
- package/dist/registry/install.js.map +1 -0
- package/dist/registry/install.test.js +168 -0
- package/dist/registry/install.test.js.map +1 -0
- package/dist/registry/load-registry.js +24 -0
- package/dist/registry/load-registry.js.map +1 -0
- package/dist/registry/load-registry.test.js +59 -0
- package/dist/registry/load-registry.test.js.map +1 -0
- package/dist/registry/migration-planner.js +204 -0
- package/dist/registry/migration-planner.js.map +1 -0
- package/dist/registry/migration-planner.test.js +340 -0
- package/dist/registry/migration-planner.test.js.map +1 -0
- package/dist/registry/migrations-store.js +125 -0
- package/dist/registry/migrations-store.js.map +1 -0
- package/dist/registry/migrations-store.test.js +163 -0
- package/dist/registry/migrations-store.test.js.map +1 -0
- package/dist/registry/migrations-types.js +25 -0
- package/dist/registry/migrations-types.js.map +1 -0
- package/dist/registry/project-meta.js +84 -0
- package/dist/registry/project-meta.js.map +1 -0
- package/dist/registry/registry-items.js +354 -0
- package/dist/registry/registry-items.js.map +1 -0
- package/dist/registry/registry-items.test.js +99 -0
- package/dist/registry/registry-items.test.js.map +1 -0
- package/dist/registry/registry-store.js +232 -0
- package/dist/registry/registry-store.js.map +1 -0
- package/dist/registry/registry-store.test.js +136 -0
- package/dist/registry/registry-store.test.js.map +1 -0
- package/dist/registry/registry-types.js +18 -0
- package/dist/registry/registry-types.js.map +1 -0
- package/dist/registry/registry-types.test.js +146 -0
- package/dist/registry/registry-types.test.js.map +1 -0
- package/dist/registry/render-content-source-files.js +158 -0
- package/dist/registry/render-content-source-files.js.map +1 -0
- package/dist/registry/render-multi-source.js +296 -0
- package/dist/registry/render-multi-source.js.map +1 -0
- package/dist/registry/render-multi-source.test.js +110 -0
- package/dist/registry/render-multi-source.test.js.map +1 -0
- package/dist/registry/text-utils.js +42 -0
- package/dist/registry/text-utils.js.map +1 -0
- package/dist/registry/uninstall.js +250 -0
- package/dist/registry/uninstall.js.map +1 -0
- package/dist/registry/uninstall.test.js +264 -0
- package/dist/registry/uninstall.test.js.map +1 -0
- package/dist/registry/update.js +280 -0
- package/dist/registry/update.js.map +1 -0
- package/dist/registry/update.test.js +229 -0
- package/dist/registry/update.test.js.map +1 -0
- package/dist/render.js +549 -0
- package/dist/render.js.map +1 -0
- package/dist/render.test.js +414 -0
- package/dist/render.test.js.map +1 -0
- package/dist/templates/.dev.vars.example.tmpl +32 -0
- package/dist/templates/.gitignore.tmpl +58 -0
- package/dist/templates/README.md.tmpl +417 -0
- package/dist/templates/app/[slug]/page.tsx.tmpl +55 -0
- package/dist/templates/app/admin/account/page.tsx.tmpl +18 -0
- package/dist/templates/app/admin/content-models/page.tsx.tmpl +6 -0
- package/dist/templates/app/admin/layout.tsx.tmpl +90 -0
- package/dist/templates/app/admin/loading.tsx.tmpl +6 -0
- package/dist/templates/app/admin/page.tsx.tmpl +17 -0
- package/dist/templates/app/api/auth/google/callback/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/google/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/verify-email/route.ts.tmpl +3 -0
- package/dist/templates/app/api/auth/viewer/route.ts.tmpl +3 -0
- package/dist/templates/app/api/health/route.ts.tmpl +3 -0
- package/dist/templates/app/api/{{contentSourceId}}/[slug]/route.ts.tmpl +27 -0
- package/dist/templates/app/api/{{contentSourceId}}/route.ts.tmpl +18 -0
- package/dist/templates/app/globals.css.tmpl +109 -0
- package/dist/templates/app/layout.tsx.tmpl +56 -0
- package/dist/templates/app/login/page.tsx.tmpl +154 -0
- package/dist/templates/app/page.fallback.tsx.tmpl +31 -0
- package/dist/templates/app/page.tsx.tmpl +42 -0
- package/dist/templates/app/register/page.tsx.tmpl +138 -0
- package/dist/templates/app/{{contentSourceListPath}}/[slug]/page.tsx.tmpl +113 -0
- package/dist/templates/app/{{contentSourceListPath}}/page.tsx.tmpl +74 -0
- package/dist/templates/components/content/post-card.tsx.tmpl +80 -0
- package/dist/templates/components/notion-blocks.tsx.tmpl +668 -0
- package/dist/templates/components/page-blocks/feature-grid-block.tsx.tmpl +68 -0
- package/dist/templates/components/page-blocks/hero-block.tsx.tmpl +73 -0
- package/dist/templates/components/page-blocks/latest-posts-block.tsx.tmpl +59 -0
- package/dist/templates/components/page-blocks/story-block.tsx.tmpl +70 -0
- package/dist/templates/components/page-blocks.fallback.tsx.tmpl +17 -0
- package/dist/templates/components/page-blocks.tsx.tmpl +32 -0
- package/dist/templates/components/search/search-dialog.tsx.tmpl +171 -0
- package/dist/templates/components/site/locale-switcher.tsx.tmpl +65 -0
- package/dist/templates/components/site/site-footer.tsx.tmpl +106 -0
- package/dist/templates/components/site/site-header.tsx.tmpl +80 -0
- package/dist/templates/components/site/site-shell.tsx.tmpl +20 -0
- package/dist/templates/components/site/theme-bootstrap.tsx.tmpl +51 -0
- package/dist/templates/components/theme-provider.tsx.tmpl +14 -0
- package/dist/templates/components/theme-toggle.tsx.tmpl +38 -0
- package/dist/templates/components/ui/accordion.tsx.tmpl +56 -0
- package/dist/templates/components/ui/alert.tsx.tmpl +59 -0
- package/dist/templates/components/ui/aspect-ratio.tsx.tmpl +8 -0
- package/dist/templates/components/ui/avatar.tsx.tmpl +44 -0
- package/dist/templates/components/ui/badge.tsx.tmpl +33 -0
- package/dist/templates/components/ui/button.tsx.tmpl +56 -0
- package/dist/templates/components/ui/card.tsx.tmpl +61 -0
- package/dist/templates/components/ui/checkbox.tsx.tmpl +28 -0
- package/dist/templates/components/ui/dialog.tsx.tmpl +104 -0
- package/dist/templates/components/ui/dropdown-menu.tsx.tmpl +183 -0
- package/dist/templates/components/ui/input.tsx.tmpl +21 -0
- package/dist/templates/components/ui/label.tsx.tmpl +25 -0
- package/dist/templates/components/ui/popover.tsx.tmpl +30 -0
- package/dist/templates/components/ui/radio-group.tsx.tmpl +44 -0
- package/dist/templates/components/ui/select.tsx.tmpl +150 -0
- package/dist/templates/components/ui/separator.tsx.tmpl +30 -0
- package/dist/templates/components/ui/sheet.tsx.tmpl +125 -0
- package/dist/templates/components/ui/skeleton.tsx.tmpl +15 -0
- package/dist/templates/components/ui/sonner.tsx.tmpl +30 -0
- package/dist/templates/components/ui/switch.tsx.tmpl +29 -0
- package/dist/templates/components/ui/table.tsx.tmpl +107 -0
- package/dist/templates/components/ui/tabs.tsx.tmpl +55 -0
- package/dist/templates/components/ui/textarea.tsx.tmpl +24 -0
- package/dist/templates/components/ui/tooltip.tsx.tmpl +30 -0
- package/dist/templates/components.json.tmpl +21 -0
- package/dist/templates/env.d.ts.tmpl +32 -0
- package/dist/templates/lib/admin/actions.ts.tmpl +43 -0
- package/dist/templates/lib/admin/context.tsx.tmpl +209 -0
- package/dist/templates/lib/admin/nav.ts.tmpl +23 -0
- package/dist/templates/lib/auth.config.fallback.ts.tmpl +10 -0
- package/dist/templates/lib/auth.config.ts.tmpl +45 -0
- package/dist/templates/lib/blocks/translations.ts.tmpl +44 -0
- package/dist/templates/lib/blog/translations.ts.tmpl +52 -0
- package/dist/templates/lib/content/models.ts.tmpl +53 -0
- package/dist/templates/lib/i18n/config.ts.tmpl +18 -0
- package/dist/templates/lib/i18n/index.ts.tmpl +1 -0
- package/dist/templates/lib/locale-contract/built-in.ts.tmpl +19 -0
- package/dist/templates/lib/locale-contract/index.ts.tmpl +3 -0
- package/dist/templates/lib/locale-contract/paths.ts.tmpl +29 -0
- package/dist/templates/lib/pages/model.ts.tmpl +16 -0
- package/dist/templates/lib/pages/source.ts.tmpl +566 -0
- package/dist/templates/lib/pages/translations.ts.tmpl +34 -0
- package/dist/templates/lib/search/config.fallback.ts.tmpl +11 -0
- package/dist/templates/lib/search/config.ts.tmpl +25 -0
- package/dist/templates/lib/site/config.ts.tmpl +120 -0
- package/dist/templates/lib/site/request-env.ts.tmpl +71 -0
- package/dist/templates/lib/site/settings.fallback.ts.tmpl +21 -0
- package/dist/templates/lib/site/settings.ts.tmpl +320 -0
- package/dist/templates/lib/site/translations.ts.tmpl +30 -0
- package/dist/templates/lib/utils.ts.tmpl +9 -0
- package/dist/templates/migrations/0001_init.sql.tmpl +57 -0
- package/dist/templates/migrations/0002_admin_seed.sql.tmpl +30 -0
- package/dist/templates/migrations/0003_search_index.sql.tmpl +29 -0
- package/dist/templates/next.config.ts.tmpl +18 -0
- package/dist/templates/package.json.tmpl +40 -0
- package/dist/templates/shims/cloudflare-workers-empty.mjs +4 -0
- package/dist/templates/shims/next-headers-empty.mjs +4 -0
- package/dist/templates/tests/smoke.test.ts.tmpl +83 -0
- package/dist/templates/tsconfig.json.tmpl +31 -0
- package/dist/templates/vite.config.ts.tmpl +53 -0
- package/dist/templates/vitest.config.ts.tmpl +13 -0
- package/dist/templates/worker/index.ts.tmpl +52 -0
- package/dist/templates/wrangler.jsonc.tmpl +44 -0
- package/dist/ui-presets.js +60 -0
- package/dist/ui-presets.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// packages/create-notionx-app/src/provision/ntn-credentials.ts
|
|
2
|
+
//
|
|
3
|
+
// Resolve a Notion API token from the `ntn` CLI's local credentials,
|
|
4
|
+
// so the scaffolder can skip the "paste your token" step for users
|
|
5
|
+
// who have already done `ntn login`.
|
|
6
|
+
//
|
|
7
|
+
// Where `ntn` stores its token:
|
|
8
|
+
//
|
|
9
|
+
// - macOS: Keychain (service "notion-cli"). We read it with
|
|
10
|
+
// `security find-generic-password -s notion-cli -w`.
|
|
11
|
+
// The user may be prompted by macOS to allow the
|
|
12
|
+
// first read in a session, but subsequent reads are
|
|
13
|
+
// silent.
|
|
14
|
+
// - Linux: GNOME Keyring / KWallet via `secret-tool`. The
|
|
15
|
+
// attribute pair is `service notion-cli`. We try
|
|
16
|
+
// `secret-tool` first and fall back to
|
|
17
|
+
// `~/.config/notion/auth.json`.
|
|
18
|
+
// - Windows: Credential Manager (`cmdkey /list`). We do not
|
|
19
|
+
// implement Windows keychain reads here — the
|
|
20
|
+
// file-based fallback applies when the user has set
|
|
21
|
+
// `NOTION_KEYRING=0`.
|
|
22
|
+
// - File mode: When the user has exported `NOTION_KEYRING=0`
|
|
23
|
+
// before running `ntn login`, ntn writes a JSON file
|
|
24
|
+
// to `~/.config/notion/auth.json` containing the
|
|
25
|
+
// active workspace's token. We read that.
|
|
26
|
+
//
|
|
27
|
+
// All three return paths are best-effort: if the local credential
|
|
28
|
+
// store does not contain a Notion token (or we cannot read it), the
|
|
29
|
+
// caller falls back to the manual `secret_…` paste prompt.
|
|
30
|
+
import { readFile } from "node:fs/promises";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { homedir, platform } from "node:os";
|
|
34
|
+
import { run, runNtn } from "./shell.js";
|
|
35
|
+
const KEYCHAIN_SERVICE = "notion-cli";
|
|
36
|
+
/**
|
|
37
|
+
* Try every well-known location in turn. Returns the first token that
|
|
38
|
+
* verifies as a live Notion API token, or `null` if nothing usable
|
|
39
|
+
* was found.
|
|
40
|
+
*/
|
|
41
|
+
export async function readNtnToken() {
|
|
42
|
+
// 0. Honor an explicit env override first. (This is the path
|
|
43
|
+
// `ntn` itself uses internally.)
|
|
44
|
+
if (process.env.NOTION_API_TOKEN && process.env.NOTION_API_TOKEN.length > 0) {
|
|
45
|
+
return { token: process.env.NOTION_API_TOKEN, source: "ntn-env" };
|
|
46
|
+
}
|
|
47
|
+
// 1. Try the platform-native credential store.
|
|
48
|
+
const native = await readFromNativeStore();
|
|
49
|
+
if (native)
|
|
50
|
+
return native;
|
|
51
|
+
// 2. Fall back to the file-based store ntn uses when the user has
|
|
52
|
+
// disabled the keyring via `NOTION_KEYRING=0`.
|
|
53
|
+
const fileBased = await readFromAuthJson();
|
|
54
|
+
if (fileBased)
|
|
55
|
+
return fileBased;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
async function readFromNativeStore() {
|
|
59
|
+
const p = platform();
|
|
60
|
+
if (p === "darwin") {
|
|
61
|
+
return readFromMacosKeychain();
|
|
62
|
+
}
|
|
63
|
+
if (p === "linux") {
|
|
64
|
+
return readFromLinuxKeyring();
|
|
65
|
+
}
|
|
66
|
+
// Windows: skip native store — not implemented.
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
async function readFromMacosKeychain() {
|
|
70
|
+
const r = await run("security", [
|
|
71
|
+
"find-generic-password",
|
|
72
|
+
"-s",
|
|
73
|
+
KEYCHAIN_SERVICE,
|
|
74
|
+
"-w",
|
|
75
|
+
]);
|
|
76
|
+
if (r.code !== 0)
|
|
77
|
+
return null;
|
|
78
|
+
const token = r.stdout.trim();
|
|
79
|
+
if (!token)
|
|
80
|
+
return null;
|
|
81
|
+
// `ntn` stores its token in the account field as a UUID-like value,
|
|
82
|
+
// not in the password field. Some installations put it in the
|
|
83
|
+
// password — cover both.
|
|
84
|
+
if (looksLikeNotionToken(token)) {
|
|
85
|
+
return { token, source: "ntn-macos-keychain" };
|
|
86
|
+
}
|
|
87
|
+
const accountR = await run("security", [
|
|
88
|
+
"find-generic-password",
|
|
89
|
+
"-s",
|
|
90
|
+
KEYCHAIN_SERVICE,
|
|
91
|
+
"-g",
|
|
92
|
+
]);
|
|
93
|
+
const accountMatch = accountR.stdout.match(/"acct"<blob>="([^"]+)"/);
|
|
94
|
+
const account = accountMatch?.[1]?.trim();
|
|
95
|
+
if (account && looksLikeNotionToken(account)) {
|
|
96
|
+
return { token: account, source: "ntn-macos-keychain" };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
async function readFromLinuxKeyring() {
|
|
101
|
+
// `secret-tool` is the standard D-Bus Secret Service client on
|
|
102
|
+
// GNOME. It prompts the user the first time per session.
|
|
103
|
+
const r = await run("secret-tool", [
|
|
104
|
+
"lookup",
|
|
105
|
+
"service",
|
|
106
|
+
KEYCHAIN_SERVICE,
|
|
107
|
+
]);
|
|
108
|
+
if (r.code !== 0 || !r.stdout.trim())
|
|
109
|
+
return null;
|
|
110
|
+
const token = r.stdout.trim();
|
|
111
|
+
if (!looksLikeNotionToken(token))
|
|
112
|
+
return null;
|
|
113
|
+
return { token, source: "ntn-linux-keyring" };
|
|
114
|
+
}
|
|
115
|
+
async function readFromAuthJson() {
|
|
116
|
+
const file = join(homedir(), ".config", "notion", "auth.json");
|
|
117
|
+
if (!existsSync(file))
|
|
118
|
+
return null;
|
|
119
|
+
let raw;
|
|
120
|
+
try {
|
|
121
|
+
raw = await readFile(file, "utf8");
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// Flat schema: `{ "token": "ntn_…" }`
|
|
134
|
+
if (parsed.token && looksLikeNotionToken(parsed.token)) {
|
|
135
|
+
return { token: parsed.token, source: "ntn-auth-json" };
|
|
136
|
+
}
|
|
137
|
+
// Nested schema: `{ workspaces: { prod: { <id>: { token, name } } } }`
|
|
138
|
+
if (parsed.workspaces) {
|
|
139
|
+
for (const [envKey, wsMap] of Object.entries(parsed.workspaces)) {
|
|
140
|
+
if (!wsMap)
|
|
141
|
+
continue;
|
|
142
|
+
// Prefer the default workspace id, fall back to the first
|
|
143
|
+
// entry that has a token.
|
|
144
|
+
const preferredId = parsed.defaultWorkspaceIds?.[envKey] ??
|
|
145
|
+
parsed.defaultWorkspaceId ??
|
|
146
|
+
Object.keys(wsMap)[0];
|
|
147
|
+
const ordered = [
|
|
148
|
+
wsMap[preferredId],
|
|
149
|
+
...Object.entries(wsMap)
|
|
150
|
+
.filter(([id]) => id !== preferredId)
|
|
151
|
+
.map(([, v]) => v),
|
|
152
|
+
];
|
|
153
|
+
for (const entry of ordered) {
|
|
154
|
+
if (entry?.token && looksLikeNotionToken(entry.token)) {
|
|
155
|
+
return {
|
|
156
|
+
token: entry.token,
|
|
157
|
+
source: "ntn-auth-json",
|
|
158
|
+
workspace: entry.name,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Best-effort: ask the `ntn` CLI to confirm that *some* credentials
|
|
168
|
+
* are present. We use this to give a clear "Run `ntn login` first"
|
|
169
|
+
* hint when we fail to read the token directly.
|
|
170
|
+
*/
|
|
171
|
+
export async function isNtnLoggedIn() {
|
|
172
|
+
// `ntn whoami` is read-only but still calls libuv's
|
|
173
|
+
// `uv_tty_init` on startup, so we keep the PTY-aware wrapper for
|
|
174
|
+
// it to actually exit 0 on hosts where the libuv TTY dance is
|
|
175
|
+
// strict.
|
|
176
|
+
const r = await runNtn(["whoami"]);
|
|
177
|
+
return r.code === 0;
|
|
178
|
+
}
|
|
179
|
+
function looksLikeNotionToken(s) {
|
|
180
|
+
// Notion integration tokens start with "secret_", "ntn_", or
|
|
181
|
+
// (for OAuth public integrations) a UUID-like bearer. The CLI
|
|
182
|
+
// stores its own OAuth token, so ntn_… is the common shape.
|
|
183
|
+
return /^(secret_[A-Za-z0-9]{20,}|ntn_[A-Za-z0-9]{20,}|[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,})$/.test(s);
|
|
184
|
+
}
|
|
185
|
+
/** Human-friendly source label for the status card. */
|
|
186
|
+
export function describeNtnSource(source) {
|
|
187
|
+
switch (source) {
|
|
188
|
+
case "ntn-macos-keychain":
|
|
189
|
+
return "macOS Keychain (service=notion-cli)";
|
|
190
|
+
case "ntn-linux-keyring":
|
|
191
|
+
return "Linux Secret Service (service=notion-cli)";
|
|
192
|
+
case "ntn-auth-json":
|
|
193
|
+
return "~/.config/notion/auth.json";
|
|
194
|
+
case "ntn-env":
|
|
195
|
+
return "NOTION_API_TOKEN env var";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=ntn-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ntn-credentials.js","sourceRoot":"","sources":["../../src/provision/ntn-credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function defaultProvisionMode(name) {
|
|
2
|
+
if (name === "repair") {
|
|
3
|
+
return {
|
|
4
|
+
name,
|
|
5
|
+
deploy: false,
|
|
6
|
+
allowRemoteMigrations: false,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
name,
|
|
11
|
+
deploy: true,
|
|
12
|
+
allowRemoteMigrations: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/provision/options.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,oBAAoB,CAAC,IAAuB;IAC1D,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,KAAK;YACb,qBAAqB,EAAE,KAAK;SAC7B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,IAAI;QACZ,qBAAqB,EAAE,IAAI;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// packages/create-notionx-app/src/provision/password-hash.ts
|
|
2
|
+
//
|
|
3
|
+
// Hashes the admin password at scaffold time so it can be baked into
|
|
4
|
+
// `migrations/0002_admin_seed.sql` and verified by `@notionx/core`'s
|
|
5
|
+
// `verifyPassword` on the first admin login.
|
|
6
|
+
//
|
|
7
|
+
// Why a local copy instead of importing from `@notionx/core`:
|
|
8
|
+
// - The scaffolder is a Node CLI, not a Cloudflare Worker. It must
|
|
9
|
+
// run on Node 22+ for the user, in a place where the runtime
|
|
10
|
+
// package may not be installed yet (e.g. in CI for a brand-new
|
|
11
|
+
// project that doesn't exist on disk).
|
|
12
|
+
// - PBKDF2-SHA256 is a 30-line algorithm. Keeping a local copy is
|
|
13
|
+
// simpler than wiring a workspace import + build dep.
|
|
14
|
+
// - The generated project imports the *canonical* implementation
|
|
15
|
+
// from `@notionx/core`; the only thing that matters is that the
|
|
16
|
+
// wire format matches so `verifyPassword` can decode it.
|
|
17
|
+
//
|
|
18
|
+
// Wire format (must stay in lockstep with
|
|
19
|
+
// `packages/notionx/src/auth/passwords.ts#hashPassword`):
|
|
20
|
+
//
|
|
21
|
+
// pbkdf2_sha256$<iterations>$<salt-base64>$<derived-key-base64>
|
|
22
|
+
//
|
|
23
|
+
// The `iterations` field is technically variable, but the runtime
|
|
24
|
+
// caps it at 100000 because Cloudflare Workers WebCrypto refuses
|
|
25
|
+
// higher counts. We bake 100000 in here so the seed row verifies
|
|
26
|
+
// in the same Workers environment the rest of the app runs in.
|
|
27
|
+
const HASH_PREFIX = "pbkdf2_sha256";
|
|
28
|
+
const PBKDF2_ITERATIONS = 100_000;
|
|
29
|
+
const SALT_BYTES = 16;
|
|
30
|
+
const DERIVED_KEY_BITS = 256;
|
|
31
|
+
function bytesToBase64(bytes) {
|
|
32
|
+
// Manual base64 because `Buffer` exists in Node 22 but we want
|
|
33
|
+
// this module to also work in any other env (e.g. edge test
|
|
34
|
+
// runners) that has `btoa` but not `Buffer`. `btoa` is available
|
|
35
|
+
// in Node 16+ and all modern browsers.
|
|
36
|
+
let bin = "";
|
|
37
|
+
for (const b of bytes)
|
|
38
|
+
bin += String.fromCharCode(b);
|
|
39
|
+
return btoa(bin);
|
|
40
|
+
}
|
|
41
|
+
async function deriveBits(password, salt, iterations) {
|
|
42
|
+
// Node 22 exposes `crypto.subtle` globally. The scaffolder's
|
|
43
|
+
// package.json already requires Node >= 22, so we don't need a
|
|
44
|
+
// `node:crypto` import here.
|
|
45
|
+
const enc = new TextEncoder();
|
|
46
|
+
const baseKey = await crypto.subtle.importKey("raw", enc.encode(password), "PBKDF2", false, ["deriveBits"]);
|
|
47
|
+
const derived = await crypto.subtle.deriveBits({
|
|
48
|
+
name: "PBKDF2",
|
|
49
|
+
hash: "SHA-256",
|
|
50
|
+
salt: salt,
|
|
51
|
+
iterations,
|
|
52
|
+
}, baseKey, DERIVED_KEY_BITS);
|
|
53
|
+
return new Uint8Array(derived);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Hash `password` the same way `@notionx/core`'s `hashPassword` does.
|
|
57
|
+
*
|
|
58
|
+
* The output is a single string suitable for direct interpolation
|
|
59
|
+
* into a SQL literal — e.g.
|
|
60
|
+
*
|
|
61
|
+
* `INSERT INTO users (email, password_hash) VALUES ('admin@example.com', '${hash}')`
|
|
62
|
+
*
|
|
63
|
+
* `render.ts` calls this exactly once per scaffold run (for the
|
|
64
|
+
* admin password) and bakes the result into a migration file. The
|
|
65
|
+
* 100k PBKDF2 iteration count takes ~200ms on a developer laptop —
|
|
66
|
+
* visible in the spinner, but not annoying.
|
|
67
|
+
*/
|
|
68
|
+
export async function hashPasswordForScaffold(password) {
|
|
69
|
+
const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
|
|
70
|
+
const derived = await deriveBits(password, salt, PBKDF2_ITERATIONS);
|
|
71
|
+
return [
|
|
72
|
+
HASH_PREFIX,
|
|
73
|
+
String(PBKDF2_ITERATIONS),
|
|
74
|
+
bytesToBase64(salt),
|
|
75
|
+
bytesToBase64(derived),
|
|
76
|
+
].join("$");
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=password-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-hash.js","sourceRoot":"","sources":["../../src/provision/password-hash.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,6CAA6C;AAC7C,EAAE;AACF,8DAA8D;AAC9D,qEAAqE;AACrE,iEAAiE;AACjE,mEAAmE;AACnE,2CAA2C;AAC3C,oEAAoE;AACpE,0DAA0D;AAC1D,mEAAmE;AACnE,oEAAoE;AACpE,6DAA6D;AAC7D,EAAE;AACF,0CAA0C;AAC1C,0DAA0D;AAC1D,EAAE;AACF,kEAAkE;AAClE,EAAE;AACF,kEAAkE;AAClE,iEAAiE;AACjE,iEAAiE;AACjE,+DAA+D;AAE/D,MAAM,WAAW,GAAG,eAAe,CAAC;AACpC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,SAAS,aAAa,CAAC,KAAiB;IACtC,+DAA+D;IAC/D,4DAA4D;IAC5D,iEAAiE;IACjE,uCAAuC;IACvC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,QAAgB,EAChB,IAAgB,EAChB,UAAkB;IAElB,6DAA6D;IAC7D,+DAA+D;IAC/D,6BAA6B;IAC7B,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAC3C,KAAK,EACL,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EACpB,QAAQ,EACR,KAAK,EACL,CAAC,YAAY,CAAC,CACf,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAC5C;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,IAA+B;QACrC,UAAU;KACX,EACD,OAAO,EACP,gBAAgB,CACjB,CAAC;IACF,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAgB;IAEhB,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACpE,OAAO;QACL,WAAW;QACX,MAAM,CAAC,iBAAiB,CAAC;QACzB,aAAa,CAAC,IAAI,CAAC;QACnB,aAAa,CAAC,OAAO,CAAC;KACvB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// packages/create-notionx-app/src/provision/prompts.ts
|
|
2
|
+
//
|
|
3
|
+
// Interactive (and silent) prompts for the post-render provisioning
|
|
4
|
+
// stage. Each prompt is a no-op when not running in a TTY (e.g. when
|
|
5
|
+
// the scaffolder is invoked with `--yes` or piped). Callers must
|
|
6
|
+
// tolerate `null` returns — they translate to "skip this step".
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
const SAFE_NON_TTY = null;
|
|
9
|
+
/** "Enable Resend email verification? (y/N)". Skipped silently if !interactive. */
|
|
10
|
+
export async function promptResend(ctx) {
|
|
11
|
+
if (!ctx.interactive)
|
|
12
|
+
return null;
|
|
13
|
+
const enable = await p.confirm({
|
|
14
|
+
message: "Enable Resend email verification? (no = auth falls back to no-op)",
|
|
15
|
+
initialValue: false,
|
|
16
|
+
});
|
|
17
|
+
if (p.isCancel(enable) || !enable)
|
|
18
|
+
return null;
|
|
19
|
+
const apiKey = await p.password({
|
|
20
|
+
message: "Resend API key (re_…)",
|
|
21
|
+
validate: (v) => !v || v.trim().length === 0 ? "Required when enabling Resend" : undefined,
|
|
22
|
+
});
|
|
23
|
+
if (p.isCancel(apiKey))
|
|
24
|
+
return null;
|
|
25
|
+
const fromAddress = await p.text({
|
|
26
|
+
message: "Resend sender address",
|
|
27
|
+
placeholder: "no-reply@example.com",
|
|
28
|
+
initialValue: "no-reply@example.com",
|
|
29
|
+
validate: (v) => (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v) ? undefined : "Must be an email address"),
|
|
30
|
+
});
|
|
31
|
+
if (p.isCancel(fromAddress))
|
|
32
|
+
return null;
|
|
33
|
+
return { apiKey: String(apiKey), fromAddress: String(fromAddress) };
|
|
34
|
+
}
|
|
35
|
+
/** "Enable Google sign-in? (y/N)". Skipped silently if !interactive. */
|
|
36
|
+
export async function promptGoogle(ctx, siteUrl) {
|
|
37
|
+
if (!ctx.interactive)
|
|
38
|
+
return null;
|
|
39
|
+
const enable = await p.confirm({
|
|
40
|
+
message: "Enable Google sign-in? (you'll need a Google Cloud OAuth client)",
|
|
41
|
+
initialValue: false,
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(enable) || !enable)
|
|
44
|
+
return null;
|
|
45
|
+
const redirectUri = `${siteUrl.replace(/\/$/, "")}/api/auth/google/callback`;
|
|
46
|
+
p.log.info(`Add this redirect URI to your Google OAuth client:\n ${redirectUri}`);
|
|
47
|
+
const clientId = await p.text({
|
|
48
|
+
message: "Google OAuth Client ID",
|
|
49
|
+
validate: (v) => (v && v.trim().length > 0 ? undefined : "Required"),
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(clientId))
|
|
52
|
+
return null;
|
|
53
|
+
const clientSecret = await p.password({
|
|
54
|
+
message: "Google OAuth Client Secret",
|
|
55
|
+
validate: (v) => (v && v.trim().length > 0 ? undefined : "Required"),
|
|
56
|
+
});
|
|
57
|
+
if (p.isCancel(clientSecret))
|
|
58
|
+
return null;
|
|
59
|
+
return {
|
|
60
|
+
clientId: String(clientId),
|
|
61
|
+
clientSecret: String(clientSecret),
|
|
62
|
+
redirectUri,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* "Wire up Notion now?" — triggered when no `NOTION_API_TOKEN` env var
|
|
67
|
+
* is set and we couldn't auto-detect credentials from `ntn`. The
|
|
68
|
+
* caller can pass a `preloadedToken` (e.g. read from the ntn CLI's
|
|
69
|
+
* local keychain) to skip the token prompt entirely. Returns the
|
|
70
|
+
* token + parent page id the user types in, or null to skip.
|
|
71
|
+
*/
|
|
72
|
+
export async function promptNotion(ctx, fields, preloadedToken, seedCount = 6) {
|
|
73
|
+
if (!ctx.interactive)
|
|
74
|
+
return null;
|
|
75
|
+
let apiToken = preloadedToken ?? "";
|
|
76
|
+
if (!apiToken) {
|
|
77
|
+
p.log.info("Notion: create an integration at https://www.notion.so/my-integrations, share a target page with it, then paste the token below. (Tip: run `ntn login` first and the scaffolder will pick up the token automatically.)");
|
|
78
|
+
const input = await p.password({
|
|
79
|
+
message: "Notion integration token (secret_…) — Enter to skip",
|
|
80
|
+
});
|
|
81
|
+
if (p.isCancel(input) || !input)
|
|
82
|
+
return null;
|
|
83
|
+
apiToken = String(input).trim();
|
|
84
|
+
}
|
|
85
|
+
p.log.info([
|
|
86
|
+
"Notion parent page:",
|
|
87
|
+
" 1. Create or choose a Notion page.",
|
|
88
|
+
" 2. Add the Notion CLI / integration connection to that page.",
|
|
89
|
+
" 3. Paste the page URL or page id below.",
|
|
90
|
+
"The scaffolder will create the blog database and 6 realistic sample posts under it.",
|
|
91
|
+
].join("\n"));
|
|
92
|
+
const parentPageId = await p.text({
|
|
93
|
+
message: "Parent page URL or id (the page your integration can edit)",
|
|
94
|
+
placeholder: "https://www.notion.so/workspace/Page-00000000000000000000000000000000",
|
|
95
|
+
initialValue: preloadedToken ? "" : undefined,
|
|
96
|
+
validate: (v) => {
|
|
97
|
+
const id = extractNotionPageId(v ?? "");
|
|
98
|
+
return id ? undefined : "Paste a Notion page URL or a 32-char page id";
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (p.isCancel(parentPageId))
|
|
102
|
+
return null;
|
|
103
|
+
return {
|
|
104
|
+
apiToken,
|
|
105
|
+
parentPageId: extractNotionPageId(String(parentPageId)),
|
|
106
|
+
seedCount,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function extractNotionPageId(value) {
|
|
110
|
+
const compact = value.trim().replace(/-/g, "");
|
|
111
|
+
const matches = compact.match(/[0-9a-fA-F]{32}/g);
|
|
112
|
+
return matches?.at(-1)?.toLowerCase() ?? null;
|
|
113
|
+
}
|
|
114
|
+
export const _internal = { SAFE_NON_TTY };
|
|
115
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/provision/prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DEFAULT_ANSWERS } from "../prompt.js";
|
|
2
|
+
import { provision } from "./index.js";
|
|
3
|
+
import { defaultProvisionMode } from "./options.js";
|
|
4
|
+
import { inspectProvisionRepair } from "./inspect.js";
|
|
5
|
+
export { inspectProvisionRepair } from "./inspect.js";
|
|
6
|
+
export function buildRepairAnswers(registry) {
|
|
7
|
+
const m = registry.manifest;
|
|
8
|
+
return {
|
|
9
|
+
projectName: m.projectName,
|
|
10
|
+
targetDir: process.cwd(),
|
|
11
|
+
defaultLocale: m.defaultLocale,
|
|
12
|
+
supportedLocales: [...m.supportedLocales],
|
|
13
|
+
notionxSource: m.notionxCore,
|
|
14
|
+
enableSiteSettings: m.enableSiteSettings,
|
|
15
|
+
enableBlocks: m.enableBlocks,
|
|
16
|
+
enableAuth: m.enableAuth,
|
|
17
|
+
enableAdmin: m.enableAdmin,
|
|
18
|
+
enablePages: m.enablePages,
|
|
19
|
+
enableSearch: m.enableSearch,
|
|
20
|
+
contentSource: {
|
|
21
|
+
id: m.contentSource.id,
|
|
22
|
+
title: m.contentSource.title,
|
|
23
|
+
fields: m.contentSource.fields.map((field) => ({
|
|
24
|
+
key: field.key,
|
|
25
|
+
notionName: field.notionName,
|
|
26
|
+
})),
|
|
27
|
+
},
|
|
28
|
+
adminEmail: DEFAULT_ANSWERS.adminEmail,
|
|
29
|
+
adminPassword: DEFAULT_ANSWERS.adminPassword,
|
|
30
|
+
notionParentPage: DEFAULT_ANSWERS.notionParentPage,
|
|
31
|
+
notionSeedCount: DEFAULT_ANSWERS.notionSeedCount,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function runProvisionRepair(registry, projectDir, answers = buildRepairAnswers(registry), options = {}) {
|
|
35
|
+
if (options.conflictChoice) {
|
|
36
|
+
const entries = await inspectProvisionRepair(projectDir);
|
|
37
|
+
const applicable = entries.filter((entry) => options.conflictChoice === "apply-all" ? true : entry.risk === "safe");
|
|
38
|
+
for (const entry of applicable) {
|
|
39
|
+
await entry.apply();
|
|
40
|
+
}
|
|
41
|
+
return { answers, appliedEntries: applicable };
|
|
42
|
+
}
|
|
43
|
+
return provision(answers, projectDir, {
|
|
44
|
+
interactive: false,
|
|
45
|
+
mode: defaultProvisionMode("repair"),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=repair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair.js","sourceRoot":"","sources":["../../src/provision/repair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,UAAU,kBAAkB,CAAC,QAAwB;IACzD,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;IAC5B,OAAO;QACL,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE;QACxB,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC;QACzC,aAAa,EAAE,CAAC,CAAC,WAAW;QAC5B,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;QACxC,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,aAAa,EAAE;YACb,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE;YACtB,KAAK,EAAE,CAAC,CAAC,aAAa,CAAC,KAAK;YAC5B,MAAM,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC7C,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;SACJ;QACD,UAAU,EAAE,eAAe,CAAC,UAAU;QACtC,aAAa,EAAE,eAAe,CAAC,aAAa;QAC5C,gBAAgB,EAAE,eAAe,CAAC,gBAAgB;QAClD,eAAe,EAAE,eAAe,CAAC,eAAe;KACjD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAwB,EACxB,UAAkB,EAClB,UAAmB,kBAAkB,CAAC,QAAQ,CAAC,EAC/C,UAA0D,EAAE;IAE5D,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1C,OAAO,CAAC,cAAc,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CACtE,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;IACjD,CAAC;IAED,OAAO,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE;QACpC,WAAW,EAAE,KAAK;QAClB,IAAI,EAAE,oBAAoB,CAAC,QAAQ,CAAC;KACrC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { defaultProvisionMode } from "./options.js";
|
|
3
|
+
import { inspectProvisionRepair, runProvisionRepair } from "./repair.js";
|
|
4
|
+
const provisionMock = vi.hoisted(() => vi.fn());
|
|
5
|
+
const inspectProvisionMock = vi.hoisted(() => vi.fn());
|
|
6
|
+
vi.mock("./index.js", async () => {
|
|
7
|
+
const actual = await vi.importActual("./index.js");
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
provision: provisionMock,
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
vi.mock("./inspect.js", async () => {
|
|
14
|
+
const actual = await vi.importActual("./inspect.js");
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
inspectProvisionRepair: inspectProvisionMock,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
const registry = {
|
|
21
|
+
manifest: {
|
|
22
|
+
$schema: "https://notionx.dev/schemas/registry.v2.json",
|
|
23
|
+
projectKind: "notionx",
|
|
24
|
+
projectName: "demo",
|
|
25
|
+
scaffoldVersion: "0.4.10",
|
|
26
|
+
notionxCore: "^0.1.2",
|
|
27
|
+
defaultLocale: "en",
|
|
28
|
+
supportedLocales: ["en"],
|
|
29
|
+
enableSiteSettings: true,
|
|
30
|
+
enableBlocks: true,
|
|
31
|
+
enableAuth: true,
|
|
32
|
+
enableAdmin: true,
|
|
33
|
+
enablePages: true,
|
|
34
|
+
enableSearch: true,
|
|
35
|
+
contentSource: {
|
|
36
|
+
id: "blog",
|
|
37
|
+
title: "Blog",
|
|
38
|
+
fields: [{ key: "title", notionName: "Name" }],
|
|
39
|
+
},
|
|
40
|
+
compat: { mode: "v2-native" },
|
|
41
|
+
registries: {},
|
|
42
|
+
installed: [],
|
|
43
|
+
managedFiles: { platform: [], bridge: [], user: [] },
|
|
44
|
+
},
|
|
45
|
+
managedFiles: { platform: [], bridge: [], user: [] },
|
|
46
|
+
};
|
|
47
|
+
const projectDir = "/tmp/demo";
|
|
48
|
+
describe("provision mode defaults", () => {
|
|
49
|
+
it("disables deploy for repair mode", () => {
|
|
50
|
+
expect(defaultProvisionMode("repair").deploy).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it("enables deploy for create mode", () => {
|
|
53
|
+
expect(defaultProvisionMode("create").deploy).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("runProvisionRepair", () => {
|
|
57
|
+
it("invokes provision in repair mode", async () => {
|
|
58
|
+
const answers = {
|
|
59
|
+
projectName: "demo",
|
|
60
|
+
targetDir: "./demo",
|
|
61
|
+
defaultLocale: "en",
|
|
62
|
+
supportedLocales: ["en"],
|
|
63
|
+
notionxSource: "^0.1.2",
|
|
64
|
+
enableSiteSettings: true,
|
|
65
|
+
enableBlocks: true,
|
|
66
|
+
enableAuth: true,
|
|
67
|
+
enableAdmin: true,
|
|
68
|
+
enablePages: true,
|
|
69
|
+
enableSearch: true,
|
|
70
|
+
contentSource: {
|
|
71
|
+
id: "blog",
|
|
72
|
+
title: "Blog",
|
|
73
|
+
fields: [{ key: "title", notionName: "Name" }],
|
|
74
|
+
},
|
|
75
|
+
adminEmail: "admin@example.com",
|
|
76
|
+
adminPassword: "ChangeMe1234",
|
|
77
|
+
notionParentPage: "",
|
|
78
|
+
notionSeedCount: 3,
|
|
79
|
+
};
|
|
80
|
+
provisionMock.mockResolvedValueOnce({ deploy: { skipped: true } });
|
|
81
|
+
await runProvisionRepair(registry, projectDir, answers);
|
|
82
|
+
expect(provisionMock).toHaveBeenCalledWith(answers, "/tmp/demo", expect.objectContaining({
|
|
83
|
+
interactive: false,
|
|
84
|
+
mode: expect.objectContaining({ name: "repair", deploy: false }),
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
it("can apply only safe inspected entries", async () => {
|
|
88
|
+
const safeApply = vi.fn();
|
|
89
|
+
const conflictApply = vi.fn();
|
|
90
|
+
inspectProvisionMock.mockResolvedValueOnce([
|
|
91
|
+
{
|
|
92
|
+
label: "cloudflare:add-var:VINEXT_KV_CACHE",
|
|
93
|
+
kind: "cloudflare",
|
|
94
|
+
group: "cloudflareBinding",
|
|
95
|
+
risk: "safe",
|
|
96
|
+
apply: safeApply,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: "notion:update-site-settings:Nav",
|
|
100
|
+
kind: "notion",
|
|
101
|
+
group: "notionContent",
|
|
102
|
+
risk: "conflict",
|
|
103
|
+
apply: conflictApply,
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
await runProvisionRepair(registry, projectDir, undefined, {
|
|
107
|
+
conflictChoice: "safe-only",
|
|
108
|
+
});
|
|
109
|
+
expect(safeApply).toHaveBeenCalledTimes(1);
|
|
110
|
+
expect(conflictApply).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe("inspectProvisionRepair", () => {
|
|
114
|
+
it("marks additive Notion schema repairs as safe", async () => {
|
|
115
|
+
inspectProvisionMock.mockResolvedValueOnce([
|
|
116
|
+
{
|
|
117
|
+
label: "notion:add-property:Count",
|
|
118
|
+
kind: "notion",
|
|
119
|
+
group: "notionContent",
|
|
120
|
+
risk: "safe",
|
|
121
|
+
apply: vi.fn(),
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
const entries = await inspectProvisionRepair(projectDir);
|
|
125
|
+
expect(entries[0]?.risk).toBe("safe");
|
|
126
|
+
});
|
|
127
|
+
it("marks populated site settings replacements as conflicts", async () => {
|
|
128
|
+
inspectProvisionMock.mockResolvedValueOnce([
|
|
129
|
+
{
|
|
130
|
+
label: "notion:update-site-settings:Nav",
|
|
131
|
+
kind: "notion",
|
|
132
|
+
group: "notionContent",
|
|
133
|
+
risk: "conflict",
|
|
134
|
+
apply: vi.fn(),
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
const entries = await inspectProvisionRepair(projectDir);
|
|
138
|
+
expect(entries[0]?.risk).toBe("conflict");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=repair.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair.test.js","sourceRoot":"","sources":["../../src/provision/repair.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEzE,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAChD,MAAM,oBAAoB,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAEvD,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;IAC/B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,YAAY,CAA8B,YAAY,CAAC,CAAC;IAChF,OAAO;QACL,GAAG,MAAM;QACT,SAAS,EAAE,aAAa;KACzB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;IACjC,MAAM,MAAM,GACV,MAAM,EAAE,CAAC,YAAY,CAAgC,cAAc,CAAC,CAAC;IACvE,OAAO;QACL,GAAG,MAAM;QACT,sBAAsB,EAAE,oBAAoB;KAC7C,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAmB;IAC/B,QAAQ,EAAE;QACR,OAAO,EAAE,8CAA8C;QACvD,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,MAAM;QACnB,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE,QAAQ;QACrB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,CAAC,IAAI,CAAC;QACxB,kBAAkB,EAAE,IAAI;QACxB,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,IAAI;QACjB,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE;YACb,EAAE,EAAE,MAAM;YACV,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;SAC/C;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;QAC7B,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE;QACb,YAAY,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;KACrD;IACD,YAAY,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;CACrD,CAAC;AAEF,MAAM,UAAU,GAAG,WAAW,CAAC;AAE/B,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,OAAO,GAAY;YACvB,WAAW,EAAE,MAAM;YACnB,SAAS,EAAE,QAAQ;YACnB,aAAa,EAAE,IAAI;YACnB,gBAAgB,EAAE,CAAC,IAAI,CAAC;YACxB,aAAa,EAAE,QAAQ;YACvB,kBAAkB,EAAE,IAAI;YACxB,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,IAAI;YACjB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE;gBACb,EAAE,EAAE,MAAM;gBACV,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;aAC/C;YACD,UAAU,EAAE,mBAAmB;YAC/B,aAAa,EAAE,cAAc;YAC7B,gBAAgB,EAAE,EAAE;YACpB,eAAe,EAAE,CAAC;SACnB,CAAC;QACF,aAAa,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAEnE,MAAM,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;QAExD,MAAM,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACxC,OAAO,EACP,WAAW,EACX,MAAM,CAAC,gBAAgB,CAAC;YACtB,WAAW,EAAE,KAAK;YAClB,IAAI,EAAE,MAAM,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;SACjE,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,oBAAoB,CAAC,qBAAqB,CAAC;YACzC;gBACE,KAAK,EAAE,oCAAoC;gBAC3C,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,mBAAmB;gBAC1B,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,SAAS;aACjB;YACD;gBACE,KAAK,EAAE,iCAAiC;gBACxC,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,eAAe;gBACtB,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,aAAa;aACrB;SACF,CAAC,CAAC;QAEH,MAAM,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE;YACxD,cAAc,EAAE,WAAW;SAC5B,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,oBAAoB,CAAC,qBAAqB,CAAC;YACzC;gBACE,KAAK,EAAE,2BAA2B;gBAClC,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,eAAe;gBACtB,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;aACf;SACF,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAC;QAEzD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,oBAAoB,CAAC,qBAAqB,CAAC;YACzC;gBACE,KAAK,EAAE,iCAAiC;gBACxC,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,eAAe;gBACtB,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;aACf;SACF,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAC;QAEzD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|