@getvitops/create 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/package.json +3 -2
- package/templates/emdash/.agents/skills/setup/SKILL.md +61 -0
- package/templates/emdash/AGENTS.md +19 -11
- package/templates/emdash/README.md +40 -42
- package/templates/emdash/SETUP.html +526 -0
- package/templates/emdash/_gitignore +3 -0
- package/templates/emdash/astro.config.mjs +7 -1
- package/templates/emdash/package.json +6 -4
- package/templates/emdash/seed/seed.json +1 -1
- package/templates/emdash/site.json +228 -0
- package/templates/emdash/vite.config.ts +1 -1
- package/templates/emdash/wrangler.jsonc +8 -3
- package/templates/static/.agents/skills/setup/SKILL.md +61 -0
- package/templates/static/AGENTS.md +14 -7
- package/templates/static/README.md +15 -20
- package/templates/static/SETUP.html +526 -0
- package/templates/static/_gitignore +3 -0
- package/templates/static/astro.config.mjs +5 -1
- package/templates/static/package.json +6 -4
- package/templates/static/site.json +228 -0
- package/templates/static/src/pages/index.astro +2 -2
- package/templates/static/wrangler.jsonc +10 -3
- package/templates/emdash/design-system.json +0 -148
- package/templates/emdash/scripts/init-github.mjs +0 -186
- package/templates/emdash/scripts/init-hosting.mjs +0 -150
- package/templates/static/scripts/init-github.mjs +0 -186
- package/templates/static/scripts/init-hosting.mjs +0 -62
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// One-time hosting-platform setup for the target configured in
|
|
2
|
-
// astro.config.mjs (vitopsHosting from @getvitops/emdash).
|
|
3
|
-
//
|
|
4
|
-
// pnpm run init:hosting # cloudflare (the default target)
|
|
5
|
-
// pnpm run init:hosting -- --dry-run
|
|
6
|
-
// pnpm run init:hosting -- --target node
|
|
7
|
-
//
|
|
8
|
-
// cloudflare: creates the isolated DEV resources (D1 database + R2 bucket —
|
|
9
|
-
// prod ones are auto-provisioned by wrangler on the first deploy) and, when a
|
|
10
|
-
// GitHub repo is wired up (see init-github.mjs), sets the repo secrets the
|
|
11
|
-
// deploy workflows need. The Cloudflare API token cannot be created via CLI,
|
|
12
|
-
// so that one secret stays manual.
|
|
13
|
-
//
|
|
14
|
-
// Idempotent: safe to re-run — existing resources and secrets are just
|
|
15
|
-
// updated/skipped. Requires wrangler auth (`npx wrangler login`).
|
|
16
|
-
|
|
17
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
18
|
-
import { readFileSync } from 'node:fs';
|
|
19
|
-
|
|
20
|
-
const args = process.argv.slice(2);
|
|
21
|
-
const dryRun = args.includes('--dry-run');
|
|
22
|
-
const targetFlag = args[args.indexOf('--target') + 1];
|
|
23
|
-
const target = (args.includes('--target') && targetFlag) || process.env.HOSTING || 'cloudflare';
|
|
24
|
-
|
|
25
|
-
const name = JSON.parse(readFileSync('package.json', 'utf8')).name.replace(/^@[^/]+\//, '');
|
|
26
|
-
|
|
27
|
-
function quiet(cmd) {
|
|
28
|
-
try {
|
|
29
|
-
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function run(cmd, { capture = false } = {}) {
|
|
36
|
-
if (dryRun) {
|
|
37
|
-
console.log(`[dry-run] ${cmd}`);
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
console.log(`$ ${cmd}`);
|
|
41
|
-
try {
|
|
42
|
-
if (capture) {
|
|
43
|
-
const out = execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
|
|
44
|
-
process.stdout.write(out);
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
48
|
-
return '';
|
|
49
|
-
} catch {
|
|
50
|
-
console.error(
|
|
51
|
-
`\nERROR: \`${cmd}\` failed (see output above). Fix and re-run — the script is idempotent.`,
|
|
52
|
-
);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (target === 'node') {
|
|
58
|
-
console.log(`Hosting target 'node' has no cloud resources to provision. To switch this
|
|
59
|
-
project to a Node host (VPS / docker-compose / k8s):
|
|
60
|
-
|
|
61
|
-
1. pnpm add @astrojs/node better-sqlite3
|
|
62
|
-
2. In astro.config.mjs: vitopsHosting({ target: 'node' }) (or set HOSTING=node)
|
|
63
|
-
3. Build and run: pnpm build && node ./dist/server/entry.mjs
|
|
64
|
-
Data lives in ./data/ (SQLite db + uploads) — persist it as a volume.
|
|
65
|
-
4. CI deploy for VPS/docker/k8s is not scaffolded yet — the deploy-*.yml
|
|
66
|
-
workflows are Cloudflare-specific; replace or disable them.
|
|
67
|
-
|
|
68
|
-
For production on Node, move storage to S3-compatible and/or the database to
|
|
69
|
-
Postgres via vitopsHosting({ node: { database, storage } }) — see the
|
|
70
|
-
@getvitops/emdash README.`);
|
|
71
|
-
process.exit(0);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (target !== 'cloudflare') {
|
|
75
|
-
console.error(`ERROR: unknown hosting target '${target}'. Valid targets: cloudflare, node.`);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// --- Preflight: wrangler auth ------------------------------------------------
|
|
80
|
-
const whoami = spawnSync('npx', ['wrangler', 'whoami'], { encoding: 'utf8' });
|
|
81
|
-
if (whoami.status !== 0) {
|
|
82
|
-
console.error('ERROR: wrangler is not logged in. Run `npx wrangler login` first.');
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
// "Account Name | <32-hex account id>" table row in `wrangler whoami` output.
|
|
86
|
-
const accountId = (whoami.stdout.match(/\b([0-9a-f]{32})\b/) ?? [])[1] ?? null;
|
|
87
|
-
|
|
88
|
-
// --- Dev D1 database ---------------------------------------------------------
|
|
89
|
-
const devDb = `${name}-dev`;
|
|
90
|
-
let devDbId = null;
|
|
91
|
-
const d1List = quiet('npx wrangler d1 list --json');
|
|
92
|
-
if (d1List) {
|
|
93
|
-
devDbId = JSON.parse(d1List).find((d) => d.name === devDb)?.uuid ?? null;
|
|
94
|
-
}
|
|
95
|
-
if (devDbId) {
|
|
96
|
-
console.log(`D1 database ${devDb} already exists (${devDbId}).`);
|
|
97
|
-
} else {
|
|
98
|
-
const out = run(`npx wrangler d1 create ${devDb}`, { capture: true });
|
|
99
|
-
devDbId = (out.match(/"database_id":\s*"([0-9a-f-]{36})"/) ?? [])[1] ?? null;
|
|
100
|
-
if (!dryRun && !devDbId) {
|
|
101
|
-
// Output format drifts across wrangler versions — the list is authoritative.
|
|
102
|
-
const relist = quiet('npx wrangler d1 list --json');
|
|
103
|
-
devDbId = relist ? (JSON.parse(relist).find((d) => d.name === devDb)?.uuid ?? null) : null;
|
|
104
|
-
}
|
|
105
|
-
if (!dryRun && !devDbId) {
|
|
106
|
-
console.error('ERROR: could not determine the database_id of the created dev database.');
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// --- Dev R2 bucket -----------------------------------------------------------
|
|
112
|
-
const devBucket = `${name}-media-dev`;
|
|
113
|
-
const bucketList = quiet('npx wrangler r2 bucket list');
|
|
114
|
-
if (bucketList && new RegExp(`name:\\s+${devBucket}\\s*$`, 'm').test(bucketList)) {
|
|
115
|
-
console.log(`R2 bucket ${devBucket} already exists.`);
|
|
116
|
-
} else {
|
|
117
|
-
run(`npx wrangler r2 bucket create ${devBucket}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// --- GitHub repo secrets (when the repo is wired up) -------------------------
|
|
121
|
-
const hasGh = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8' }).status === 0;
|
|
122
|
-
const hasOrigin = quiet('git remote get-url origin') !== null;
|
|
123
|
-
if (hasGh && hasOrigin) {
|
|
124
|
-
if (devDbId) run(`gh secret set DEV_D1_ID --body ${devDbId}`);
|
|
125
|
-
if (accountId) run(`gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId}`);
|
|
126
|
-
} else {
|
|
127
|
-
console.log(
|
|
128
|
-
'\nSkipping GitHub secrets (no gh auth or no origin remote — run init:github first).',
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
console.log(`
|
|
133
|
-
Cloudflare dev environment ready:
|
|
134
|
-
D1: ${devDb}${devDbId ? ` (${devDbId})` : ''}
|
|
135
|
-
R2: ${devBucket}
|
|
136
|
-
${
|
|
137
|
-
hasGh && hasOrigin
|
|
138
|
-
? ''
|
|
139
|
-
: ` Set repo secrets once GitHub is wired up:
|
|
140
|
-
gh secret set DEV_D1_ID --body ${devDbId ?? '<id>'}
|
|
141
|
-
gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId ?? '<account id>'}
|
|
142
|
-
`
|
|
143
|
-
}
|
|
144
|
-
Still manual (CLI can't create API tokens):
|
|
145
|
-
1. Create a token with Workers + D1 + R2 edit permissions:
|
|
146
|
-
https://dash.cloudflare.com/profile/api-tokens
|
|
147
|
-
2. gh secret set CLOUDFLARE_API_TOKEN
|
|
148
|
-
|
|
149
|
-
Prod D1/R2 are provisioned automatically by wrangler on the first \`pnpm deploy\`.
|
|
150
|
-
Local \`pnpm deploy:dev\` runs need: export DEV_D1_ID=${devDbId ?? '<id>'}`);
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// One-time GitHub bootstrap: creates a GitHub repo named after this project
|
|
2
|
-
// (package.json name), pushes `main` + `dev`, makes `dev` the default branch,
|
|
3
|
-
// and provisions the branch-protection + environment policy the deploy
|
|
4
|
-
// workflows assume (deploy-dev on push to dev, deploy-prod on push to main,
|
|
5
|
-
// promote.yml between them):
|
|
6
|
-
//
|
|
7
|
-
// - `main` requires a PR + the `ci` status check — it only advances via
|
|
8
|
-
// promote.yml, never a direct push.
|
|
9
|
-
// - the `production` GitHub Environment requires review from an admin
|
|
10
|
-
// collaborator before deploy-prod.yml is allowed to run — this is the
|
|
11
|
-
// "promoting to prod needs admin sign-off" gate.
|
|
12
|
-
// - `dev` is left UNPROTECTED on purpose — that's what lets an agent (or
|
|
13
|
-
// anyone with push access) commit and merge there directly for
|
|
14
|
-
// integration; only the promotion to `main` is gated.
|
|
15
|
-
//
|
|
16
|
-
// pnpm run init:github # private repo (default)
|
|
17
|
-
// pnpm run init:github -- --public
|
|
18
|
-
// pnpm run init:github -- --dry-run
|
|
19
|
-
//
|
|
20
|
-
// Idempotent: safe to re-run — existing git repo, commits, dev branch, origin
|
|
21
|
-
// remote, branch protection and environments are all left alone or updated
|
|
22
|
-
// in place. Requires the GitHub CLI (`gh`), logged in (`gh auth login`).
|
|
23
|
-
//
|
|
24
|
-
// NOTE: branch protection and environment required-reviewers are GitHub
|
|
25
|
-
// Pro/Team features on a PRIVATE repo. Each `gh api` call below prints the
|
|
26
|
-
// manual equivalent and continues rather than aborting when it 403s.
|
|
27
|
-
|
|
28
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
29
|
-
import { readFileSync } from 'node:fs';
|
|
30
|
-
|
|
31
|
-
const args = new Set(process.argv.slice(2));
|
|
32
|
-
const dryRun = args.has('--dry-run');
|
|
33
|
-
const visibility = args.has('--public') ? '--public' : '--private';
|
|
34
|
-
|
|
35
|
-
const repoName = JSON.parse(readFileSync('package.json', 'utf8')).name.replace(/^@[^/]+\//, '');
|
|
36
|
-
|
|
37
|
-
function check(cmd) {
|
|
38
|
-
return spawnSync(cmd[0], cmd.slice(1), { encoding: 'utf8' });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function quiet(cmd) {
|
|
42
|
-
try {
|
|
43
|
-
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function run(cmd) {
|
|
50
|
-
if (dryRun) {
|
|
51
|
-
console.log(`[dry-run] ${cmd}`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
console.log(`$ ${cmd}`);
|
|
55
|
-
try {
|
|
56
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
57
|
-
} catch {
|
|
58
|
-
console.error(
|
|
59
|
-
`\nERROR: \`${cmd}\` failed (see output above). Fix and re-run — the script is idempotent.`,
|
|
60
|
-
);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Like `run`, but for `gh api` calls that provision an org/plan-gated
|
|
67
|
-
* feature (branch protection, environment reviewers). A 403 there means the
|
|
68
|
-
* repo's plan doesn't support it — not that the bootstrap failed — so this
|
|
69
|
-
* prints the manual equivalent and keeps going instead of exiting.
|
|
70
|
-
*/
|
|
71
|
-
function runPolicy(cmd, manualHint) {
|
|
72
|
-
if (dryRun) {
|
|
73
|
-
console.log(`[dry-run] ${cmd}`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
console.log(`$ ${cmd}`);
|
|
77
|
-
const result = spawnSync(cmd, { shell: true, encoding: 'utf8' });
|
|
78
|
-
if (result.status === 0) return;
|
|
79
|
-
console.warn(
|
|
80
|
-
`\nWARNING: \`${cmd}\` failed (likely needs GitHub Pro/Team on a private repo).\n` +
|
|
81
|
-
`Set it up manually instead:\n ${manualHint}\n`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- Preflight: gh installed and authenticated -------------------------------
|
|
86
|
-
if (check(['gh', '--version']).status !== 0) {
|
|
87
|
-
console.error('ERROR: the GitHub CLI (`gh`) is required. Install it from https://cli.github.com');
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
if (check(['gh', 'auth', 'status']).status !== 0) {
|
|
91
|
-
console.error('ERROR: `gh` is not logged in. Run `gh auth login` first.');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// --- Local git: repo, initial commit, main + dev branches --------------------
|
|
96
|
-
if (quiet('git rev-parse --is-inside-work-tree') === null) {
|
|
97
|
-
run('git init -b main');
|
|
98
|
-
}
|
|
99
|
-
if (quiet('git rev-parse HEAD') === null) {
|
|
100
|
-
run('git add -A');
|
|
101
|
-
run('git commit -m "Initial commit (scaffolded from @getvitops/create)"');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// The workflows key off `main`; normalize whatever the initial branch is.
|
|
105
|
-
const branch = quiet('git branch --show-current');
|
|
106
|
-
if (branch && branch !== 'main' && branch !== 'dev') {
|
|
107
|
-
run(`git branch -m ${branch} main`);
|
|
108
|
-
}
|
|
109
|
-
if (quiet('git show-ref --verify refs/heads/dev') === null) {
|
|
110
|
-
run('git branch dev main');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// --- GitHub: create repo (same name), push, default to dev -------------------
|
|
114
|
-
if (quiet('git remote get-url origin') === null) {
|
|
115
|
-
run(`gh repo create ${repoName} ${visibility} --source=. --remote=origin`);
|
|
116
|
-
} else {
|
|
117
|
-
console.log('origin remote already set — skipping `gh repo create`.');
|
|
118
|
-
}
|
|
119
|
-
run('git push -u origin main dev');
|
|
120
|
-
run('gh repo edit --default-branch dev');
|
|
121
|
-
run('git checkout dev');
|
|
122
|
-
|
|
123
|
-
// --- Policy: protect main, gate production behind admin review --------------
|
|
124
|
-
const nwo = quiet('gh repo view --json nameWithOwner -q .nameWithOwner');
|
|
125
|
-
if (nwo) {
|
|
126
|
-
runPolicy(
|
|
127
|
-
`gh api -X PUT repos/${nwo}/branches/main/protection --input - <<'JSON'
|
|
128
|
-
{
|
|
129
|
-
"required_status_checks": { "strict": true, "contexts": ["ci"] },
|
|
130
|
-
"enforce_admins": false,
|
|
131
|
-
"required_pull_request_reviews": { "required_approving_review_count": 1 },
|
|
132
|
-
"restrictions": null,
|
|
133
|
-
"allow_force_pushes": false,
|
|
134
|
-
"allow_deletions": false
|
|
135
|
-
}
|
|
136
|
-
JSON`,
|
|
137
|
-
`Settings → Branches → protect 'main' (require a PR, 1 approval, the 'ci' ` +
|
|
138
|
-
`status check, no force-push/deletion).`,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const admins = quiet(
|
|
142
|
-
`gh api repos/${nwo}/collaborators?permission=admin -q '[.[].login]' 2>/dev/null`,
|
|
143
|
-
);
|
|
144
|
-
const reviewers = admins
|
|
145
|
-
? JSON.parse(admins).map((login) => ({ type: 'User', id_or_login: login }))
|
|
146
|
-
: [];
|
|
147
|
-
|
|
148
|
-
runPolicy(
|
|
149
|
-
`gh api -X PUT repos/${nwo}/environments/production --input - <<'JSON'
|
|
150
|
-
{
|
|
151
|
-
"reviewers": ${JSON.stringify(reviewers)},
|
|
152
|
-
"deployment_branch_policy": { "protected_branches": true, "custom_branch_policies": false }
|
|
153
|
-
}
|
|
154
|
-
JSON`,
|
|
155
|
-
`Settings → Environments → 'production' → require reviewers = your admins, ` +
|
|
156
|
-
`restrict deployment branches to 'main'. This is the prod-needs-admin-approval gate.`,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// `dev` gets an environment too (for the deploy-dev workflow's `environment:
|
|
160
|
-
// dev` reference), but deliberately no reviewers and no branch restriction —
|
|
161
|
-
// that's what keeps it open for direct pushes/merges.
|
|
162
|
-
runPolicy(
|
|
163
|
-
`gh api -X PUT repos/${nwo}/environments/dev --input - <<'JSON'
|
|
164
|
-
{ "reviewers": [] }
|
|
165
|
-
JSON`,
|
|
166
|
-
`Settings → Environments → create 'dev' with no required reviewers.`,
|
|
167
|
-
);
|
|
168
|
-
} else {
|
|
169
|
-
console.warn('WARNING: could not resolve the repo name-with-owner; skipping policy setup.');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// --- What's left (manual, needs your Cloudflare account) ---------------------
|
|
173
|
-
console.log(`
|
|
174
|
-
Repo ready: main (protected, production) + dev (integration, default branch).
|
|
175
|
-
NOTE: the first workflow runs will fail until the secrets below exist.
|
|
176
|
-
|
|
177
|
-
Next steps:
|
|
178
|
-
1. Dev resources: wrangler d1 create ${repoName}-dev (note the printed id)
|
|
179
|
-
wrangler r2 bucket create ${repoName}-media-dev
|
|
180
|
-
2. Secrets: gh secret set CLOUDFLARE_API_TOKEN
|
|
181
|
-
gh secret set CLOUDFLARE_ACCOUNT_ID
|
|
182
|
-
gh secret set DEV_D1_ID
|
|
183
|
-
3. Verify policy: Settings → Branches (main protected?) and
|
|
184
|
-
Settings → Environments → production (reviewers set?) —
|
|
185
|
-
printed above if this ran on a plan that supports them.
|
|
186
|
-
`);
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// One-time hosting-platform setup for this static site.
|
|
2
|
-
//
|
|
3
|
-
// pnpm run init:hosting
|
|
4
|
-
// pnpm run init:hosting -- --dry-run
|
|
5
|
-
//
|
|
6
|
-
// There is no D1/R2 to provision here — a pure static build has no data
|
|
7
|
-
// layer — so this reduces to reading the Cloudflare account id from
|
|
8
|
-
// `wrangler whoami` and, when a GitHub repo is wired up (see
|
|
9
|
-
// init-github.mjs), setting it as a repo secret. The API token itself still
|
|
10
|
-
// has to be created by hand (the CLI can't mint one).
|
|
11
|
-
//
|
|
12
|
-
// Idempotent: safe to re-run. Requires wrangler auth (`npx wrangler login`).
|
|
13
|
-
|
|
14
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
15
|
-
|
|
16
|
-
const dryRun = process.argv.includes('--dry-run');
|
|
17
|
-
|
|
18
|
-
function quiet(cmd) {
|
|
19
|
-
try {
|
|
20
|
-
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function run(cmd) {
|
|
27
|
-
if (dryRun) {
|
|
28
|
-
console.log(`[dry-run] ${cmd}`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
console.log(`$ ${cmd}`);
|
|
32
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const whoami = spawnSync('npx', ['wrangler', 'whoami'], { encoding: 'utf8' });
|
|
36
|
-
if (whoami.status !== 0) {
|
|
37
|
-
console.error('ERROR: wrangler is not logged in. Run `npx wrangler login` first.');
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
// "Account Name | <32-hex account id>" table row in `wrangler whoami` output.
|
|
41
|
-
const accountId = (whoami.stdout.match(/\b([0-9a-f]{32})\b/) ?? [])[1] ?? null;
|
|
42
|
-
|
|
43
|
-
const hasGh = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8' }).status === 0;
|
|
44
|
-
const hasOrigin = quiet('git remote get-url origin') !== null;
|
|
45
|
-
if (hasGh && hasOrigin && accountId) {
|
|
46
|
-
run(`gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId}`);
|
|
47
|
-
} else {
|
|
48
|
-
console.log(
|
|
49
|
-
'\nSkipping GitHub secret (no gh auth, no origin remote, or no account id — run init:github first).',
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log(`
|
|
54
|
-
Cloudflare account: ${accountId ?? '(not found — check `wrangler whoami`)'}
|
|
55
|
-
|
|
56
|
-
Still manual (CLI can't create API tokens):
|
|
57
|
-
1. Create a token with Workers edit permission:
|
|
58
|
-
https://dash.cloudflare.com/profile/api-tokens
|
|
59
|
-
2. gh secret set CLOUDFLARE_API_TOKEN
|
|
60
|
-
|
|
61
|
-
Prod/dev D1/R2 don't apply here — this is a pure static site.
|
|
62
|
-
`);
|