@mp-lb/arcnet 0.0.0-stage → 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.
- package/README.md +19 -2
- package/dist/addDependency.d.ts +3 -0
- package/dist/addDependency.d.ts.map +1 -0
- package/dist/addDependency.js +29 -0
- package/dist/addDependency.js.map +1 -0
- package/dist/apiClient.d.ts +3 -0
- package/dist/apiClient.d.ts.map +1 -0
- package/dist/apiClient.js +39 -0
- package/dist/apiClient.js.map +1 -0
- package/dist/checkIdentity.d.ts +3 -0
- package/dist/checkIdentity.d.ts.map +1 -0
- package/dist/checkIdentity.js +9 -0
- package/dist/checkIdentity.js.map +1 -0
- package/dist/ciOutputs.d.ts +2 -0
- package/dist/ciOutputs.d.ts.map +1 -0
- package/dist/ciOutputs.js +31 -0
- package/dist/ciOutputs.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -0
- package/dist/createCli.d.ts +3 -0
- package/dist/createCli.d.ts.map +1 -0
- package/dist/createCli.js +83 -0
- package/dist/createCli.js.map +1 -0
- package/dist/createProject.d.ts +26 -0
- package/dist/createProject.d.ts.map +1 -0
- package/dist/createProject.js +35 -0
- package/dist/createProject.js.map +1 -0
- package/dist/createSetupCommand.d.ts +3 -0
- package/dist/createSetupCommand.d.ts.map +1 -0
- package/dist/createSetupCommand.js +11 -0
- package/dist/createSetupCommand.js.map +1 -0
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +47 -0
- package/dist/deploy.js.map +1 -0
- package/dist/fail.d.ts +2 -0
- package/dist/fail.d.ts.map +1 -0
- package/dist/fail.js +4 -0
- package/dist/fail.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/inspectReferences.d.ts +3 -0
- package/dist/inspectReferences.d.ts.map +1 -0
- package/dist/inspectReferences.js +10 -0
- package/dist/inspectReferences.js.map +1 -0
- package/dist/inspectStaticTree.d.ts +2 -0
- package/dist/inspectStaticTree.d.ts.map +1 -0
- package/dist/inspectStaticTree.js +17 -0
- package/dist/inspectStaticTree.js.map +1 -0
- package/dist/packageInfo.d.ts +3 -0
- package/dist/packageInfo.d.ts.map +1 -0
- package/dist/packageInfo.js +9 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/parseSelection.d.ts +3 -0
- package/dist/parseSelection.d.ts.map +1 -0
- package/dist/parseSelection.js +13 -0
- package/dist/parseSelection.js.map +1 -0
- package/dist/parseSetupInput.d.ts +16 -0
- package/dist/parseSetupInput.d.ts.map +1 -0
- package/dist/parseSetupInput.js +34 -0
- package/dist/parseSetupInput.js.map +1 -0
- package/dist/readSelection.d.ts +15 -0
- package/dist/readSelection.d.ts.map +1 -0
- package/dist/readSelection.js +36 -0
- package/dist/readSelection.js.map +1 -0
- package/dist/renderPlan.d.ts +27 -0
- package/dist/renderPlan.d.ts.map +1 -0
- package/dist/renderPlan.js +25 -0
- package/dist/renderPlan.js.map +1 -0
- package/dist/renderResult.d.ts +5 -0
- package/dist/renderResult.d.ts.map +1 -0
- package/dist/renderResult.js +15 -0
- package/dist/renderResult.js.map +1 -0
- package/dist/resolveRepository.d.ts +2 -0
- package/dist/resolveRepository.d.ts.map +1 -0
- package/dist/resolveRepository.js +24 -0
- package/dist/resolveRepository.js.map +1 -0
- package/dist/runProcess.d.ts +2 -0
- package/dist/runProcess.d.ts.map +1 -0
- package/dist/runProcess.js +16 -0
- package/dist/runProcess.js.map +1 -0
- package/dist/runTerraform.d.ts +8 -0
- package/dist/runTerraform.d.ts.map +1 -0
- package/dist/runTerraform.js +111 -0
- package/dist/runTerraform.js.map +1 -0
- package/dist/runVercel.d.ts +34 -0
- package/dist/runVercel.d.ts.map +1 -0
- package/dist/runVercel.js +52 -0
- package/dist/runVercel.js.map +1 -0
- package/dist/safePath.d.ts +2 -0
- package/dist/safePath.d.ts.map +1 -0
- package/dist/safePath.js +35 -0
- package/dist/safePath.js.map +1 -0
- package/dist/safeTargetPath.d.ts +2 -0
- package/dist/safeTargetPath.d.ts.map +1 -0
- package/dist/safeTargetPath.js +29 -0
- package/dist/safeTargetPath.js.map +1 -0
- package/dist/schemas.d.ts +106 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +85 -0
- package/dist/schemas.js.map +1 -0
- package/dist/selectNetwork.d.ts +26 -0
- package/dist/selectNetwork.d.ts.map +1 -0
- package/dist/selectNetwork.js +48 -0
- package/dist/selectNetwork.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +192 -0
- package/dist/setup.js.map +1 -0
- package/dist/setupReport.d.ts +8 -0
- package/dist/setupReport.d.ts.map +1 -0
- package/dist/setupReport.js +2 -0
- package/dist/setupReport.js.map +1 -0
- package/dist/setupVercel.d.ts +9 -0
- package/dist/setupVercel.d.ts.map +1 -0
- package/dist/setupVercel.js +72 -0
- package/dist/setupVercel.js.map +1 -0
- package/dist/stageSite.d.ts +3 -0
- package/dist/stageSite.d.ts.map +1 -0
- package/dist/stageSite.js +31 -0
- package/dist/stageSite.js.map +1 -0
- package/dist/validateConfig.d.ts +3 -0
- package/dist/validateConfig.d.ts.map +1 -0
- package/dist/validateConfig.js +17 -0
- package/dist/validateConfig.js.map +1 -0
- package/docs/public/commands.md +58 -0
- package/docs/public/llms-full.txt +79 -0
- package/package.json +49 -3
- package/src/addDependency.ts +41 -0
- package/src/apiClient.ts +51 -0
- package/src/checkIdentity.ts +18 -0
- package/src/ciOutputs.ts +47 -0
- package/src/cli.ts +11 -0
- package/src/createCli.ts +118 -0
- package/src/createProject.ts +66 -0
- package/src/createSetupCommand.ts +16 -0
- package/src/deploy.ts +71 -0
- package/src/fail.ts +3 -0
- package/src/index.ts +2 -0
- package/src/inspectReferences.ts +20 -0
- package/src/inspectStaticTree.ts +19 -0
- package/src/packageInfo.ts +10 -0
- package/src/parseSelection.ts +22 -0
- package/src/parseSetupInput.ts +40 -0
- package/src/readSelection.ts +47 -0
- package/src/renderPlan.ts +27 -0
- package/src/renderResult.ts +18 -0
- package/src/resolveRepository.ts +38 -0
- package/src/runProcess.ts +26 -0
- package/src/runTerraform.ts +182 -0
- package/src/runVercel.ts +77 -0
- package/src/safePath.ts +43 -0
- package/src/safeTargetPath.ts +32 -0
- package/src/schemas.ts +126 -0
- package/src/selectNetwork.ts +73 -0
- package/src/setup.ts +277 -0
- package/src/setupReport.ts +11 -0
- package/src/setupVercel.ts +126 -0
- package/src/stageSite.ts +54 -0
- package/src/validateConfig.ts +22 -0
- package/templates/deploy-arc.yaml +73 -0
- package/templates/deploy-vercel.yaml +43 -0
- package/templates/network.yaml +105 -0
- package/templates/vercel-static/main.tf +128 -0
- package/templates/vercel-static/module.yaml +51 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fail } from "./fail.js";
|
|
5
|
+
import { inspectStaticTree } from "./inspectStaticTree.js";
|
|
6
|
+
import { safePath } from "./safePath.js";
|
|
7
|
+
export const stageSite = (root, site, account) => {
|
|
8
|
+
const output = safePath(root, site.output);
|
|
9
|
+
if (!existsSync(output) ||
|
|
10
|
+
!lstatSync(output).isDirectory() ||
|
|
11
|
+
!existsSync(join(output, "index.html")))
|
|
12
|
+
fail(`Static output needs index.html: ${site.output}`);
|
|
13
|
+
inspectStaticTree(output);
|
|
14
|
+
const stage = mkdtempSync(join(tmpdir(), "arcnet-static-"));
|
|
15
|
+
try {
|
|
16
|
+
mkdirSync(join(stage, ".vercel/output"), { recursive: true });
|
|
17
|
+
cpSync(output, join(stage, ".vercel/output/static"), { recursive: true });
|
|
18
|
+
writeFileSync(join(stage, ".vercel/output/config.json"), JSON.stringify(site.outputConfig));
|
|
19
|
+
writeFileSync(join(stage, ".vercel/project.json"), JSON.stringify({
|
|
20
|
+
projectId: site.projectId,
|
|
21
|
+
orgId: account.id,
|
|
22
|
+
projectName: site.projectName,
|
|
23
|
+
}));
|
|
24
|
+
return stage;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
rmSync(stage, { recursive: true, force: true });
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=stageSite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stageSite.js","sourceRoot":"","sources":["../src/stageSite.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,UAAU,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,MAAM,EACN,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAY,EACZ,IAAgB,EAChB,OAAsB,EACtB,EAAE;IACF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3C,IACE,CAAC,UAAU,CAAC,MAAM,CAAC;QACnB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE;QAChC,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAEvC,IAAI,CAAC,mCAAmC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAE5D,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,uBAAuB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1E,aAAa,CACX,IAAI,CAAC,KAAK,EAAE,4BAA4B,CAAC,EACzC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAClC,CAAC;QAEF,aAAa,CACX,IAAI,CAAC,KAAK,EAAE,sBAAsB,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC;YACb,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,OAAO,CAAC,EAAE;YACjB,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CACH,CAAC;QAEF,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateConfig.d.ts","sourceRoot":"","sources":["../src/validateConfig.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAG,YAgB7D,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fail } from "./fail.js";
|
|
2
|
+
import { safePath } from "./safePath.js";
|
|
3
|
+
import { vercelConfigSchema } from "./schemas.js";
|
|
4
|
+
export const validateConfig = (input, root) => {
|
|
5
|
+
const config = vercelConfigSchema.parse(input);
|
|
6
|
+
const identities = new Set();
|
|
7
|
+
for (const site of Object.values(config.sites)) {
|
|
8
|
+
for (const id of [site.projectId, site.projectName].filter((id) => Boolean(id))) {
|
|
9
|
+
if (identities.has(id))
|
|
10
|
+
fail("Duplicate Vercel project selection.");
|
|
11
|
+
identities.add(id);
|
|
12
|
+
}
|
|
13
|
+
safePath(root, site.output);
|
|
14
|
+
}
|
|
15
|
+
return config;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=validateConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateConfig.js","sourceRoot":"","sources":["../src/validateConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAc,EAAE,IAAY,EAAgB,EAAE;IAC3E,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CACxD,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAClC,EAAE,CAAC;YACF,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,qCAAqC,CAAC,CAAC;YACpE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QAED,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ArcNet command reference
|
|
2
|
+
|
|
3
|
+
Generated from the CLI command tree.
|
|
4
|
+
|
|
5
|
+
## Global Options
|
|
6
|
+
|
|
7
|
+
| Option | Description |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `-V, --version` | output the version number |
|
|
10
|
+
| `--root <directory>` | Target project root |
|
|
11
|
+
| `--json` | Render one JSON result |
|
|
12
|
+
| `--jsonl` | Render structured completion events |
|
|
13
|
+
| `--noninteractive` | Run without prompts (all commands are noninteractive) |
|
|
14
|
+
|
|
15
|
+
## `arcnet version`
|
|
16
|
+
|
|
17
|
+
Show the installed package version.
|
|
18
|
+
|
|
19
|
+
## `arcnet setup <slug>`
|
|
20
|
+
|
|
21
|
+
Add ArcNet configuration and workflow; pin the CLI in the project manifest.
|
|
22
|
+
|
|
23
|
+
| Option | Description |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `--dry-run` | Report generated files without writing |
|
|
26
|
+
| `--service <name>` | Default container service |
|
|
27
|
+
| `--dockerfile <path>` | Container Dockerfile |
|
|
28
|
+
| `--repository <owner/repo>` | GitHub repository (defaults to origin) |
|
|
29
|
+
| `--services <path>` | Explicit service selection JSON |
|
|
30
|
+
| `--vercel <path>` | Static Vercel references JSON |
|
|
31
|
+
|
|
32
|
+
## `arcnet check`
|
|
33
|
+
|
|
34
|
+
Validate project configuration offline.
|
|
35
|
+
|
|
36
|
+
## `arcnet plan`
|
|
37
|
+
|
|
38
|
+
Plan deployment (Terraform contacts providers; static Vercel stays offline).
|
|
39
|
+
|
|
40
|
+
## `arcnet deploy`
|
|
41
|
+
|
|
42
|
+
Deploy the approved project.
|
|
43
|
+
|
|
44
|
+
## `arcnet preview`
|
|
45
|
+
|
|
46
|
+
Deploy static Vercel sites to preview.
|
|
47
|
+
|
|
48
|
+
## `arcnet inspect`
|
|
49
|
+
|
|
50
|
+
Verify static Vercel project identities remotely.
|
|
51
|
+
|
|
52
|
+
## `arcnet create <site>`
|
|
53
|
+
|
|
54
|
+
Create one explicitly approved Vercel project.
|
|
55
|
+
|
|
56
|
+
## `arcnet ci`
|
|
57
|
+
|
|
58
|
+
Validate deployment identity and write GitHub Actions outputs.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @mp-lb/arcnet
|
|
2
|
+
|
|
3
|
+
ArcNet's TypeScript CLI for installing deployment configuration and deploying projects. The package owns shared runners, validation, workflow templates and the Vercel Terraform module; projects keep configuration and application-specific Dockerfiles.
|
|
4
|
+
|
|
5
|
+
**Release status:** implementation and checks are complete; the first npm publication is blocked by registry authentication. The commands below target the pending `0.1.0` release.
|
|
6
|
+
|
|
7
|
+
From an existing project with Zap, GitHub, and pinned pnpm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm dlx @mp-lb/arcnet@0.1.0 setup hello-world --dry-run
|
|
11
|
+
pnpm dlx @mp-lb/arcnet@0.1.0 setup hello-world
|
|
12
|
+
pnpm install
|
|
13
|
+
pnpm exec arcnet check
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Setup pins this package in the existing root manifest. Commit the resulting lockfile. Deployment starts disabled, and setup never provisions cloud resources. Upgrades use the project's normal dependency workflow.
|
|
17
|
+
|
|
18
|
+
[Project setup](https://github.com/mp-lb/stackforge/blob/main/arcnet/SETUP.md) · [Environment setup](https://github.com/mp-lb/stackforge/blob/main/arcnet/environment/setup.md) · [Audit](https://github.com/mp-lb/stackforge/blob/main/arcnet/AUDIT.md) · [Command reference](docs/public/commands.md)
|
|
19
|
+
|
|
20
|
+
Zap Arc remains the pinned Terraform engine dependency. Environment bootstrap, health checks and teardown are not implemented by this package yet. The static Vercel adapter retains its existing static-only and explicit-project-reference limitations.
|
|
21
|
+
|
|
22
|
+
# ArcNet command reference
|
|
23
|
+
|
|
24
|
+
Generated from the CLI command tree.
|
|
25
|
+
|
|
26
|
+
## Global Options
|
|
27
|
+
|
|
28
|
+
| Option | Description |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `-V, --version` | output the version number |
|
|
31
|
+
| `--root <directory>` | Target project root |
|
|
32
|
+
| `--json` | Render one JSON result |
|
|
33
|
+
| `--jsonl` | Render structured completion events |
|
|
34
|
+
| `--noninteractive` | Run without prompts (all commands are noninteractive) |
|
|
35
|
+
|
|
36
|
+
## `arcnet version`
|
|
37
|
+
|
|
38
|
+
Show the installed package version.
|
|
39
|
+
|
|
40
|
+
## `arcnet setup <slug>`
|
|
41
|
+
|
|
42
|
+
Add ArcNet configuration and workflow; pin the CLI in the project manifest.
|
|
43
|
+
|
|
44
|
+
| Option | Description |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `--dry-run` | Report generated files without writing |
|
|
47
|
+
| `--service <name>` | Default container service |
|
|
48
|
+
| `--dockerfile <path>` | Container Dockerfile |
|
|
49
|
+
| `--repository <owner/repo>` | GitHub repository (defaults to origin) |
|
|
50
|
+
| `--services <path>` | Explicit service selection JSON |
|
|
51
|
+
| `--vercel <path>` | Static Vercel references JSON |
|
|
52
|
+
|
|
53
|
+
## `arcnet check`
|
|
54
|
+
|
|
55
|
+
Validate project configuration offline.
|
|
56
|
+
|
|
57
|
+
## `arcnet plan`
|
|
58
|
+
|
|
59
|
+
Plan deployment (Terraform contacts providers; static Vercel stays offline).
|
|
60
|
+
|
|
61
|
+
## `arcnet deploy`
|
|
62
|
+
|
|
63
|
+
Deploy the approved project.
|
|
64
|
+
|
|
65
|
+
## `arcnet preview`
|
|
66
|
+
|
|
67
|
+
Deploy static Vercel sites to preview.
|
|
68
|
+
|
|
69
|
+
## `arcnet inspect`
|
|
70
|
+
|
|
71
|
+
Verify static Vercel project identities remotely.
|
|
72
|
+
|
|
73
|
+
## `arcnet create <site>`
|
|
74
|
+
|
|
75
|
+
Create one explicitly approved Vercel project.
|
|
76
|
+
|
|
77
|
+
## `arcnet ci`
|
|
78
|
+
|
|
79
|
+
Validate deployment identity and write GitHub Actions outputs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mp-lb/arcnet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ArcNet project installation and deployment CLI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"arcnet": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"templates",
|
|
24
|
+
"README.md",
|
|
25
|
+
"docs/public",
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/mp-lb/tools.git",
|
|
31
|
+
"directory": "packages/arcnet"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@mp-lb/zapper": "0.21.0",
|
|
38
|
+
"commander": "14.0.3",
|
|
39
|
+
"yaml": "2.8.1",
|
|
40
|
+
"zod": "4.4.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@mp-lb/cli-docs": "0.0.1"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rm -rf dist && tsc --project tsconfig.json && chmod +x dist/cli.js",
|
|
47
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
48
|
+
"test": "pnpm build && node --import tsx --test test/*.test.ts && pnpm docs:check",
|
|
49
|
+
"docs:gen": "pnpm --filter @mp-lb/cli-docs build && tsx scripts/generateDocs.ts",
|
|
50
|
+
"docs:check": "pnpm --filter @mp-lb/cli-docs build && tsx scripts/generateDocs.ts --check"
|
|
51
|
+
}
|
|
6
52
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { packageVersion } from "./packageInfo.js";
|
|
5
|
+
|
|
6
|
+
const manifestSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
9
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
10
|
+
})
|
|
11
|
+
.passthrough();
|
|
12
|
+
|
|
13
|
+
export const addDependency = (path: string) => {
|
|
14
|
+
const manifest = manifestSchema.parse(JSON.parse(readFileSync(path, "utf8")));
|
|
15
|
+
|
|
16
|
+
const existing =
|
|
17
|
+
manifest.dependencies?.["@mp-lb/arcnet"] ??
|
|
18
|
+
manifest.devDependencies?.["@mp-lb/arcnet"];
|
|
19
|
+
|
|
20
|
+
if (existing && existing !== packageVersion)
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Existing ArcNet dependency differs; review its version before setup.",
|
|
23
|
+
);
|
|
24
|
+
if (!manifest.dependencies?.["@mp-lb/arcnet"])
|
|
25
|
+
manifest.devDependencies = {
|
|
26
|
+
...manifest.devDependencies,
|
|
27
|
+
"@mp-lb/arcnet": packageVersion,
|
|
28
|
+
};
|
|
29
|
+
return JSON.stringify(manifest, null, 2) + "\n";
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const hasDependency = (root: string) => {
|
|
33
|
+
const manifest = manifestSchema.parse(
|
|
34
|
+
JSON.parse(readFileSync(join(root, "package.json"), "utf8")),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return Boolean(
|
|
38
|
+
manifest.dependencies?.["@mp-lb/arcnet"] ??
|
|
39
|
+
manifest.devDependencies?.["@mp-lb/arcnet"],
|
|
40
|
+
);
|
|
41
|
+
};
|
package/src/apiClient.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { fail } from "./fail.js";
|
|
2
|
+
import type { VercelRequest } from "./schemas.js";
|
|
3
|
+
import { vercelResponseSchema } from "./schemas.js";
|
|
4
|
+
|
|
5
|
+
export const apiClient = (
|
|
6
|
+
token: string | undefined,
|
|
7
|
+
fetcher: typeof fetch = fetch,
|
|
8
|
+
): VercelRequest => {
|
|
9
|
+
if (!token)
|
|
10
|
+
fail(
|
|
11
|
+
"VERCEL_TOKEN is required for remote operations; check and plan remain offline.",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return async (method, path, account, body) => {
|
|
15
|
+
const url = new URL(path, "https://api.vercel.com");
|
|
16
|
+
if (account.type === "team") url.searchParams.set("teamId", account.id);
|
|
17
|
+
let response;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
response = await fetcher(url, {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${token}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
27
|
+
signal: AbortSignal.timeout(30000),
|
|
28
|
+
});
|
|
29
|
+
} catch {
|
|
30
|
+
fail(
|
|
31
|
+
method === "POST"
|
|
32
|
+
? "Creation outcome uncertain. Inspect the named project before retrying; no automatic retry was attempted."
|
|
33
|
+
: "Vercel read failed; no changes attempted.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (response.status === 404 && method === "GET") return null;
|
|
38
|
+
if (!response.ok)
|
|
39
|
+
fail(
|
|
40
|
+
`Vercel ${method} failed (HTTP ${response.status}). Check scope/access; response content omitted.`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return vercelResponseSchema.parse(await response.json());
|
|
45
|
+
} catch {
|
|
46
|
+
fail(
|
|
47
|
+
"Vercel returned an invalid response; inspect the project before retrying.",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fail } from "./fail.js";
|
|
2
|
+
import type { VercelAccount, VercelResponse, VercelSite } from "./schemas.js";
|
|
3
|
+
|
|
4
|
+
export const checkIdentity = (
|
|
5
|
+
project: VercelResponse | null,
|
|
6
|
+
site: VercelSite,
|
|
7
|
+
account: VercelAccount,
|
|
8
|
+
) => {
|
|
9
|
+
if (
|
|
10
|
+
!project ||
|
|
11
|
+
project.id !== site.projectId ||
|
|
12
|
+
project.name !== site.projectName ||
|
|
13
|
+
project.accountId !== account.id
|
|
14
|
+
)
|
|
15
|
+
fail(
|
|
16
|
+
`Project identity/scope mismatch for ${site.projectName}. No deployment attempted.`,
|
|
17
|
+
);
|
|
18
|
+
};
|
package/src/ciOutputs.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { terraformConfigSchema } from "./schemas.js";
|
|
6
|
+
import { validateConfig } from "./validateConfig.js";
|
|
7
|
+
|
|
8
|
+
export const ciOutputs = (root: string): Record<string, string> => {
|
|
9
|
+
const input: unknown = JSON.parse(
|
|
10
|
+
readFileSync(join(root, ".arcnet/project.json"), "utf8"),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
z.object({ mode: z.string().optional() }).parse(input).mode === "vercel"
|
|
15
|
+
) {
|
|
16
|
+
const c = validateConfig(input, root);
|
|
17
|
+
return {
|
|
18
|
+
approved: String(
|
|
19
|
+
c.approved &&
|
|
20
|
+
Object.values(c.sites).every((s) => s.mode === "existing"),
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const c = terraformConfigSchema.parse(input);
|
|
26
|
+
|
|
27
|
+
const n = z
|
|
28
|
+
.object({ region: z.string().regex(/^[a-z]+-[a-z]+[0-9]$/) })
|
|
29
|
+
.parse(parse(readFileSync(join(root, ".arcnet/network.yaml"), "utf8")));
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
c.approved &&
|
|
33
|
+
(!/^projects\/\d+\/locations\/global\/workloadIdentityPools\/github\/providers\/github-actions$/.test(
|
|
34
|
+
c.workloadIdentityProvider,
|
|
35
|
+
) ||
|
|
36
|
+
!/^arc-deploy@[a-z][a-z0-9-]+\.iam\.gserviceaccount\.com$/.test(
|
|
37
|
+
c.serviceAccount,
|
|
38
|
+
))
|
|
39
|
+
)
|
|
40
|
+
throw new Error("Complete WIF configuration before approval.");
|
|
41
|
+
return {
|
|
42
|
+
approved: String(c.approved),
|
|
43
|
+
provider: c.approved ? c.workloadIdentityProvider : "",
|
|
44
|
+
account: c.approved ? c.serviceAccount : "",
|
|
45
|
+
registry: `${n.region}-docker.pkg.dev`,
|
|
46
|
+
};
|
|
47
|
+
};
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createCli } from "./createCli.js";
|
|
3
|
+
await createCli()
|
|
4
|
+
.parseAsync()
|
|
5
|
+
.catch((error: unknown) => {
|
|
6
|
+
process.stderr.write(
|
|
7
|
+
`ArcNet: ${error instanceof Error ? error.message : "Command failed"}\n`,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
});
|
package/src/createCli.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { appendFileSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ciOutputs } from "./ciOutputs.js";
|
|
6
|
+
import { createSetupCommand } from "./createSetupCommand.js";
|
|
7
|
+
import { packageVersion } from "./packageInfo.js";
|
|
8
|
+
import { renderResult } from "./renderResult.js";
|
|
9
|
+
import { runTerraform } from "./runTerraform.js";
|
|
10
|
+
import { runVercel } from "./runVercel.js";
|
|
11
|
+
import { setup } from "./setup.js";
|
|
12
|
+
|
|
13
|
+
export const createCli = () => {
|
|
14
|
+
const cli = new Command("arcnet")
|
|
15
|
+
.description("Install and deploy ArcNet projects.")
|
|
16
|
+
.version(packageVersion)
|
|
17
|
+
.option("--root <directory>", "Target project root", process.cwd())
|
|
18
|
+
.option("--json", "Render one JSON result")
|
|
19
|
+
.option("--jsonl", "Render structured completion events")
|
|
20
|
+
.option(
|
|
21
|
+
"--noninteractive",
|
|
22
|
+
"Run without prompts (all commands are noninteractive)",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
cli.hook("preAction", () => {
|
|
26
|
+
if (cli.opts().json && cli.opts().jsonl)
|
|
27
|
+
throw new Error("Choose either --json or --jsonl.");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const output = (result: unknown) => {
|
|
31
|
+
process.stdout.write(renderResult(result, cli.opts()) + "\n");
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
cli
|
|
35
|
+
.command("version")
|
|
36
|
+
.description("Show the installed package version.")
|
|
37
|
+
.action(() => output({ version: packageVersion }));
|
|
38
|
+
|
|
39
|
+
const install = createSetupCommand();
|
|
40
|
+
install.action(() =>
|
|
41
|
+
output({
|
|
42
|
+
...setup(
|
|
43
|
+
install.args.concat(
|
|
44
|
+
install.options.flatMap((option) => {
|
|
45
|
+
const key = option.attributeName();
|
|
46
|
+
if (install.getOptionValueSource(key) !== "cli") return [];
|
|
47
|
+
const value: unknown = install.opts()[key];
|
|
48
|
+
return value === true
|
|
49
|
+
? [option.long!]
|
|
50
|
+
: [option.long!, String(value)];
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
realpathSync(cli.opts().root),
|
|
54
|
+
),
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
cli.addCommand(install);
|
|
59
|
+
|
|
60
|
+
for (const [name, description] of Object.entries({
|
|
61
|
+
check: "Validate project configuration offline.",
|
|
62
|
+
plan: "Plan deployment (Terraform contacts providers; static Vercel stays offline).",
|
|
63
|
+
deploy: "Deploy the approved project.",
|
|
64
|
+
preview: "Deploy static Vercel sites to preview.",
|
|
65
|
+
inspect: "Verify static Vercel project identities remotely.",
|
|
66
|
+
create: "Create one explicitly approved Vercel project.",
|
|
67
|
+
})) {
|
|
68
|
+
const command = cli.command(name).description(description);
|
|
69
|
+
if (name === "create") command.argument("<site>", "Configured site name");
|
|
70
|
+
command.action(async (site: unknown) => {
|
|
71
|
+
const root = realpathSync(cli.opts().root);
|
|
72
|
+
|
|
73
|
+
const mode = z
|
|
74
|
+
.object({ mode: z.string().optional() })
|
|
75
|
+
.parse(
|
|
76
|
+
JSON.parse(readFileSync(join(root, ".arcnet/project.json"), "utf8")),
|
|
77
|
+
).mode;
|
|
78
|
+
|
|
79
|
+
const machine = Boolean(cli.opts().json || cli.opts().jsonl);
|
|
80
|
+
if (mode === "vercel")
|
|
81
|
+
output(
|
|
82
|
+
await runVercel(
|
|
83
|
+
name,
|
|
84
|
+
root,
|
|
85
|
+
typeof site === "string" ? site : undefined,
|
|
86
|
+
machine,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
else if (name === "check" || name === "plan" || name === "deploy")
|
|
90
|
+
output(runTerraform(name, root, machine));
|
|
91
|
+
else
|
|
92
|
+
throw new Error(
|
|
93
|
+
`${name} is only supported for static Vercel references.`,
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
cli
|
|
99
|
+
.command("ci")
|
|
100
|
+
.description(
|
|
101
|
+
"Validate deployment identity and write GitHub Actions outputs.",
|
|
102
|
+
)
|
|
103
|
+
.action(() => {
|
|
104
|
+
const outputs = ciOutputs(realpathSync(cli.opts().root));
|
|
105
|
+
if (!process.env.GITHUB_OUTPUT)
|
|
106
|
+
throw new Error("GITHUB_OUTPUT is required for the ci command.");
|
|
107
|
+
appendFileSync(
|
|
108
|
+
process.env.GITHUB_OUTPUT,
|
|
109
|
+
Object.entries(outputs)
|
|
110
|
+
.map(([key, value]) => `${key}=${value}\n`)
|
|
111
|
+
.join(""),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
output(outputs);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return cli;
|
|
118
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { checkIdentity } from "./checkIdentity.js";
|
|
2
|
+
import { fail } from "./fail.js";
|
|
3
|
+
import type { VercelConfig, VercelRequest } from "./schemas.js";
|
|
4
|
+
|
|
5
|
+
export const createProject = async (
|
|
6
|
+
config: VercelConfig,
|
|
7
|
+
name: string,
|
|
8
|
+
request: VercelRequest,
|
|
9
|
+
) => {
|
|
10
|
+
const site = config.sites[name];
|
|
11
|
+
if (!site) fail("Choose a configured site.");
|
|
12
|
+
|
|
13
|
+
if (site.mode === "existing") {
|
|
14
|
+
checkIdentity(
|
|
15
|
+
await request("GET", `/v9/projects/${site.projectId}`, config.account),
|
|
16
|
+
site,
|
|
17
|
+
config.account,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (site.creationApproved !== true)
|
|
24
|
+
fail(
|
|
25
|
+
"Project creation disabled. Obtain explicit target/cost approval and set creationApproved=true for this site.",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (config.account.type === "personal") {
|
|
29
|
+
const who = await request("GET", "/v2/user", config.account);
|
|
30
|
+
if (who?.user?.id !== config.account.id)
|
|
31
|
+
fail(
|
|
32
|
+
"Token personal account does not match configured account; no project created.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
await request(
|
|
38
|
+
"GET",
|
|
39
|
+
`/v9/projects/${encodeURIComponent(site.projectName)}`,
|
|
40
|
+
config.account,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
fail(
|
|
44
|
+
`Project name ${site.projectName} already exists. Set an explicit existing reference after verifying its ID; no duplicate project was created.`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const created = await request("POST", "/v11/projects", config.account, {
|
|
48
|
+
name: site.projectName,
|
|
49
|
+
framework: null,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
!created ||
|
|
54
|
+
!/^prj_[A-Za-z0-9]+$/.test(created.id ?? "") ||
|
|
55
|
+
created.accountId !== config.account.id ||
|
|
56
|
+
created.name !== site.projectName
|
|
57
|
+
)
|
|
58
|
+
fail(
|
|
59
|
+
"Creation response identity mismatch. Inspect the account before retrying.",
|
|
60
|
+
);
|
|
61
|
+
const next = structuredClone(config);
|
|
62
|
+
next.approved = false;
|
|
63
|
+
next.sites[name] = { ...site, mode: "existing", projectId: created.id! };
|
|
64
|
+
delete next.sites[name]!.creationApproved;
|
|
65
|
+
return next;
|
|
66
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export const createSetupCommand = () =>
|
|
3
|
+
new Command("setup")
|
|
4
|
+
.description(
|
|
5
|
+
"Add ArcNet configuration and workflow; pin the CLI in the project manifest.",
|
|
6
|
+
)
|
|
7
|
+
.argument("<slug>", "Project slug matching zap.yaml")
|
|
8
|
+
.option("--dry-run", "Report generated files without writing")
|
|
9
|
+
.option("--service <name>", "Default container service", "backend")
|
|
10
|
+
.option("--dockerfile <path>", "Container Dockerfile", ".arcnet/Dockerfile")
|
|
11
|
+
.option(
|
|
12
|
+
"--repository <owner/repo>",
|
|
13
|
+
"GitHub repository (defaults to origin)",
|
|
14
|
+
)
|
|
15
|
+
.option("--services <path>", "Explicit service selection JSON")
|
|
16
|
+
.option("--vercel <path>", "Static Vercel references JSON");
|