ras-stack 0.39.5 → 0.41.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/README.md +17 -8
- package/dist/auth/settings.d.ts +1 -1
- package/dist/auth/settings.js +1 -1
- package/dist/auth/settings.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/create/index.d.ts +1 -0
- package/dist/create/index.js +69 -0
- package/dist/create/index.js.map +1 -0
- package/dist/runtime/dev.js +1 -1
- package/dist/runtime/dev.js.map +1 -1
- package/dist/runtime/index.js +77 -13
- package/dist/runtime/index.js.map +1 -1
- package/examples/full-stack/.env.example +16 -0
- package/examples/full-stack/.oxfmtrc.json +7 -0
- package/examples/full-stack/Dockerfile +37 -0
- package/examples/full-stack/Dockerfile.standalone +28 -0
- package/examples/full-stack/centrifugo.json +14 -0
- package/examples/full-stack/dockerignore.template +8 -0
- package/examples/full-stack/drizzle/0000_production_reference.sql +114 -0
- package/examples/full-stack/drizzle/meta/_journal.json +13 -0
- package/examples/full-stack/e2e/full-stack.spec.ts +45 -0
- package/examples/full-stack/gitignore.template +9 -0
- package/examples/full-stack/oxlint.json +4 -0
- package/examples/full-stack/package.json +58 -0
- package/examples/full-stack/playwright.config.ts +7 -0
- package/examples/full-stack/pnpm-workspace.template.yaml +13 -0
- package/examples/full-stack/ras-stack.assets.json +4 -0
- package/examples/full-stack/scripts/containerRuntime.ts +29 -0
- package/examples/full-stack/scripts/database.test.ts +121 -0
- package/examples/full-stack/scripts/database.ts +105 -0
- package/examples/full-stack/src/client/auth.ts +3 -0
- package/examples/full-stack/src/client/queries.ts +4 -0
- package/examples/full-stack/src/client/queryClient.ts +1 -0
- package/examples/full-stack/src/client/useRealtime.ts +20 -0
- package/examples/full-stack/src/posthog.ts +17 -0
- package/examples/full-stack/src/routeTree.gen.ts +230 -0
- package/examples/full-stack/src/router.tsx +17 -0
- package/examples/full-stack/src/routes/__root.tsx +38 -0
- package/examples/full-stack/src/routes/api/auth.$.ts +8 -0
- package/examples/full-stack/src/routes/api/centrifugo.connect.ts +29 -0
- package/examples/full-stack/src/routes/api/health.ts +6 -0
- package/examples/full-stack/src/routes/api/live.ts +5 -0
- package/examples/full-stack/src/routes/api/ready.ts +30 -0
- package/examples/full-stack/src/routes/api/uploads.$id.ts +40 -0
- package/examples/full-stack/src/routes/api/uploads.ts +25 -0
- package/examples/full-stack/src/routes/index.tsx +187 -0
- package/examples/full-stack/src/server/app.test.ts +24 -0
- package/examples/full-stack/src/server/app.ts +172 -0
- package/examples/full-stack/src/server/auth-flow.test.ts +148 -0
- package/examples/full-stack/src/server/auth.ts +60 -0
- package/examples/full-stack/src/server/environment.test.ts +43 -0
- package/examples/full-stack/src/server/environment.ts +67 -0
- package/examples/full-stack/src/server/fns.ts +38 -0
- package/examples/full-stack/src/server/messages.test.ts +38 -0
- package/examples/full-stack/src/server/messages.ts +28 -0
- package/examples/full-stack/src/server/migration.test.ts +34 -0
- package/examples/full-stack/src/server/outbox.test.ts +130 -0
- package/examples/full-stack/src/server/outbox.ts +110 -0
- package/examples/full-stack/src/server/posthog.test.ts +30 -0
- package/examples/full-stack/src/server/rate-limit.test.ts +32 -0
- package/examples/full-stack/src/server/rate-limit.ts +29 -0
- package/examples/full-stack/src/server/rpc.ts +16 -0
- package/examples/full-stack/src/server/schema.ts +134 -0
- package/examples/full-stack/src/server/session.test.ts +56 -0
- package/examples/full-stack/src/server/session.ts +13 -0
- package/examples/full-stack/src/server/uploads.test.ts +142 -0
- package/examples/full-stack/src/server/uploads.ts +199 -0
- package/examples/full-stack/src/start.ts +12 -0
- package/examples/full-stack/src/styles.css +42 -0
- package/examples/full-stack/tsconfig.json +8 -0
- package/examples/full-stack/vite.config.ts +31 -0
- package/examples/full-stack/vitest.config.ts +3 -0
- package/package.json +27 -22
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Applications still configure every upstream library directly. This table describ
|
|
|
54
54
|
|
|
55
55
|
The application keeps schemas, migrations, repositories, routes, authorization, templates, upload rules, realtime payloads, storage, deployment topology, and UI. There is no shared application factory or giant configuration object.
|
|
56
56
|
|
|
57
|
-
The [`examples/full-stack`](examples/full-stack) workspace shows the boundaries together and tests them through `workspace:*`. It is
|
|
57
|
+
The [`examples/full-stack`](examples/full-stack) workspace shows the boundaries together and tests them through `workspace:*`. It is the repository's integration contract and the canonical source for the generated starter; once generated, the application owns its copied code and normal semver dependency.
|
|
58
58
|
|
|
59
59
|
## Pick what you need 🧩
|
|
60
60
|
|
|
@@ -70,7 +70,7 @@ Nodemailer, Centrifuge, `better-sqlite3`, Drizzle, Postgres.js, and `tus-js-clie
|
|
|
70
70
|
pnpm add nodemailer
|
|
71
71
|
pnpm add centrifuge
|
|
72
72
|
pnpm add tus-js-client
|
|
73
|
-
pnpm add better-sqlite3 drizzle-orm
|
|
73
|
+
pnpm add better-auth @better-auth/drizzle-adapter better-sqlite3 drizzle-orm
|
|
74
74
|
pnpm add postgres drizzle-orm
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -87,6 +87,14 @@ Start with the narrowest public entrypoint that owns the repeated mechanic:
|
|
|
87
87
|
| Compiler, lint, CI, release, and preview mechanics | `ras-stack/config/*`, `actions/*`, `.github/workflows/*`, `ras-stack/preview/*` | Triggers, permissions, services, deployment, and verification |
|
|
88
88
|
| Generated repository policy files | `ras policy` | Which policies apply and every declared override |
|
|
89
89
|
|
|
90
|
+
To start from the production reference instead of assembling entrypoints individually:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
pnpm create ras-app my-app
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The `create-ras-app` command delegates to the same scaffold implementation available through `pnpm dlx ras-stack create my-app`. The scaffold includes Better Auth, checked-in migrations, durable uploads, SMTP flows, a transactional realtime outbox, production health/lifecycle behavior, and its tests. It remains ordinary application code rather than a second framework API.
|
|
97
|
+
|
|
90
98
|
## Dokploy previews 🚀
|
|
91
99
|
|
|
92
100
|
Three reusable workflows provide the standard pull-request preview lifecycle:
|
|
@@ -99,12 +107,13 @@ Applications supply only their package, application prefix, domain, port, enviro
|
|
|
99
107
|
|
|
100
108
|
## Guides 📚
|
|
101
109
|
|
|
102
|
-
| Guide
|
|
103
|
-
|
|
|
104
|
-
| [Application primitives](docs/application-primitives.md)
|
|
105
|
-
| [Repository tooling](docs/repository-tooling.md)
|
|
106
|
-
| [PostHog integration](docs/posthog.md)
|
|
107
|
-
| [Full-stack example](docs/full-stack-example.md)
|
|
110
|
+
| Guide | What it covers |
|
|
111
|
+
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| [Application primitives](docs/application-primitives.md) | Authentication, request security, databases, realtime clients, email, uploads, and stateful development resources |
|
|
113
|
+
| [Repository tooling](docs/repository-tooling.md) | TypeScript and Oxlint configuration, generated policy, GitHub Actions, previews, releases, and production runtime composition |
|
|
114
|
+
| [PostHog integration](docs/posthog.md) | Browser/server setup, identity and session correlation, ingest proxying, shutdown, coverage declarations, and source-map responsibility |
|
|
115
|
+
| [Full-stack example](docs/full-stack-example.md) | The `workspace:*` integration contract, local development, production container, and two-browser journey |
|
|
116
|
+
| [Production operations](docs/production-reference-operations.md) | Migration, backup/restore, rollback, configuration, proxy, shutdown, and supply-chain boundaries |
|
|
108
117
|
|
|
109
118
|
## Development 🛠️
|
|
110
119
|
|
package/dist/auth/settings.d.ts
CHANGED
package/dist/auth/settings.js
CHANGED
|
@@ -10,7 +10,7 @@ export function standardRateLimitOptions(customRules = {}) {
|
|
|
10
10
|
customRules: {
|
|
11
11
|
'/sign-in/email': { window: 60, max: 20 },
|
|
12
12
|
'/sign-up/email': { window: 60, max: 15 },
|
|
13
|
-
'/
|
|
13
|
+
'/request-password-reset': { window: 60, max: 5 },
|
|
14
14
|
...customRules,
|
|
15
15
|
},
|
|
16
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/auth/settings.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,sBAAsB;IACpC,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,WAAW,GAAkC,EAAE;IACtF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,UAAmB;QAC5B,MAAM,EAAE,EAAE;QACV,GAAG,EAAE,GAAG;QACR,WAAW,EAAE;YACX,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACzC,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACzC,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/auth/settings.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,sBAAsB;IACpC,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,WAAW,GAAkC,EAAE;IACtF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,UAAmB;QAC5B,MAAM,EAAE,EAAE;QACV,GAAG,EAAE,GAAG;QACR,WAAW,EAAE;YACX,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACzC,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACzC,yBAAyB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE;YACjD,GAAG,WAAW;SACf;KACF,CAAA;AACH,CAAC","sourcesContent":["export type RateLimitRule = { window: number; max: number }\n\nexport function standardSessionOptions() {\n return { expiresIn: 60 * 60 * 24 * 90, updateAge: 60 * 60 * 24 }\n}\n\nexport function standardRateLimitOptions(customRules: Record<string, RateLimitRule> = {}) {\n return {\n enabled: true,\n storage: 'database' as const,\n window: 60,\n max: 120,\n customRules: {\n '/sign-in/email': { window: 60, max: 20 },\n '/sign-up/email': { window: 60, max: 15 },\n '/request-password-reset': { window: 60, max: 5 },\n ...customRules,\n },\n }\n}\n"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runAssetsCli } from './build/cli.js';
|
|
3
|
+
import { runCreateCli } from './create/index.js';
|
|
3
4
|
import { runInitCli } from './policy/init-cli.js';
|
|
4
5
|
import { runPolicyCli } from './policy/cli.js';
|
|
5
6
|
import { runPreviewCli } from './preview/cli.js';
|
|
6
7
|
import { runRealtimeCli } from './runtime/dev-cli.js';
|
|
7
8
|
declare const commands: {
|
|
8
9
|
readonly assets: typeof runAssetsCli;
|
|
10
|
+
readonly create: typeof runCreateCli;
|
|
9
11
|
readonly init: typeof runInitCli;
|
|
10
12
|
readonly policy: typeof runPolicyCli;
|
|
11
13
|
readonly preview: typeof runPreviewCli;
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runAssetsCli } from './build/cli.js';
|
|
3
|
+
import { runCreateCli } from './create/index.js';
|
|
3
4
|
import { runInitCli } from './policy/init-cli.js';
|
|
4
5
|
import { runPolicyCli } from './policy/cli.js';
|
|
5
6
|
import { runPreviewCli } from './preview/cli.js';
|
|
6
7
|
import { runRealtimeCli } from './runtime/dev-cli.js';
|
|
7
8
|
const commands = {
|
|
8
9
|
assets: runAssetsCli,
|
|
10
|
+
create: runCreateCli,
|
|
9
11
|
init: runInitCli,
|
|
10
12
|
policy: runPolicyCli,
|
|
11
13
|
preview: runPreviewCli,
|
|
@@ -20,7 +22,7 @@ export function parseRasCommand(arguments_) {
|
|
|
20
22
|
export async function runRasCli(arguments_) {
|
|
21
23
|
const parsed = parseRasCommand(arguments_);
|
|
22
24
|
if (!parsed) {
|
|
23
|
-
console.error('usage: ras <assets|init|policy|preview|realtime> [arguments]');
|
|
25
|
+
console.error('usage: ras <assets|create|init|policy|preview|realtime> [arguments]');
|
|
24
26
|
process.exitCode = 2;
|
|
25
27
|
return;
|
|
26
28
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,QAAQ,GAAG;IACf,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;CAChB,CAAA;AAIV,MAAM,UAAU,eAAe,CAAC,UAAoB;IAClD,MAAM,CAAC,OAAO,EAAE,GAAG,aAAa,CAAC,GAAG,UAAU,CAAA;IAC9C,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACpE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,OAAqB,EAAE,CAAA;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAoB;IAClD,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,QAAQ,GAAG;IACf,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;CAChB,CAAA;AAIV,MAAM,UAAU,eAAe,CAAC,UAAoB;IAClD,MAAM,CAAC,OAAO,EAAE,GAAG,aAAa,CAAC,GAAG,UAAU,CAAA;IAC9C,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACpE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,OAAqB,EAAE,CAAA;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAoB;IAClD,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAA;QACpF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QACpB,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;AAClD,CAAC;AAED,IAAI,OAAO,IAAI,CAAC,IAAI;IAAE,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\nimport { runAssetsCli } from './build/cli.js'\nimport { runCreateCli } from './create/index.js'\nimport { runInitCli } from './policy/init-cli.js'\nimport { runPolicyCli } from './policy/cli.js'\nimport { runPreviewCli } from './preview/cli.js'\nimport { runRealtimeCli } from './runtime/dev-cli.js'\n\nconst commands = {\n assets: runAssetsCli,\n create: runCreateCli,\n init: runInitCli,\n policy: runPolicyCli,\n preview: runPreviewCli,\n realtime: runRealtimeCli,\n} as const\n\nexport type RasCommand = keyof typeof commands\n\nexport function parseRasCommand(arguments_: string[]): { arguments: string[]; command: RasCommand } | undefined {\n const [command, ...argumentsRest] = arguments_\n if (!(command && Object.hasOwn(commands, command))) return undefined\n return { arguments: argumentsRest, command: command as RasCommand }\n}\n\nexport async function runRasCli(arguments_: string[]): Promise<void> {\n const parsed = parseRasCommand(arguments_)\n if (!parsed) {\n console.error('usage: ras <assets|create|init|policy|preview|realtime> [arguments]')\n process.exitCode = 2\n return\n }\n await commands[parsed.command](parsed.arguments)\n}\n\nif (import.meta.main) await runRasCli(process.argv.slice(2))\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCreateCli(arguments_: string[]): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { copyFile, cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function runCreateCli(arguments_) {
|
|
4
|
+
const dryRun = arguments_.includes('--dry-run');
|
|
5
|
+
const positional = arguments_.filter((argument) => argument !== '--dry-run');
|
|
6
|
+
if (positional.length !== 1) {
|
|
7
|
+
console.error('usage: ras create <directory> [--dry-run]');
|
|
8
|
+
process.exitCode = 2;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const destination = path.resolve(positional[0]);
|
|
12
|
+
const source = path.resolve(import.meta.dirname, '../../examples/full-stack');
|
|
13
|
+
await assertSource(source);
|
|
14
|
+
await assertEmptyDestination(destination);
|
|
15
|
+
if (dryRun) {
|
|
16
|
+
console.log(destination);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await mkdir(destination, { recursive: true });
|
|
20
|
+
await cp(source, destination, {
|
|
21
|
+
recursive: true,
|
|
22
|
+
filter: (entry) => !excluded(path.relative(source, entry)),
|
|
23
|
+
});
|
|
24
|
+
await materializeTemplate(destination, 'gitignore.template', '.gitignore');
|
|
25
|
+
await materializeTemplate(destination, 'dockerignore.template', '.dockerignore');
|
|
26
|
+
await materializeTemplate(destination, 'pnpm-workspace.template.yaml', 'pnpm-workspace.yaml');
|
|
27
|
+
await copyFile(path.join(destination, 'Dockerfile.standalone'), path.join(destination, 'Dockerfile'));
|
|
28
|
+
await rm(path.join(destination, 'Dockerfile.standalone'));
|
|
29
|
+
const packageFile = path.join(destination, 'package.json');
|
|
30
|
+
const manifest = JSON.parse(await readFile(packageFile, 'utf8'));
|
|
31
|
+
const packageManifest = JSON.parse(await readFile(path.resolve(import.meta.dirname, '../../package.json'), 'utf8'));
|
|
32
|
+
manifest.name = packageName(path.basename(destination));
|
|
33
|
+
manifest.dependencies['ras-stack'] = `^${packageManifest.version}`;
|
|
34
|
+
manifest.scripts.build = manifest.scripts.build.replaceAll('node ../../dist/cli.js', 'ras');
|
|
35
|
+
await writeFile(packageFile, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
36
|
+
console.log(destination);
|
|
37
|
+
}
|
|
38
|
+
async function materializeTemplate(directory, template, output) {
|
|
39
|
+
const source = path.join(directory, template);
|
|
40
|
+
await copyFile(source, path.join(directory, output));
|
|
41
|
+
await rm(source);
|
|
42
|
+
}
|
|
43
|
+
function excluded(relative) {
|
|
44
|
+
const [root] = relative.split(path.sep);
|
|
45
|
+
return ['.output', '.data', 'node_modules', 'test-results'].includes(root ?? '');
|
|
46
|
+
}
|
|
47
|
+
async function assertSource(source) {
|
|
48
|
+
if (!(await stat(path.join(source, 'package.json'))).isFile())
|
|
49
|
+
throw new Error('The packaged full-stack starter is missing');
|
|
50
|
+
}
|
|
51
|
+
async function assertEmptyDestination(destination) {
|
|
52
|
+
try {
|
|
53
|
+
const info = await stat(destination);
|
|
54
|
+
if (!info.isDirectory() || (await readdir(destination)).length > 0)
|
|
55
|
+
throw new Error(`Destination must be an empty directory: ${destination}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code !== 'ENOENT')
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function packageName(name) {
|
|
63
|
+
const normalized = name
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.replaceAll(/[^a-z\d._-]/g, '-')
|
|
66
|
+
.replaceAll(/^-+|-+$/g, '');
|
|
67
|
+
return normalized || 'ras-stack-app';
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyFile, cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport async function runCreateCli(arguments_: string[]) {\n const dryRun = arguments_.includes('--dry-run')\n const positional = arguments_.filter((argument) => argument !== '--dry-run')\n if (positional.length !== 1) {\n console.error('usage: ras create <directory> [--dry-run]')\n process.exitCode = 2\n return\n }\n const destination = path.resolve(positional[0]!)\n const source = path.resolve(import.meta.dirname, '../../examples/full-stack')\n await assertSource(source)\n await assertEmptyDestination(destination)\n if (dryRun) {\n console.log(destination)\n return\n }\n await mkdir(destination, { recursive: true })\n await cp(source, destination, {\n recursive: true,\n filter: (entry) => !excluded(path.relative(source, entry)),\n })\n await materializeTemplate(destination, 'gitignore.template', '.gitignore')\n await materializeTemplate(destination, 'dockerignore.template', '.dockerignore')\n await materializeTemplate(destination, 'pnpm-workspace.template.yaml', 'pnpm-workspace.yaml')\n await copyFile(path.join(destination, 'Dockerfile.standalone'), path.join(destination, 'Dockerfile'))\n await rm(path.join(destination, 'Dockerfile.standalone'))\n const packageFile = path.join(destination, 'package.json')\n const manifest = JSON.parse(await readFile(packageFile, 'utf8')) as {\n name: string\n dependencies: Record<string, string>\n scripts: Record<string, string>\n }\n const packageManifest = JSON.parse(await readFile(path.resolve(import.meta.dirname, '../../package.json'), 'utf8')) as { version: string }\n manifest.name = packageName(path.basename(destination))\n manifest.dependencies['ras-stack'] = `^${packageManifest.version}`\n manifest.scripts.build = manifest.scripts.build!.replaceAll('node ../../dist/cli.js', 'ras')\n await writeFile(packageFile, `${JSON.stringify(manifest, null, 2)}\\n`)\n console.log(destination)\n}\n\nasync function materializeTemplate(directory: string, template: string, output: string) {\n const source = path.join(directory, template)\n await copyFile(source, path.join(directory, output))\n await rm(source)\n}\n\nfunction excluded(relative: string) {\n const [root] = relative.split(path.sep)\n return ['.output', '.data', 'node_modules', 'test-results'].includes(root ?? '')\n}\n\nasync function assertSource(source: string) {\n if (!(await stat(path.join(source, 'package.json'))).isFile()) throw new Error('The packaged full-stack starter is missing')\n}\n\nasync function assertEmptyDestination(destination: string) {\n try {\n const info = await stat(destination)\n if (!info.isDirectory() || (await readdir(destination)).length > 0)\n throw new Error(`Destination must be an empty directory: ${destination}`)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n }\n}\n\nfunction packageName(name: string) {\n const normalized = name\n .toLowerCase()\n .replaceAll(/[^a-z\\d._-]/g, '-')\n .replaceAll(/^-+|-+$/g, '')\n return normalized || 'ras-stack-app'\n}\n"]}
|
package/dist/runtime/dev.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { access } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
const runtimeImage = 'ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.
|
|
4
|
+
const runtimeImage = 'ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.1@sha256:581a691b59a603685bee8ba9576e80d419b967b63017f82d778fce39ca6f0c0b';
|
|
5
5
|
export async function runRealtimeDev(options, dependencies = {}) {
|
|
6
6
|
const configPath = path.resolve(options.configPath);
|
|
7
7
|
try {
|
package/dist/runtime/dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/runtime/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process'\nimport { access } from 'node:fs/promises'\nimport path from 'node:path'\n\nconst runtimeImage =\n 'ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.0@sha256:5f82b2d53b93465bf91cc1bc90b292e94cbdd823cedd3f432dca94097e59163d'\n\nexport type RealtimeDevOptions = {\n configPath: string\n containerName: string\n port: number\n bindAddress?: string\n origin: string\n secret: string\n detached?: boolean\n connectProxyEndpoint?: string\n}\n\ntype CommandResult = { code: number }\ntype RunCommand = (command: string, args: readonly string[]) => Promise<CommandResult>\ntype RealtimeDevDependencies = { remove?: (containerName: string) => Promise<void>; run?: RunCommand }\n\nexport async function runRealtimeDev(options: RealtimeDevOptions, dependencies: RealtimeDevDependencies = {}) {\n const configPath = path.resolve(options.configPath)\n try {\n await access(configPath)\n } catch (error) {\n throw new Error(`realtime config is unavailable: ${configPath}`, { cause: error })\n }\n const containerName = dockerName(options.containerName)\n const publicPort = tcpPort(options.port)\n const bindAddress = dockerBindAddress(options.bindAddress ?? '127.0.0.1')\n const origin = httpUrl(options.origin, 'origin')\n const secret = required(options.secret, 'secret')\n const connectProxyEndpoint = options.connectProxyEndpoint ? httpEndpoint(options.connectProxyEndpoint) : undefined\n const run = dependencies.run ?? runCommand\n const remove = dependencies.remove ?? removeContainer\n\n try {\n if (options.detached) await remove(containerName)\n const result = await run('docker', [\n 'run',\n '--rm',\n ...(options.detached ? ['-d'] : []),\n '--name',\n containerName,\n '--add-host',\n 'host.docker.internal:host-gateway',\n '-p',\n `${bindAddress}:${publicPort}:8000`,\n '-e',\n `CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY=${secret}`,\n '-e',\n 'CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_ENABLED=true',\n '-e',\n `CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_HMAC_SECRET_KEY=${secret}`,\n '-e',\n `CENTRIFUGO_HTTP_API_KEY=${secret}`,\n '-e',\n `CENTRIFUGO_CLIENT_ALLOWED_ORIGINS=${origin}`,\n '-e',\n `CENTRIFUGO_VAR_PROXY_SECRET=${secret}`,\n ...(connectProxyEndpoint ? ['-e', `CENTRIFUGO_CLIENT_PROXY_CONNECT_ENDPOINT=${connectProxyEndpoint}`] : []),\n '-v',\n `${configPath}:/centrifugo/config.json:ro`,\n runtimeImage,\n '/usr/local/bin/centrifugo',\n '--config=/centrifugo/config.json',\n '--health.enabled',\n ])\n if (result.code !== 0) throw new Error(`Docker exited with status ${result.code}`)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error('Docker is required but was not found', { cause: error })\n }\n throw error\n }\n}\n\nexport function parseRealtimeDevArguments(arguments_: readonly string[]): RealtimeDevOptions {\n const values = new Map<string, string>()\n let detached = false\n for (let index = 0; index < arguments_.length; index += 1) {\n const argument = arguments_[index]!\n if (argument === '--detach') {\n detached = true\n continue\n }\n if (!['--config', '--name', '--port', '--bind-address', '--origin', '--secret', '--connect-proxy-endpoint'].includes(argument)) {\n throw new Error(`unknown argument: ${argument}`)\n }\n const value = arguments_[index + 1]\n if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value`)\n values.set(argument, value)\n index += 1\n }\n const connectProxyEndpoint = values.get('--connect-proxy-endpoint')\n const bindAddress = values.get('--bind-address')\n return {\n configPath: required(values.get('--config'), '--config'),\n containerName: required(values.get('--name'), '--name'),\n port: Number(required(values.get('--port'), '--port')),\n ...(bindAddress ? { bindAddress } : {}),\n origin: required(values.get('--origin'), '--origin'),\n secret: required(values.get('--secret'), '--secret'),\n ...(detached ? { detached: true } : {}),\n ...(connectProxyEndpoint ? { connectProxyEndpoint } : {}),\n }\n}\n\nasync function runCommand(command: string, args: readonly string[]) {\n return new Promise<CommandResult>((resolve, reject) => {\n const child = spawn(command, [...args], { stdio: 'inherit' })\n child.once('error', reject)\n child.once('exit', (code) => resolve({ code: code ?? 1 }))\n })\n}\n\nasync function removeContainer(containerName: string) {\n await new Promise<void>((resolve, reject) => {\n const child = spawn('docker', ['rm', '--force', containerName], { stdio: 'ignore' })\n child.once('error', reject)\n child.once('exit', () => resolve())\n })\n}\n\nfunction required(value: string | undefined, name: string) {\n const normalized = value?.trim()\n if (!normalized) throw new Error(`${name} is required`)\n return normalized\n}\n\nfunction dockerName(value: string) {\n const normalized = required(value, 'containerName')\n if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(normalized)) throw new Error('containerName is invalid')\n return normalized\n}\n\nfunction tcpPort(value: number) {\n if (!Number.isInteger(value) || value < 1 || value > 65_535) throw new Error('port must be a valid TCP port')\n return value\n}\n\nfunction dockerBindAddress(value: string) {\n if (value !== '127.0.0.1' && value !== '0.0.0.0') throw new Error('bindAddress must be 127.0.0.1 or 0.0.0.0')\n return value\n}\n\nfunction httpUrl(value: string, name: string) {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n throw new Error(`${name} must be an HTTP URL`)\n }\n if (\n !['http:', 'https:'].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password ||\n parsed.pathname !== '/' ||\n parsed.search ||\n parsed.hash\n ) {\n throw new Error(`${name} must be an HTTP origin`)\n }\n return parsed.origin\n}\n\nfunction httpEndpoint(value: string) {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n throw new Error('connectProxyEndpoint must be an HTTP URL')\n }\n if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new Error('connectProxyEndpoint must be an HTTP URL without credentials, query, or fragment')\n }\n return parsed.toString()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/runtime/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process'\nimport { access } from 'node:fs/promises'\nimport path from 'node:path'\n\nconst runtimeImage =\n 'ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.1@sha256:581a691b59a603685bee8ba9576e80d419b967b63017f82d778fce39ca6f0c0b'\n\nexport type RealtimeDevOptions = {\n configPath: string\n containerName: string\n port: number\n bindAddress?: string\n origin: string\n secret: string\n detached?: boolean\n connectProxyEndpoint?: string\n}\n\ntype CommandResult = { code: number }\ntype RunCommand = (command: string, args: readonly string[]) => Promise<CommandResult>\ntype RealtimeDevDependencies = { remove?: (containerName: string) => Promise<void>; run?: RunCommand }\n\nexport async function runRealtimeDev(options: RealtimeDevOptions, dependencies: RealtimeDevDependencies = {}) {\n const configPath = path.resolve(options.configPath)\n try {\n await access(configPath)\n } catch (error) {\n throw new Error(`realtime config is unavailable: ${configPath}`, { cause: error })\n }\n const containerName = dockerName(options.containerName)\n const publicPort = tcpPort(options.port)\n const bindAddress = dockerBindAddress(options.bindAddress ?? '127.0.0.1')\n const origin = httpUrl(options.origin, 'origin')\n const secret = required(options.secret, 'secret')\n const connectProxyEndpoint = options.connectProxyEndpoint ? httpEndpoint(options.connectProxyEndpoint) : undefined\n const run = dependencies.run ?? runCommand\n const remove = dependencies.remove ?? removeContainer\n\n try {\n if (options.detached) await remove(containerName)\n const result = await run('docker', [\n 'run',\n '--rm',\n ...(options.detached ? ['-d'] : []),\n '--name',\n containerName,\n '--add-host',\n 'host.docker.internal:host-gateway',\n '-p',\n `${bindAddress}:${publicPort}:8000`,\n '-e',\n `CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY=${secret}`,\n '-e',\n 'CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_ENABLED=true',\n '-e',\n `CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_HMAC_SECRET_KEY=${secret}`,\n '-e',\n `CENTRIFUGO_HTTP_API_KEY=${secret}`,\n '-e',\n `CENTRIFUGO_CLIENT_ALLOWED_ORIGINS=${origin}`,\n '-e',\n `CENTRIFUGO_VAR_PROXY_SECRET=${secret}`,\n ...(connectProxyEndpoint ? ['-e', `CENTRIFUGO_CLIENT_PROXY_CONNECT_ENDPOINT=${connectProxyEndpoint}`] : []),\n '-v',\n `${configPath}:/centrifugo/config.json:ro`,\n runtimeImage,\n '/usr/local/bin/centrifugo',\n '--config=/centrifugo/config.json',\n '--health.enabled',\n ])\n if (result.code !== 0) throw new Error(`Docker exited with status ${result.code}`)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error('Docker is required but was not found', { cause: error })\n }\n throw error\n }\n}\n\nexport function parseRealtimeDevArguments(arguments_: readonly string[]): RealtimeDevOptions {\n const values = new Map<string, string>()\n let detached = false\n for (let index = 0; index < arguments_.length; index += 1) {\n const argument = arguments_[index]!\n if (argument === '--detach') {\n detached = true\n continue\n }\n if (!['--config', '--name', '--port', '--bind-address', '--origin', '--secret', '--connect-proxy-endpoint'].includes(argument)) {\n throw new Error(`unknown argument: ${argument}`)\n }\n const value = arguments_[index + 1]\n if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value`)\n values.set(argument, value)\n index += 1\n }\n const connectProxyEndpoint = values.get('--connect-proxy-endpoint')\n const bindAddress = values.get('--bind-address')\n return {\n configPath: required(values.get('--config'), '--config'),\n containerName: required(values.get('--name'), '--name'),\n port: Number(required(values.get('--port'), '--port')),\n ...(bindAddress ? { bindAddress } : {}),\n origin: required(values.get('--origin'), '--origin'),\n secret: required(values.get('--secret'), '--secret'),\n ...(detached ? { detached: true } : {}),\n ...(connectProxyEndpoint ? { connectProxyEndpoint } : {}),\n }\n}\n\nasync function runCommand(command: string, args: readonly string[]) {\n return new Promise<CommandResult>((resolve, reject) => {\n const child = spawn(command, [...args], { stdio: 'inherit' })\n child.once('error', reject)\n child.once('exit', (code) => resolve({ code: code ?? 1 }))\n })\n}\n\nasync function removeContainer(containerName: string) {\n await new Promise<void>((resolve, reject) => {\n const child = spawn('docker', ['rm', '--force', containerName], { stdio: 'ignore' })\n child.once('error', reject)\n child.once('exit', () => resolve())\n })\n}\n\nfunction required(value: string | undefined, name: string) {\n const normalized = value?.trim()\n if (!normalized) throw new Error(`${name} is required`)\n return normalized\n}\n\nfunction dockerName(value: string) {\n const normalized = required(value, 'containerName')\n if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(normalized)) throw new Error('containerName is invalid')\n return normalized\n}\n\nfunction tcpPort(value: number) {\n if (!Number.isInteger(value) || value < 1 || value > 65_535) throw new Error('port must be a valid TCP port')\n return value\n}\n\nfunction dockerBindAddress(value: string) {\n if (value !== '127.0.0.1' && value !== '0.0.0.0') throw new Error('bindAddress must be 127.0.0.1 or 0.0.0.0')\n return value\n}\n\nfunction httpUrl(value: string, name: string) {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n throw new Error(`${name} must be an HTTP URL`)\n }\n if (\n !['http:', 'https:'].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password ||\n parsed.pathname !== '/' ||\n parsed.search ||\n parsed.hash\n ) {\n throw new Error(`${name} must be an HTTP origin`)\n }\n return parsed.origin\n}\n\nfunction httpEndpoint(value: string) {\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n throw new Error('connectProxyEndpoint must be an HTTP URL')\n }\n if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new Error('connectProxyEndpoint must be an HTTP URL without credentials, query, or fragment')\n }\n return parsed.toString()\n}\n"]}
|
package/dist/runtime/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import { mkdir, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
export { runRealtimeDev } from './dev.js';
|
|
5
5
|
export async function superviseProcesses(processes, options = {}) {
|
|
6
|
+
return runSupervisor(processes, options);
|
|
7
|
+
}
|
|
8
|
+
async function runSupervisor(processes, options, shutdownOrder) {
|
|
6
9
|
if (processes.length === 0)
|
|
7
10
|
throw new Error('at least one runtime process is required');
|
|
8
11
|
const names = new Set();
|
|
@@ -27,12 +30,15 @@ export async function superviseProcesses(processes, options = {}) {
|
|
|
27
30
|
stdio: 'inherit',
|
|
28
31
|
}));
|
|
29
32
|
const children = new Map();
|
|
33
|
+
const exits = new Map();
|
|
30
34
|
let settled = false;
|
|
35
|
+
let phasedShutdown = false;
|
|
36
|
+
let expectedExit;
|
|
31
37
|
let resolveResult;
|
|
32
38
|
const result = new Promise((resolve) => {
|
|
33
39
|
resolveResult = resolve;
|
|
34
40
|
});
|
|
35
|
-
const
|
|
41
|
+
const finishImmediately = async (status) => {
|
|
36
42
|
if (settled)
|
|
37
43
|
return;
|
|
38
44
|
settled = true;
|
|
@@ -41,29 +47,87 @@ export async function superviseProcesses(processes, options = {}) {
|
|
|
41
47
|
await stopChildren([...children.keys()], shutdownTimeoutMs);
|
|
42
48
|
resolveResult(status);
|
|
43
49
|
};
|
|
44
|
-
const
|
|
50
|
+
const finishInPhases = async () => {
|
|
51
|
+
if (settled || phasedShutdown)
|
|
52
|
+
return;
|
|
53
|
+
phasedShutdown = true;
|
|
54
|
+
signalSource.off('SIGINT', onSignal);
|
|
55
|
+
signalSource.off('SIGTERM', onSignal);
|
|
56
|
+
const deadline = Date.now() + shutdownTimeoutMs;
|
|
57
|
+
for (const name of shutdownOrder ?? []) {
|
|
58
|
+
if (settled)
|
|
59
|
+
return;
|
|
60
|
+
const child = [...children].find(([, childName]) => childName === name)?.[0];
|
|
61
|
+
if (!child || !running(child))
|
|
62
|
+
continue;
|
|
63
|
+
expectedExit = child;
|
|
64
|
+
child.kill('SIGTERM');
|
|
65
|
+
// oxlint-disable-next-line no-await-in-loop -- Shutdown phases must remain dependency-ordered.
|
|
66
|
+
const exited = await beforeDeadline(exits.get(child), Math.max(0, deadline - Date.now()));
|
|
67
|
+
expectedExit = undefined;
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
if (!exited) {
|
|
71
|
+
settled = true;
|
|
72
|
+
for (const runningChild of children.keys())
|
|
73
|
+
if (running(runningChild))
|
|
74
|
+
runningChild.kill('SIGKILL');
|
|
75
|
+
resolveResult(0);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
settled = true;
|
|
80
|
+
resolveResult(0);
|
|
81
|
+
};
|
|
82
|
+
const onSignal = () => {
|
|
83
|
+
if (shutdownOrder)
|
|
84
|
+
void finishInPhases();
|
|
85
|
+
else
|
|
86
|
+
void finishImmediately(0);
|
|
87
|
+
};
|
|
45
88
|
signalSource.once('SIGINT', onSignal);
|
|
46
89
|
signalSource.once('SIGTERM', onSignal);
|
|
47
90
|
try {
|
|
48
91
|
for (const specification of processes) {
|
|
49
92
|
const child = spawnProcess(specification);
|
|
50
93
|
children.set(child, specification.name);
|
|
51
|
-
|
|
52
|
-
|
|
94
|
+
let resolveExit;
|
|
95
|
+
exits.set(child, new Promise((resolve) => {
|
|
96
|
+
resolveExit = resolve;
|
|
97
|
+
}));
|
|
98
|
+
child.once('error', () => void finishImmediately(1));
|
|
99
|
+
child.once('exit', (code) => {
|
|
100
|
+
resolveExit();
|
|
101
|
+
if (!phasedShutdown || child !== expectedExit)
|
|
102
|
+
void finishImmediately(code && code > 0 ? code : 1);
|
|
103
|
+
});
|
|
53
104
|
}
|
|
54
105
|
}
|
|
55
106
|
catch (error) {
|
|
56
|
-
await
|
|
107
|
+
await finishImmediately(1);
|
|
57
108
|
throw error;
|
|
58
109
|
}
|
|
59
110
|
return result;
|
|
60
111
|
}
|
|
112
|
+
async function beforeDeadline(exited, timeoutMs) {
|
|
113
|
+
let timer;
|
|
114
|
+
const timeout = new Promise((resolve) => {
|
|
115
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
116
|
+
});
|
|
117
|
+
const result = await Promise.race([exited.then(() => true), timeout]);
|
|
118
|
+
if (timer)
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
function running(child) {
|
|
123
|
+
return child.exitCode === null && child.signalCode === null;
|
|
124
|
+
}
|
|
61
125
|
async function stopChildren(children, timeoutMs) {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
126
|
+
const active = children.filter(running);
|
|
127
|
+
if (active.length === 0)
|
|
64
128
|
return;
|
|
65
|
-
const exited = Promise.all(
|
|
66
|
-
for (const child of
|
|
129
|
+
const exited = Promise.all(active.map((child) => new Promise((resolve) => child.once('exit', () => resolve())))).then(() => 'exited');
|
|
130
|
+
for (const child of active)
|
|
67
131
|
child.kill('SIGTERM');
|
|
68
132
|
let timer;
|
|
69
133
|
const timeout = new Promise((resolve) => {
|
|
@@ -73,8 +137,8 @@ async function stopChildren(children, timeoutMs) {
|
|
|
73
137
|
if (timer)
|
|
74
138
|
clearTimeout(timer);
|
|
75
139
|
if (outcome === 'timeout') {
|
|
76
|
-
for (const child of
|
|
77
|
-
if (child
|
|
140
|
+
for (const child of active)
|
|
141
|
+
if (running(child))
|
|
78
142
|
child.kill('SIGKILL');
|
|
79
143
|
}
|
|
80
144
|
}
|
|
@@ -83,7 +147,7 @@ export async function runRealtimeStack(options) {
|
|
|
83
147
|
const centrifugoConfigPath = absolutePath(options.centrifugo.configPath, 'centrifugo.configPath');
|
|
84
148
|
await mkdir(path.dirname(configPath), { recursive: true });
|
|
85
149
|
await writeFile(configPath, caddyRealtimeProxy(options.caddy.proxy), 'utf8');
|
|
86
|
-
return
|
|
150
|
+
return runSupervisor([
|
|
87
151
|
{ name: 'app', ...options.app },
|
|
88
152
|
{
|
|
89
153
|
name: 'realtime',
|
|
@@ -99,7 +163,7 @@ export async function runRealtimeStack(options) {
|
|
|
99
163
|
...(options.caddy.cwd ? { cwd: options.caddy.cwd } : {}),
|
|
100
164
|
env: { ...options.caddy.env, ...caddyRuntimeEnvironment(options.caddy.runtime) },
|
|
101
165
|
},
|
|
102
|
-
], options.supervisor);
|
|
166
|
+
], options.supervisor ?? {}, ['proxy', 'app', 'realtime']);
|
|
103
167
|
}
|
|
104
168
|
export function centrifugoEnvironment(options) {
|
|
105
169
|
const apiKey = requiredValue(options.apiKey, 'apiKey');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport { runRealtimeDev, type RealtimeDevOptions } from './dev.js'\n\nexport type RuntimeProcess = {\n name: string\n command: string\n args?: readonly string[]\n cwd?: string\n env?: NodeJS.ProcessEnv\n}\n\ntype SignalSource = Pick<NodeJS.Process, 'off' | 'once'>\ntype SpawnProcess = (process: RuntimeProcess) => ChildProcess\n\nexport type SupervisorOptions = {\n shutdownTimeoutMs?: number\n signalSource?: SignalSource\n spawn?: SpawnProcess\n}\n\nexport async function superviseProcesses(processes: readonly RuntimeProcess[], options: SupervisorOptions = {}) {\n if (processes.length === 0) throw new Error('at least one runtime process is required')\n const names = new Set<string>()\n for (const process of processes) {\n if (!process.name.trim()) throw new Error('runtime process names must not be empty')\n if (!process.command.trim()) throw new Error(`runtime process ${process.name} must have a command`)\n if (names.has(process.name)) throw new Error(`duplicate runtime process name: ${process.name}`)\n names.add(process.name)\n }\n\n const signalSource = options.signalSource ?? process\n const shutdownTimeoutMs = options.shutdownTimeoutMs ?? 10_000\n if (!Number.isSafeInteger(shutdownTimeoutMs) || shutdownTimeoutMs < 0) {\n throw new Error('shutdownTimeoutMs must be a non-negative integer')\n }\n const spawnProcess =\n options.spawn ??\n ((specification: RuntimeProcess) =>\n spawn(specification.command, [...(specification.args ?? [])], {\n cwd: specification.cwd,\n env: specification.env ?? process.env,\n stdio: 'inherit',\n }))\n const children = new Map<ChildProcess, string>()\n let settled = false\n let resolveResult!: (value: number) => void\n const result = new Promise<number>((resolve) => {\n resolveResult = resolve\n })\n\n const finish = async (status: number) => {\n if (settled) return\n settled = true\n signalSource.off('SIGINT', onSignal)\n signalSource.off('SIGTERM', onSignal)\n await stopChildren([...children.keys()], shutdownTimeoutMs)\n resolveResult(status)\n }\n const onSignal = () => void finish(0)\n signalSource.once('SIGINT', onSignal)\n signalSource.once('SIGTERM', onSignal)\n\n try {\n for (const specification of processes) {\n const child = spawnProcess(specification)\n children.set(child, specification.name)\n child.once('error', () => void finish(1))\n child.once('exit', (code) => void finish(code && code > 0 ? code : 1))\n }\n } catch (error) {\n await finish(1)\n throw error\n }\n return result\n}\n\nasync function stopChildren(children: ChildProcess[], timeoutMs: number) {\n const running = children.filter((child) => child.exitCode === null && child.signalCode === null)\n if (running.length === 0) return\n const exited = Promise.all(running.map((child) => new Promise<void>((resolve) => child.once('exit', () => resolve())))).then(\n () => 'exited' as const,\n )\n for (const child of running) child.kill('SIGTERM')\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), timeoutMs)\n })\n const outcome = await Promise.race([exited, timeout])\n if (timer) clearTimeout(timer)\n if (outcome === 'timeout') {\n for (const child of running) if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL')\n }\n}\n\nexport type CentrifugoEnvironmentOptions = {\n apiKey: string\n clientTokenSecret?: string\n subscriptionTokenSecret?: string\n allowedOrigins?: string\n redisUrl?: string\n}\n\nexport type RealtimeStackOptions = {\n app: Omit<RuntimeProcess, 'name'>\n centrifugo: {\n command?: string\n configPath: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n environment: CentrifugoEnvironmentOptions\n }\n caddy: {\n command?: string\n configPath: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n proxy?: CaddyRealtimeProxyOptions\n runtime?: CaddyRuntimeEnvironmentOptions\n }\n supervisor?: SupervisorOptions\n}\n\nexport async function runRealtimeStack(options: RealtimeStackOptions) {\n const configPath = absolutePath(options.caddy.configPath, 'caddy.configPath')\n const centrifugoConfigPath = absolutePath(options.centrifugo.configPath, 'centrifugo.configPath')\n await mkdir(path.dirname(configPath), { recursive: true })\n await writeFile(configPath, caddyRealtimeProxy(options.caddy.proxy), 'utf8')\n return superviseProcesses(\n [\n { name: 'app', ...options.app },\n {\n name: 'realtime',\n command: options.centrifugo.command ?? 'centrifugo',\n args: [`--config=${centrifugoConfigPath}`],\n ...(options.centrifugo.cwd ? { cwd: options.centrifugo.cwd } : {}),\n env: { ...options.centrifugo.env, ...centrifugoEnvironment(options.centrifugo.environment) },\n },\n {\n name: 'proxy',\n command: options.caddy.command ?? 'caddy',\n args: ['run', '--config', configPath, '--adapter', 'caddyfile'],\n ...(options.caddy.cwd ? { cwd: options.caddy.cwd } : {}),\n env: { ...options.caddy.env, ...caddyRuntimeEnvironment(options.caddy.runtime) },\n },\n ],\n options.supervisor,\n )\n}\n\nexport function centrifugoEnvironment(options: CentrifugoEnvironmentOptions): NodeJS.ProcessEnv {\n const apiKey = requiredValue(options.apiKey, 'apiKey')\n const clientTokenSecret = options.clientTokenSecret?.trim()\n const subscriptionTokenSecret = options.subscriptionTokenSecret?.trim()\n return {\n CENTRIFUGO_HTTP_API_KEY: apiKey,\n CENTRIFUGO_CLIENT_ALLOWED_ORIGINS: options.allowedOrigins?.trim() || '*',\n CENTRIFUGO_HTTP_SERVER_ADDRESS: '127.0.0.1',\n CENTRIFUGO_HEALTH_ENABLED: 'true',\n ...(clientTokenSecret ? { CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY: clientTokenSecret } : {}),\n ...(subscriptionTokenSecret\n ? {\n CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_ENABLED: 'true',\n CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_HMAC_SECRET_KEY: subscriptionTokenSecret,\n }\n : {}),\n ...(options.redisUrl\n ? { CENTRIFUGO_ENGINE_TYPE: 'redis', CENTRIFUGO_ENGINE_REDIS_ADDRESS: requiredValue(options.redisUrl, 'redisUrl') }\n : {}),\n }\n}\n\nexport type CaddyRuntimeEnvironmentOptions = { configHome?: string; dataHome?: string }\n\nexport function caddyRuntimeEnvironment(options: CaddyRuntimeEnvironmentOptions = {}): NodeJS.ProcessEnv {\n const configHome = absolutePath(options.configHome ?? '/tmp/caddy-config', 'configHome')\n const dataHome = absolutePath(options.dataHome ?? '/tmp/caddy-data', 'dataHome')\n return { XDG_CONFIG_HOME: configHome, XDG_DATA_HOME: dataHome }\n}\n\nexport type CaddyRealtimeProxyOptions = { publicPort?: number; appPort?: number; realtimePort?: number; websocketPath?: string }\n\nexport function caddyRealtimeProxy(options: CaddyRealtimeProxyOptions = {}) {\n const publicPort = port(options.publicPort ?? 3000, 'publicPort')\n const appPort = port(options.appPort ?? 3001, 'appPort')\n const realtimePort = port(options.realtimePort ?? 8000, 'realtimePort')\n const websocketPath = options.websocketPath ?? '/connection/'\n if (!/^\\/[A-Za-z0-9._~/-]+\\/$/.test(websocketPath) || websocketPath.includes('//')) {\n throw new Error('websocketPath must be a normalized absolute directory path')\n }\n return `{\n\\tservers {\n\\t\\ttrusted_proxies static private_ranges\n\\t\\ttrusted_proxies_strict\n\\t}\n}\n\n:${publicPort} {\n\\troute {\n\\t\\t@foreignWebSocketOrigin \\`{path}.startsWith('${websocketPath}') && {http.request.header.Origin} != '' && {http.request.header.Origin} != 'http://' + {http.request.hostport} && {http.request.header.Origin} != 'https://' + {http.request.hostport}\\`\n\\t\\trespond @foreignWebSocketOrigin 403\n\n\\t\\thandle ${websocketPath}* {\n\\t\\t\\treverse_proxy 127.0.0.1:${realtimePort}\n\\t\\t}\n\n\\t\\thandle {\n\\t\\t\\treverse_proxy 127.0.0.1:${appPort}\n\\t\\t}\n\\t}\n}\n`\n}\n\nfunction requiredValue(value: string, name: string) {\n const normalized = value.trim()\n if (!normalized) throw new Error(`${name} is required`)\n return normalized\n}\n\nfunction port(value: number, name: string) {\n if (!Number.isInteger(value) || value < 1 || value > 65_535) throw new Error(`${name} must be a valid TCP port`)\n return value\n}\n\nfunction absolutePath(value: string, name: string) {\n if (!path.isAbsolute(value)) throw new Error(`${name} must be an absolute path`)\n return value\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nexport { runRealtimeDev, type RealtimeDevOptions } from './dev.js'\n\nexport type RuntimeProcess = {\n name: string\n command: string\n args?: readonly string[]\n cwd?: string\n env?: NodeJS.ProcessEnv\n}\n\ntype SignalSource = Pick<NodeJS.Process, 'off' | 'once'>\ntype SpawnProcess = (process: RuntimeProcess) => ChildProcess\n\nexport type SupervisorOptions = {\n shutdownTimeoutMs?: number\n signalSource?: SignalSource\n spawn?: SpawnProcess\n}\n\nexport async function superviseProcesses(processes: readonly RuntimeProcess[], options: SupervisorOptions = {}) {\n return runSupervisor(processes, options)\n}\n\nasync function runSupervisor(processes: readonly RuntimeProcess[], options: SupervisorOptions, shutdownOrder?: readonly string[]) {\n if (processes.length === 0) throw new Error('at least one runtime process is required')\n const names = new Set<string>()\n for (const process of processes) {\n if (!process.name.trim()) throw new Error('runtime process names must not be empty')\n if (!process.command.trim()) throw new Error(`runtime process ${process.name} must have a command`)\n if (names.has(process.name)) throw new Error(`duplicate runtime process name: ${process.name}`)\n names.add(process.name)\n }\n\n const signalSource = options.signalSource ?? process\n const shutdownTimeoutMs = options.shutdownTimeoutMs ?? 10_000\n if (!Number.isSafeInteger(shutdownTimeoutMs) || shutdownTimeoutMs < 0) {\n throw new Error('shutdownTimeoutMs must be a non-negative integer')\n }\n const spawnProcess =\n options.spawn ??\n ((specification: RuntimeProcess) =>\n spawn(specification.command, [...(specification.args ?? [])], {\n cwd: specification.cwd,\n env: specification.env ?? process.env,\n stdio: 'inherit',\n }))\n const children = new Map<ChildProcess, string>()\n const exits = new Map<ChildProcess, Promise<void>>()\n let settled = false\n let phasedShutdown = false\n let expectedExit: ChildProcess | undefined\n let resolveResult!: (value: number) => void\n const result = new Promise<number>((resolve) => {\n resolveResult = resolve\n })\n\n const finishImmediately = async (status: number) => {\n if (settled) return\n settled = true\n signalSource.off('SIGINT', onSignal)\n signalSource.off('SIGTERM', onSignal)\n await stopChildren([...children.keys()], shutdownTimeoutMs)\n resolveResult(status)\n }\n const finishInPhases = async () => {\n if (settled || phasedShutdown) return\n phasedShutdown = true\n signalSource.off('SIGINT', onSignal)\n signalSource.off('SIGTERM', onSignal)\n const deadline = Date.now() + shutdownTimeoutMs\n for (const name of shutdownOrder ?? []) {\n if (settled) return\n const child = [...children].find(([, childName]) => childName === name)?.[0]\n if (!child || !running(child)) continue\n expectedExit = child\n child.kill('SIGTERM')\n // oxlint-disable-next-line no-await-in-loop -- Shutdown phases must remain dependency-ordered.\n const exited = await beforeDeadline(exits.get(child)!, Math.max(0, deadline - Date.now()))\n expectedExit = undefined\n if (settled) return\n if (!exited) {\n settled = true\n for (const runningChild of children.keys()) if (running(runningChild)) runningChild.kill('SIGKILL')\n resolveResult(0)\n return\n }\n }\n settled = true\n resolveResult(0)\n }\n const onSignal = () => {\n if (shutdownOrder) void finishInPhases()\n else void finishImmediately(0)\n }\n signalSource.once('SIGINT', onSignal)\n signalSource.once('SIGTERM', onSignal)\n\n try {\n for (const specification of processes) {\n const child = spawnProcess(specification)\n children.set(child, specification.name)\n let resolveExit!: () => void\n exits.set(\n child,\n new Promise<void>((resolve) => {\n resolveExit = resolve\n }),\n )\n child.once('error', () => void finishImmediately(1))\n child.once('exit', (code) => {\n resolveExit()\n if (!phasedShutdown || child !== expectedExit) void finishImmediately(code && code > 0 ? code : 1)\n })\n }\n } catch (error) {\n await finishImmediately(1)\n throw error\n }\n return result\n}\n\nasync function beforeDeadline(exited: Promise<void>, timeoutMs: number) {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs)\n })\n const result = await Promise.race([exited.then(() => true as const), timeout])\n if (timer) clearTimeout(timer)\n return result\n}\n\nfunction running(child: ChildProcess) {\n return child.exitCode === null && child.signalCode === null\n}\n\nasync function stopChildren(children: ChildProcess[], timeoutMs: number) {\n const active = children.filter(running)\n if (active.length === 0) return\n const exited = Promise.all(active.map((child) => new Promise<void>((resolve) => child.once('exit', () => resolve())))).then(\n () => 'exited' as const,\n )\n for (const child of active) child.kill('SIGTERM')\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), timeoutMs)\n })\n const outcome = await Promise.race([exited, timeout])\n if (timer) clearTimeout(timer)\n if (outcome === 'timeout') {\n for (const child of active) if (running(child)) child.kill('SIGKILL')\n }\n}\n\nexport type CentrifugoEnvironmentOptions = {\n apiKey: string\n clientTokenSecret?: string\n subscriptionTokenSecret?: string\n allowedOrigins?: string\n redisUrl?: string\n}\n\nexport type RealtimeStackOptions = {\n app: Omit<RuntimeProcess, 'name'>\n centrifugo: {\n command?: string\n configPath: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n environment: CentrifugoEnvironmentOptions\n }\n caddy: {\n command?: string\n configPath: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n proxy?: CaddyRealtimeProxyOptions\n runtime?: CaddyRuntimeEnvironmentOptions\n }\n supervisor?: SupervisorOptions\n}\n\nexport async function runRealtimeStack(options: RealtimeStackOptions) {\n const configPath = absolutePath(options.caddy.configPath, 'caddy.configPath')\n const centrifugoConfigPath = absolutePath(options.centrifugo.configPath, 'centrifugo.configPath')\n await mkdir(path.dirname(configPath), { recursive: true })\n await writeFile(configPath, caddyRealtimeProxy(options.caddy.proxy), 'utf8')\n return runSupervisor(\n [\n { name: 'app', ...options.app },\n {\n name: 'realtime',\n command: options.centrifugo.command ?? 'centrifugo',\n args: [`--config=${centrifugoConfigPath}`],\n ...(options.centrifugo.cwd ? { cwd: options.centrifugo.cwd } : {}),\n env: { ...options.centrifugo.env, ...centrifugoEnvironment(options.centrifugo.environment) },\n },\n {\n name: 'proxy',\n command: options.caddy.command ?? 'caddy',\n args: ['run', '--config', configPath, '--adapter', 'caddyfile'],\n ...(options.caddy.cwd ? { cwd: options.caddy.cwd } : {}),\n env: { ...options.caddy.env, ...caddyRuntimeEnvironment(options.caddy.runtime) },\n },\n ],\n options.supervisor ?? {},\n ['proxy', 'app', 'realtime'],\n )\n}\n\nexport function centrifugoEnvironment(options: CentrifugoEnvironmentOptions): NodeJS.ProcessEnv {\n const apiKey = requiredValue(options.apiKey, 'apiKey')\n const clientTokenSecret = options.clientTokenSecret?.trim()\n const subscriptionTokenSecret = options.subscriptionTokenSecret?.trim()\n return {\n CENTRIFUGO_HTTP_API_KEY: apiKey,\n CENTRIFUGO_CLIENT_ALLOWED_ORIGINS: options.allowedOrigins?.trim() || '*',\n CENTRIFUGO_HTTP_SERVER_ADDRESS: '127.0.0.1',\n CENTRIFUGO_HEALTH_ENABLED: 'true',\n ...(clientTokenSecret ? { CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY: clientTokenSecret } : {}),\n ...(subscriptionTokenSecret\n ? {\n CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_ENABLED: 'true',\n CENTRIFUGO_CLIENT_SUBSCRIPTION_TOKEN_HMAC_SECRET_KEY: subscriptionTokenSecret,\n }\n : {}),\n ...(options.redisUrl\n ? { CENTRIFUGO_ENGINE_TYPE: 'redis', CENTRIFUGO_ENGINE_REDIS_ADDRESS: requiredValue(options.redisUrl, 'redisUrl') }\n : {}),\n }\n}\n\nexport type CaddyRuntimeEnvironmentOptions = { configHome?: string; dataHome?: string }\n\nexport function caddyRuntimeEnvironment(options: CaddyRuntimeEnvironmentOptions = {}): NodeJS.ProcessEnv {\n const configHome = absolutePath(options.configHome ?? '/tmp/caddy-config', 'configHome')\n const dataHome = absolutePath(options.dataHome ?? '/tmp/caddy-data', 'dataHome')\n return { XDG_CONFIG_HOME: configHome, XDG_DATA_HOME: dataHome }\n}\n\nexport type CaddyRealtimeProxyOptions = { publicPort?: number; appPort?: number; realtimePort?: number; websocketPath?: string }\n\nexport function caddyRealtimeProxy(options: CaddyRealtimeProxyOptions = {}) {\n const publicPort = port(options.publicPort ?? 3000, 'publicPort')\n const appPort = port(options.appPort ?? 3001, 'appPort')\n const realtimePort = port(options.realtimePort ?? 8000, 'realtimePort')\n const websocketPath = options.websocketPath ?? '/connection/'\n if (!/^\\/[A-Za-z0-9._~/-]+\\/$/.test(websocketPath) || websocketPath.includes('//')) {\n throw new Error('websocketPath must be a normalized absolute directory path')\n }\n return `{\n\\tservers {\n\\t\\ttrusted_proxies static private_ranges\n\\t\\ttrusted_proxies_strict\n\\t}\n}\n\n:${publicPort} {\n\\troute {\n\\t\\t@foreignWebSocketOrigin \\`{path}.startsWith('${websocketPath}') && {http.request.header.Origin} != '' && {http.request.header.Origin} != 'http://' + {http.request.hostport} && {http.request.header.Origin} != 'https://' + {http.request.hostport}\\`\n\\t\\trespond @foreignWebSocketOrigin 403\n\n\\t\\thandle ${websocketPath}* {\n\\t\\t\\treverse_proxy 127.0.0.1:${realtimePort}\n\\t\\t}\n\n\\t\\thandle {\n\\t\\t\\treverse_proxy 127.0.0.1:${appPort}\n\\t\\t}\n\\t}\n}\n`\n}\n\nfunction requiredValue(value: string, name: string) {\n const normalized = value.trim()\n if (!normalized) throw new Error(`${name} is required`)\n return normalized\n}\n\nfunction port(value: number, name: string) {\n if (!Number.isInteger(value) || value < 1 || value > 65_535) throw new Error(`${name} must be a valid TCP port`)\n return value\n}\n\nfunction absolutePath(value: string, name: string) {\n if (!path.isAbsolute(value)) throw new Error(`${name} must be an absolute path`)\n return value\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
APP_URL=http://localhost:3100
|
|
2
|
+
DATA_DIR=.data/example-full-stack
|
|
3
|
+
TRUST_PROXY=false
|
|
4
|
+
CENTRIFUGO_API_URL=http://127.0.0.1:8100/api
|
|
5
|
+
CENTRIFUGO_API_KEY=replace-for-local-development
|
|
6
|
+
CENTRIFUGO_PROXY_SECRET=replace-for-local-development
|
|
7
|
+
BETTER_AUTH_SECRET=replace-with-at-least-32-random-characters
|
|
8
|
+
SMTP_HOST=127.0.0.1
|
|
9
|
+
SMTP_PORT=1025
|
|
10
|
+
SMTP_SECURE=false
|
|
11
|
+
EMAIL_FROM=ras-stack@example.test
|
|
12
|
+
EMAIL_REQUIRE_VERIFICATION=true
|
|
13
|
+
UPLOAD_MAX_BYTES=1000000
|
|
14
|
+
UPLOAD_QUOTA_BYTES=5000000
|
|
15
|
+
UPLOAD_GLOBAL_QUOTA_BYTES=100000000
|
|
16
|
+
UPLOAD_GLOBAL_MAX_FILES=1024
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
ARG RUNTIME_BINARIES_IMAGE=ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.1@sha256:581a691b59a603685bee8ba9576e80d419b967b63017f82d778fce39ca6f0c0b
|
|
3
|
+
FROM node:24-alpine AS build
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
RUN apk add --no-cache python3 make g++
|
|
6
|
+
RUN corepack enable && corepack install --global pnpm@11.15.0
|
|
7
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
8
|
+
COPY examples/full-stack/package.json ./examples/full-stack/package.json
|
|
9
|
+
RUN pnpm fetch --frozen-lockfile
|
|
10
|
+
COPY config ./config
|
|
11
|
+
COPY dist ./dist
|
|
12
|
+
COPY src ./src
|
|
13
|
+
COPY scripts ./scripts
|
|
14
|
+
COPY tsconfig.json tsconfig.build.json LICENSE README.md ./
|
|
15
|
+
COPY examples/full-stack ./examples/full-stack
|
|
16
|
+
RUN pnpm install --offline --frozen-lockfile
|
|
17
|
+
RUN pnpm build && pnpm --filter @ras-stack/example-full-stack build
|
|
18
|
+
|
|
19
|
+
FROM ${RUNTIME_BINARIES_IMAGE} AS runtime-binaries
|
|
20
|
+
|
|
21
|
+
FROM node:24-alpine
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
RUN apk add --no-cache tini \
|
|
24
|
+
&& rm -rf /usr/local/lib/node_modules/corepack /usr/local/lib/node_modules/npm /opt/yarn-v1.22.22 \
|
|
25
|
+
&& rm -f /usr/local/bin/corepack /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/yarn /usr/local/bin/yarnpkg \
|
|
26
|
+
&& mkdir -p /data \
|
|
27
|
+
&& chown -R node:node /app /data
|
|
28
|
+
COPY --from=build --chown=node:node /app/examples/full-stack/.output ./.output
|
|
29
|
+
COPY --from=runtime-binaries /usr/local/bin/centrifugo /usr/local/bin/centrifugo
|
|
30
|
+
COPY --from=runtime-binaries /usr/local/bin/caddy /usr/local/bin/caddy
|
|
31
|
+
COPY --chown=node:node examples/full-stack/centrifugo.json ./centrifugo.json
|
|
32
|
+
COPY --chown=node:node LICENSE ./
|
|
33
|
+
ENV NODE_ENV=production DATA_DIR=/data TRUST_PROXY=false CENTRIFUGO_CONFIG=/app/centrifugo.json CENTRIFUGO_API_URL=http://127.0.0.1:8100/api CENTRIFUGO_HTTP_SERVER_PORT=8100
|
|
34
|
+
EXPOSE 3100
|
|
35
|
+
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 CMD wget -q --spider http://127.0.0.1:3100/api/ready || exit 1
|
|
36
|
+
USER node
|
|
37
|
+
ENTRYPOINT ["/sbin/tini", "-g", "--", "node", ".output/server/containerRuntime.mjs"]
|