@next-friday/create-app 5.0.3 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs
CHANGED
|
@@ -81,12 +81,15 @@ async function writeEnvironmentFile(directory) {
|
|
|
81
81
|
*/
|
|
82
82
|
function initGit(directory) {
|
|
83
83
|
if (existsSync(path.join(directory, ".git"))) return "existing";
|
|
84
|
+
const environment = projectGitEnvironment();
|
|
84
85
|
if (spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
85
86
|
cwd: directory,
|
|
87
|
+
env: environment,
|
|
86
88
|
stdio: "ignore"
|
|
87
89
|
}).status === 0) return "parent";
|
|
88
90
|
return spawnSync("git", ["init", "--initial-branch=main"], {
|
|
89
91
|
cwd: directory,
|
|
92
|
+
env: environment,
|
|
90
93
|
stdio: "ignore"
|
|
91
94
|
}).status === 0 ? "initialized" : "failed";
|
|
92
95
|
}
|
|
@@ -99,9 +102,22 @@ function didInstallDependencies(directory) {
|
|
|
99
102
|
return spawnSync("pnpm install", {
|
|
100
103
|
cwd: directory,
|
|
101
104
|
stdio: "inherit",
|
|
102
|
-
shell: true
|
|
105
|
+
shell: true,
|
|
106
|
+
env: projectGitEnvironment()
|
|
103
107
|
}).status === 0;
|
|
104
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The environment without the repository variables that `git rev-parse --local-env-vars` lists.
|
|
111
|
+
* An inherited `GIT_DIR`, as Git sets for its hooks, would point Git, and the install's Git hook
|
|
112
|
+
* setup, at another repository. `git -c` options stay, as Git keeps them for another repository.
|
|
113
|
+
* @returns Environment for commands that run Git in the project directory.
|
|
114
|
+
*/
|
|
115
|
+
function projectGitEnvironment() {
|
|
116
|
+
const localVariables = new Set(spawnSync("git", ["rev-parse", "--local-env-vars"], { encoding: "utf8" }).stdout?.split("\n"));
|
|
117
|
+
localVariables.delete("GIT_CONFIG_PARAMETERS");
|
|
118
|
+
localVariables.delete("GIT_CONFIG_COUNT");
|
|
119
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !localVariables.has(name)));
|
|
120
|
+
}
|
|
105
121
|
// src/project-name.ts
|
|
106
122
|
const packageNamePattern = /^[a-z0-9][a-z0-9._-]*$/;
|
|
107
123
|
const maxPackageNameLength = 214;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next-friday/create-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Create a Next Friday project from a template.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"template",
|
|
9
9
|
"next-friday"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://github.com/next-friday/
|
|
11
|
+
"homepage": "https://github.com/next-friday/next-friday-create-app#readme",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/next-friday/
|
|
13
|
+
"url": "https://github.com/next-friday/next-friday-create-app/issues"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/next-friday/
|
|
17
|
+
"url": "git+https://github.com/next-friday/next-friday-create-app.git"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "Next Friday",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"bin": {
|
|
27
|
-
"
|
|
27
|
+
"next-friday-create-app": "./dist/main.mjs"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
allow() {
|
|
5
|
+
printf '%s\n' '{"permission":"allow"}'
|
|
6
|
+
exit 0
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
block() {
|
|
10
|
+
jq -n --arg message "$1" '{permission:"deny",user_message:$message,agent_message:$message}'
|
|
11
|
+
printf 'agent-production-gate: %s\n' "$1" >&2
|
|
12
|
+
exit 2
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
trap 'block ".agents/hooks/agent-production-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
|
|
16
|
+
command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
|
|
17
|
+
command=$(jq -r '.command // .tool_input.command // empty')
|
|
18
|
+
[[ -n $command && -z ${RELEASE_APPROVAL:-} ]] || allow
|
|
19
|
+
|
|
20
|
+
release_gate="A release manager runs prod deploys, commands against prod, and release tag pushes. Do not run this; report what is ready to promote, such as the commit SHA. If a gated command appears only inside a message, write the text to a file and pass it with git commit -F <file> or gh pr create --body-file <file>."
|
|
21
|
+
|
|
22
|
+
# The gate reads the command line as text, joining continued lines and dropping quotes,
|
|
23
|
+
# backslashes, and fd redirections, so wrappers such as bash -c, eval, or npx and split words such
|
|
24
|
+
# as pr''od do not hide the common forms. A dispatch input or Railway environment the gate cannot
|
|
25
|
+
# read (a variable, a command substitution, a file, or stdin) counts as prod. It is a convenience,
|
|
26
|
+
# not a security boundary: the prod environment's required reviewers and Railway credentials are.
|
|
27
|
+
text=${command//'\\'/}
|
|
28
|
+
text=${text//$'\\\n'/}
|
|
29
|
+
text=$(sed -E 's/([0-9]*[<>]&[0-9-]*|&>>?)//g' <<< "${text//[\"\'\\]/}")
|
|
30
|
+
simple=$'[^;&|\n]*'
|
|
31
|
+
prod="(prod([^[:alnum:]_-]|$)|[\$\`@])"
|
|
32
|
+
dispatch="(workflow[[:space:]]+run|/dispatches)"
|
|
33
|
+
environment_field="environment[]]*[=:][[:space:]]*$prod"
|
|
34
|
+
unreadable_inputs="--(json|input)([[:space:]=]|$)"
|
|
35
|
+
railway="(^|[^[:alnum:]_.-])(railway|@railway/cli(@[^[:space:]]*)?)([[:space:]]$simple)?"
|
|
36
|
+
git_push="(^|[^[:alnum:]_.-])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--(git-dir|work-tree|namespace|config-env)[[:space:]]+[^[:space:]]+|--?[^[:space:]]+))*[[:space:]]+push([[:space:]]$simple)?"
|
|
37
|
+
tag_ref="([[:space:]:+]((refs/)?tags/|v[0-9])|[[:space:]]--((follow-)?tags|mirror)([[:space:]=]|$))"
|
|
38
|
+
|
|
39
|
+
if [[ $text =~ $dispatch$simple($environment_field|$unreadable_inputs) || $text =~ $environment_field$simple$dispatch ]]; then
|
|
40
|
+
block "$release_gate"
|
|
9
41
|
fi
|
|
10
|
-
|
|
42
|
+
if [[ $text =~ $railway[[:space:]](-[[:alpha:]]*e([[:space:]]+|=)?|--environment([[:space:]]+|=))$prod || $text =~ $railway[[:space:]](env|environment)([[:space:]]+[[:alpha:]]+)?[[:space:]]+$prod ]]; then
|
|
43
|
+
block "$release_gate"
|
|
44
|
+
fi
|
|
45
|
+
if [[ $text =~ $git_push$tag_ref || $(tr '[:upper:]' '[:lower:]' <<< "$text") == *push.followtags* ]]; then
|
|
46
|
+
block "$release_gate"
|
|
47
|
+
fi
|
|
48
|
+
allow
|
|
@@ -6,12 +6,14 @@ type GateCase = {
|
|
|
6
6
|
gate: string;
|
|
7
7
|
input: Record<string, unknown>;
|
|
8
8
|
name: string;
|
|
9
|
+
releaseApproval?: string;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const shellGate = ".agents/hooks/agent-shell-gate.sh";
|
|
12
13
|
const suppressionGate = ".agents/hooks/agent-suppression-gate.sh";
|
|
13
14
|
const constitutionGate = ".cursor/hooks/agent-constitution-gate.sh";
|
|
14
15
|
const mcpGate = ".agents/hooks/agent-mcp-gate.sh";
|
|
16
|
+
const productionGate = ".agents/hooks/agent-production-gate.sh";
|
|
15
17
|
const sourceFile = "src/example.ts";
|
|
16
18
|
|
|
17
19
|
const blockedCommands = [
|
|
@@ -189,6 +191,101 @@ const allowedCommands = [
|
|
|
189
191
|
'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
|
|
190
192
|
];
|
|
191
193
|
|
|
194
|
+
const productionCommands = [
|
|
195
|
+
"gh workflow run deploy.yml -f environment=prod -f commit_sha=abc",
|
|
196
|
+
"gh workflow run Deploy --field 'environment=prod'",
|
|
197
|
+
"gh workflow run deploy.yml -fenvironment=prod",
|
|
198
|
+
"gh workflow run deploy.yml --field=environment=prod",
|
|
199
|
+
'gh workflow run deploy.yml -f "environment=prod"',
|
|
200
|
+
"gh workflow run deploy.yml -F environment=@environment.txt",
|
|
201
|
+
"gh workflow run deploy.yml --json",
|
|
202
|
+
"gh workflow run deploy.yml --json=true",
|
|
203
|
+
"for e in uat prod; do gh workflow run deploy.yml -f environment=$e -f commit_sha=abc; done",
|
|
204
|
+
"env=prod; gh workflow run deploy.yml -f environment=$env",
|
|
205
|
+
"gh workflow run deploy.yml -f environment=$(echo prod)",
|
|
206
|
+
'gh workflow run deploy.yml -f "environment=$TARGET"',
|
|
207
|
+
"gh api -X POST repos/o/r/actions/workflows/deploy.yml/dispatches -f ref=main -f 'inputs[environment]=prod'",
|
|
208
|
+
"gh api repos/o/r/actions/workflows/deploy.yml/dispatches/ -f inputs[environment]=prod",
|
|
209
|
+
"gh api -f 'inputs[environment]=prod' repos/o/r/actions/workflows/deploy.yml/dispatches",
|
|
210
|
+
"gh api repos/o/r/actions/workflows/1/dispatches --input body.json",
|
|
211
|
+
"gh api repos/o/r/actions/workflows/1/dispatches -f inputs[environment]=$E",
|
|
212
|
+
"bash -c 'gh workflow run deploy.yml -f environment=prod'",
|
|
213
|
+
"railway up --ci --environment prod",
|
|
214
|
+
"railway up -e prod",
|
|
215
|
+
"railway up -e=prod",
|
|
216
|
+
"railway up -eprod",
|
|
217
|
+
"railway up -ce prod",
|
|
218
|
+
"railway up -ceprod",
|
|
219
|
+
"railway redeploy -ye prod",
|
|
220
|
+
"railway up -s api -e prod",
|
|
221
|
+
"railway up --environment=prod",
|
|
222
|
+
'railway up -e "$ENV"',
|
|
223
|
+
"for e in uat prod; do railway up -e $e; done",
|
|
224
|
+
"railway env prod",
|
|
225
|
+
"railway env link prod",
|
|
226
|
+
"railway environment prod",
|
|
227
|
+
"railway environment delete prod --yes",
|
|
228
|
+
"railway link -e prod",
|
|
229
|
+
"railway logs -e prod",
|
|
230
|
+
"railway variables --set A=b -e prod",
|
|
231
|
+
"npx @railway/cli up -e prod",
|
|
232
|
+
"pnpm dlx @railway/cli up -e prod",
|
|
233
|
+
"/usr/local/bin/railway up -e prod",
|
|
234
|
+
"timeout 60 railway up -e prod",
|
|
235
|
+
"eval railway up -e prod",
|
|
236
|
+
"git push origin v1.0.0",
|
|
237
|
+
"git push origin HEAD:refs/tags/v1.0.0",
|
|
238
|
+
"git push --tags",
|
|
239
|
+
"git push --follow-tags origin main",
|
|
240
|
+
"git -C . push origin v2.3.4",
|
|
241
|
+
"npx @railway/cli@5.71.0 up -e prod",
|
|
242
|
+
"npx @railway/cli@latest up --environment prod",
|
|
243
|
+
"railway up -e pr''od",
|
|
244
|
+
"gh workflow run deploy.yml -f environment=pr''od",
|
|
245
|
+
"gh workflow run deploy.yml -f environment=p\\rod",
|
|
246
|
+
'gh workflow run deploy.yml -f "envi"ronment=prod',
|
|
247
|
+
"git push origin 'refs/tags/*'",
|
|
248
|
+
'git push origin "refs/tags/v*"',
|
|
249
|
+
"git push --mirror",
|
|
250
|
+
"git -c push.followTags=true push origin main",
|
|
251
|
+
"git config push.followTags true && git push",
|
|
252
|
+
"gh workflow run deploy.yml 2>&1 -f environment=prod",
|
|
253
|
+
"railway up 2>&1 -e prod",
|
|
254
|
+
"git push origin tags/v1.0.0",
|
|
255
|
+
"git --git-dir .git push origin v1.0.0",
|
|
256
|
+
"gh workflow run deploy.yml \\\n -f environment=prod \\\n -f commit_sha=abc",
|
|
257
|
+
"railway up \\\n --environment prod",
|
|
258
|
+
"git push \\\n origin v1.0.0",
|
|
259
|
+
"railway up &>/dev/null -e prod",
|
|
260
|
+
"git push &>/dev/null origin v1.0.0",
|
|
261
|
+
"cd a\\\\\ngit push origin v1.0.0",
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
const nonProductionCommands = [
|
|
265
|
+
"gh workflow run deploy.yml -f environment=uat -f commit_sha=abc",
|
|
266
|
+
"gh api repos/o/r/actions/workflows/deploy.yml/dispatches -f 'inputs[environment]=uat'",
|
|
267
|
+
"railway up -e uat",
|
|
268
|
+
"railway up -e prod-eu",
|
|
269
|
+
"railway environment uat",
|
|
270
|
+
"railway status",
|
|
271
|
+
"gh workflow list",
|
|
272
|
+
"gh run list --workflow deploy.yml",
|
|
273
|
+
"gh api repos/o/r/environments/prod",
|
|
274
|
+
"git push -u origin feature/v2-ui",
|
|
275
|
+
"git push origin main",
|
|
276
|
+
'git commit -m "release v1.0" && git push',
|
|
277
|
+
'git commit -m "docs(x): deploy to production"',
|
|
278
|
+
'gh pr create --title "ci(x): promote to prod" --body "run gh workflow run later"',
|
|
279
|
+
"echo railway",
|
|
280
|
+
"git tag v1.0.0",
|
|
281
|
+
'git stash push -m "wip v2"',
|
|
282
|
+
'git commit -m "fix(ci): push v2 images"',
|
|
283
|
+
'git commit -m "fix: railway env uat and prod split"',
|
|
284
|
+
"git push -u origin HEAD",
|
|
285
|
+
"git push origin feature/push-v2",
|
|
286
|
+
"pnpm verify 2>&1 | tail && git push",
|
|
287
|
+
];
|
|
288
|
+
|
|
192
289
|
const mcpTools = {
|
|
193
290
|
blocked: [
|
|
194
291
|
"mcp__github__merge_pull_request",
|
|
@@ -273,6 +370,47 @@ const cases: GateCase[] = [
|
|
|
273
370
|
expectedPermission: "allow" as const,
|
|
274
371
|
name: `shell: ${command}`,
|
|
275
372
|
})),
|
|
373
|
+
...productionCommands.flatMap(command => [
|
|
374
|
+
{
|
|
375
|
+
gate: productionGate,
|
|
376
|
+
input: {
|
|
377
|
+
command,
|
|
378
|
+
},
|
|
379
|
+
expectedExit: 2 as const,
|
|
380
|
+
name: `Cursor production: ${command}`,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
gate: productionGate,
|
|
384
|
+
input: {
|
|
385
|
+
tool_input: {
|
|
386
|
+
command,
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
expectedExit: 2 as const,
|
|
390
|
+
name: `Claude production: ${command}`,
|
|
391
|
+
},
|
|
392
|
+
]),
|
|
393
|
+
{
|
|
394
|
+
gate: productionGate,
|
|
395
|
+
releaseApproval: "CHG-1",
|
|
396
|
+
expectedPermission: "allow",
|
|
397
|
+
name: "production with RELEASE_APPROVAL: railway up -e prod",
|
|
398
|
+
expectedExit: 0,
|
|
399
|
+
input: {
|
|
400
|
+
tool_input: {
|
|
401
|
+
command: "railway up -e prod",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
...nonProductionCommands.map(command => ({
|
|
406
|
+
gate: productionGate,
|
|
407
|
+
input: {
|
|
408
|
+
command,
|
|
409
|
+
},
|
|
410
|
+
expectedExit: 0 as const,
|
|
411
|
+
expectedPermission: "allow" as const,
|
|
412
|
+
name: `production: ${command}`,
|
|
413
|
+
})),
|
|
276
414
|
...mcpTools.blocked.map(tool => ({
|
|
277
415
|
gate: mcpGate,
|
|
278
416
|
input: {
|
|
@@ -399,6 +537,7 @@ const failures = cases.filter(gateCase => {
|
|
|
399
537
|
...process.env,
|
|
400
538
|
CLAUDE_PROJECT_DIR: process.cwd(),
|
|
401
539
|
CURSOR_PROJECT_DIR: process.cwd(),
|
|
540
|
+
RELEASE_APPROVAL: gateCase.releaseApproval,
|
|
402
541
|
},
|
|
403
542
|
input: JSON.stringify(gateCase.input),
|
|
404
543
|
});
|