rbxts-orchestra 0.1.0-dev.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/run.js CHANGED
@@ -32,7 +32,7 @@ var import_yargs = __toESM(require("yargs"));
32
32
  // package.json
33
33
  var package_default = {
34
34
  name: "rbxts-orchestra",
35
- version: "0.1.0-dev.1",
35
+ version: "0.1.1",
36
36
  description: "A build orchestrator.",
37
37
  main: "src/index.ts",
38
38
  bin: {
@@ -44,7 +44,8 @@ var package_default = {
44
44
  },
45
45
  files: [
46
46
  "dist",
47
- "bin/run"
47
+ "bin/run",
48
+ "src"
48
49
  ],
49
50
  scripts: {
50
51
  build: "tsup-node && tsc",
@@ -167,7 +168,10 @@ function builder(yargs2) {
167
168
  async function handler(argv2) {
168
169
  const delimiterIndex = process2.argv.findIndex((arg) => arg == "--");
169
170
  const delimitedArgv = delimiterIndex !== -1 ? process2.argv.slice(delimiterIndex + 1) : void 0;
170
- const orchestra = await Orchestra.fromFileConfig(argv2.config);
171
+ const orchestra = await Orchestra.fromFileConfig(argv2.config).catch(() => {
172
+ logger.error(`Failed to find config file at ${argv2.config}`);
173
+ process2.exit(1);
174
+ });
171
175
  await orchestra.runScripts(argv2.script, delimitedArgv).catch((code) => {
172
176
  logger.error(`Failed to run script: exit code ${code}`);
173
177
  });
@@ -26,10 +26,10 @@ export function builder(yargs) {
26
26
  export async function handler(argv) {
27
27
  const delimiterIndex = process.argv.findIndex((arg) => arg == "--");
28
28
  const delimitedArgv = delimiterIndex !== -1 ? process.argv.slice(delimiterIndex + 1) : undefined;
29
- const orchestra = await Orchestra.fromFileConfig(argv.config); /*.catch(() => {
29
+ const orchestra = await Orchestra.fromFileConfig(argv.config).catch(() => {
30
30
  logger.error(`Failed to find config file at ${argv.config}`);
31
31
  process.exit(1);
32
- })*/
32
+ });
33
33
  await orchestra.runScripts(argv.script, delimitedArgv).catch((code) => {
34
34
  logger.error(`Failed to run script: exit code ${code}`);
35
35
  });
@@ -1 +1 @@
1
- {"version":3,"file":"runScript.js","sourceRoot":"","sources":["../../../src/commands/runScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAGxC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAOpE,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC;AACtC,MAAM,CAAC,MAAM,QAAQ,GAAG,iDAAiD,CAAC;AAE1E,MAAM,UAAU,OAAO,CAAC,KAAoB;IAC3C,OAAO,KAAK;SACV,UAAU,CAAC,QAAQ,EAAE;QACrB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,aAAa;KAC1B,CAAC;SACD,MAAM,CAAC,QAAQ,EAAE;QACjB,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,GAAG;QACV,WAAW,EAAE,mCAAmC;QAChD,OAAO,EAAE,GAAG,yBAAyB,EAAE;KACvC,CAAC;SACD,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE;QACnC,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAiC;IAC9D,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;;;QAG3D;IAEJ,MAAM,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;QACrE,MAAM,CAAC,KAAK,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"runScript.js","sourceRoot":"","sources":["../../../src/commands/runScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAGxC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAOpE,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC;AACtC,MAAM,CAAC,MAAM,QAAQ,GAAG,iDAAiD,CAAC;AAE1E,MAAM,UAAU,OAAO,CAAC,KAAoB;IAC3C,OAAO,KAAK;SACV,UAAU,CAAC,QAAQ,EAAE;QACrB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,aAAa;KAC1B,CAAC;SACD,MAAM,CAAC,QAAQ,EAAE;QACjB,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,GAAG;QACV,WAAW,EAAE,mCAAmC;QAChD,OAAO,EAAE,GAAG,yBAAyB,EAAE;KACvC,CAAC;SACD,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE;QACnC,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAiC;IAC9D,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACxE,MAAM,CAAC,KAAK,CAAC,iCAAiC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;QACrE,MAAM,CAAC,KAAK,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rbxts-orchestra",
3
- "version": "0.1.0-dev.1",
3
+ "version": "0.1.1",
4
4
  "description": "A build orchestrator.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "files": [
14
14
  "dist",
15
- "bin/run"
15
+ "bin/run",
16
+ "src"
16
17
  ],
17
18
  "scripts": {
18
19
  "build": "tsup-node && tsc",
@@ -0,0 +1,3 @@
1
+ import * as run from "./runScript";
2
+
3
+ export const commands = [run];
@@ -0,0 +1,47 @@
1
+ import { isAbsolute, join } from "node:path";
2
+ import * as process from "node:process";
3
+ import { ArgumentsCamelCase, Argv } from "yargs";
4
+
5
+ import { logger } from "../logger";
6
+ import { Orchestra, ORCHESTRA_CONFIG_FILENAME } from "../orchestra";
7
+
8
+ interface RunArgv {
9
+ script: string;
10
+ config: string;
11
+ }
12
+
13
+ export const command = "run <script>";
14
+ export const describe = "Runs build scripts for all orchestrated places.";
15
+
16
+ export function builder(yargs: Argv<RunArgv>): Argv {
17
+ return yargs
18
+ .positional("script", {
19
+ type: "string",
20
+ description: "script name",
21
+ })
22
+ .option("config", {
23
+ type: "string",
24
+ alias: "c",
25
+ description: "Path to the orchestra config file",
26
+ default: `${ORCHESTRA_CONFIG_FILENAME}`,
27
+ })
28
+ .coerce("config", (value: string) => {
29
+ if (isAbsolute(value)) {
30
+ return value;
31
+ }
32
+ return join(process.cwd(), value);
33
+ });
34
+ }
35
+
36
+ export async function handler(argv: ArgumentsCamelCase<RunArgv>) {
37
+ const delimiterIndex = process.argv.findIndex((arg) => arg == "--");
38
+ const delimitedArgv = delimiterIndex !== -1 ? process.argv.slice(delimiterIndex + 1) : undefined;
39
+ const orchestra = await Orchestra.fromFileConfig(argv.config).catch(() => {
40
+ logger.error(`Failed to find config file at ${argv.config}`);
41
+ process.exit(1);
42
+ });
43
+
44
+ await orchestra.runScripts(argv.script, delimitedArgv).catch((code) => {
45
+ logger.error(`Failed to run script: exit code ${code}`);
46
+ });
47
+ }
@@ -0,0 +1,45 @@
1
+ import { execSync } from "child_process";
2
+ import { Environments, Value } from "src/orchestra";
3
+
4
+ export function semverString<E extends Environments>(
5
+ /**
6
+ * The base semver string (e.g `1.0.0`)
7
+ */
8
+ baseSemver: string,
9
+ /**
10
+ * A list of environments in which to include the short hash of the
11
+ * currently checked out commit in the semver string
12
+ */
13
+ commitHashEnvironments?: (keyof E)[],
14
+ /**
15
+ * A list of environments in which to include the name of the
16
+ * local user in the semver string
17
+ */
18
+ userEnvironments?: (keyof E)[],
19
+ ): Value<E, string> {
20
+ return (orchestra) => {
21
+ let shortCommitHash, localUsername;
22
+ try {
23
+ localUsername = execSync("whoami", {
24
+ stdio: "pipe",
25
+ })
26
+ .toString()
27
+ .trim();
28
+ shortCommitHash = execSync("git rev-parse --short HEAD", {
29
+ stdio: "pipe",
30
+ })
31
+ .toString()
32
+ .trim();
33
+ } catch {}
34
+ let semver = baseSemver;
35
+
36
+ if (localUsername && userEnvironments?.includes(orchestra.environment)) {
37
+ semver += `-${localUsername}`;
38
+ }
39
+ if (shortCommitHash && commitHashEnvironments?.includes(orchestra.environment)) {
40
+ semver += `+${shortCommitHash}`;
41
+ }
42
+
43
+ return semver;
44
+ };
45
+ }
@@ -0,0 +1,64 @@
1
+ import { spawn } from "child_process";
2
+ import { resolve } from "path";
3
+
4
+ import { Environments, PlaceConfig, Script, val, Value } from "../orchestra/config";
5
+ import { Orchestra } from "../orchestra/orchestra";
6
+
7
+ interface SpawnConfig {
8
+ /**
9
+ * Whether to wait until the process exits before continuing to the next script
10
+ */
11
+ waitForExit: boolean;
12
+ /**
13
+ * Whether to ignore a non-zero exit code
14
+ */
15
+ ignoreNonZeroExitCode: boolean;
16
+ }
17
+
18
+ const DEFAULT_SPAWN_CONFIG: SpawnConfig = {
19
+ waitForExit: true,
20
+ ignoreNonZeroExitCode: false,
21
+ };
22
+
23
+ function generateEnv<E extends Environments>(
24
+ orchestra: Orchestra<E>,
25
+ place?: PlaceConfig<E>,
26
+ ): Record<string, string | undefined> {
27
+ return {
28
+ ...process.env,
29
+ ORCHESTRA_DIR: resolve("."),
30
+ RELEASE_NUMBER: orchestra.resolveConfig(orchestra.config.releaseNumber),
31
+ RELEASE_NAME: orchestra.resolveConfig(orchestra.config.releaseName),
32
+ PLACE_ID: place && orchestra.resolveConfig(place.placeId)?.toString(),
33
+ EXPERIENCE_ID: place && orchestra.resolveConfig(place.experienceId)?.toString(),
34
+ };
35
+ }
36
+
37
+ export function sh<E extends Environments>(cmd: string, config: Partial<SpawnConfig> = {}): Value<E, Script<E>> {
38
+ const fullConfig = {
39
+ ...DEFAULT_SPAWN_CONFIG,
40
+ ...config,
41
+ };
42
+
43
+ return val((orchestra, place, argv = []) => {
44
+ return new Promise((resolvePromise, reject) => {
45
+ const child = spawn(`${cmd}`, argv, {
46
+ cwd: place ? resolve(orchestra.resolveConfig(place.rootDir)) : undefined,
47
+ stdio: "inherit",
48
+ env: generateEnv(orchestra, place),
49
+ });
50
+
51
+ if (fullConfig.waitForExit) {
52
+ child.on("exit", (code) => {
53
+ if (code !== 0 && !fullConfig.ignoreNonZeroExitCode) reject(code);
54
+ resolvePromise();
55
+ });
56
+ } else {
57
+ resolvePromise();
58
+ }
59
+ });
60
+ });
61
+ }
62
+ export function script<E extends Environments>(path: string, config: Partial<SpawnConfig> = {}): Value<E, Script<E>> {
63
+ return sh(`${resolve(path)}`, config);
64
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { createConsola } from "consola";
2
+
3
+ export const logger = createConsola({});
@@ -0,0 +1,117 @@
1
+ import { Orchestra } from "./orchestra";
2
+
3
+ /**
4
+ * A value that is resolved using the Orchestra context
5
+ */
6
+ export type Value<E extends Environments, T> = (orchestra: Orchestra<E>) => T;
7
+
8
+ /**
9
+ * Allow either a raw value or a computed one
10
+ */
11
+ export type MaybeValue<E extends Environments, T> = T | Value<E, T>;
12
+
13
+ /**
14
+ * Resolve a MaybeValue at runtime
15
+ */
16
+ export function resolveValue<E extends Environments, T>(value: MaybeValue<E, T>, orchestra: Orchestra<E>): T {
17
+ return typeof value === "function" ? (value as Value<E, T>)(orchestra) : value;
18
+ }
19
+
20
+ /**
21
+ * Helper to wrap static values
22
+ */
23
+ export function val<E extends Environments, T>(value: T): Value<E, T> {
24
+ return () => value;
25
+ }
26
+
27
+ /**
28
+ * Environment-based value helper
29
+ */
30
+ export function env<V extends Record<string, unknown>>(values: V) {
31
+ return <E extends { [K in keyof V]: EnvironmentConfig }>(orchestra: Orchestra<E>): V[keyof V] => {
32
+ return values[orchestra.environment as keyof V];
33
+ };
34
+ }
35
+
36
+ export type Script<E extends Environments> = (
37
+ orchestra: Orchestra<E>,
38
+ place?: PlaceConfig<E>,
39
+ argv?: string[],
40
+ ) => Promise<void> | void;
41
+
42
+ /**
43
+ * A configuration for a place managed by an Orchestra
44
+ */
45
+ export interface PlaceConfig<E extends Environments> {
46
+ rootDir: MaybeValue<E, string>;
47
+ placeId: MaybeValue<E, number>;
48
+ experienceId: MaybeValue<E, number>;
49
+
50
+ scripts: {
51
+ /**
52
+ * A callback that runs when this place is built by the Orchestra
53
+ */
54
+ [k: string]: MaybeValue<E, Script<E>>;
55
+ };
56
+ }
57
+
58
+ /**
59
+ * A configuration for a specific environment within an Orchestra
60
+ */
61
+ export interface EnvironmentConfig {
62
+ /**
63
+ * Whether to use this environment as the default if no other environments are chosen
64
+ */
65
+ default?: boolean;
66
+
67
+ /**
68
+ * The NODE_ENV value that will result in this environment being chosen
69
+ */
70
+ nodeEnv?: string;
71
+ }
72
+
73
+ /**
74
+ * A list of environments available to an Orchestra
75
+ */
76
+ export type Environments = {
77
+ [k: string]: EnvironmentConfig;
78
+ };
79
+
80
+ /**
81
+ * A configuration for an Orchestra
82
+ */
83
+ export interface OrchestraConfig<E extends Environments> {
84
+ /**
85
+ * Semantic version
86
+ */
87
+ releaseNumber: string;
88
+
89
+ /**
90
+ * Human-friendly release name
91
+ */
92
+ releaseName: string;
93
+
94
+ /**
95
+ * Available environments
96
+ */
97
+ environments: E;
98
+
99
+ scripts?: {
100
+ /**
101
+ * Callback that runs before any scripts are run
102
+ */
103
+ prepare?: MaybeValue<E, Script<E>>;
104
+ } & Record<string, MaybeValue<E, Script<E>>>;
105
+
106
+ /**
107
+ * Places in this experience
108
+ */
109
+ places: [PlaceConfig<E>, ...PlaceConfig<E>[]];
110
+ }
111
+
112
+ /**
113
+ * Create an Orchestra config
114
+ */
115
+ export function orchestraConfig<const E extends Environments>(experience: OrchestraConfig<E>) {
116
+ return experience;
117
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./config";
2
+ export * from "./orchestra";
@@ -0,0 +1,52 @@
1
+ import assert from "assert";
2
+ import { createJiti } from "jiti";
3
+ import { resolve } from "path";
4
+
5
+ import { Environments, MaybeValue, OrchestraConfig, resolveValue } from "./config";
6
+
7
+ const jiti = createJiti(__filename);
8
+
9
+ export const ORCHESTRA_CONFIG_FILENAME = "orchestra.config.ts";
10
+
11
+ export class Orchestra<E extends Environments> {
12
+ public static async fromDirectory(directoryPath: string): Promise<Orchestra<never>> {
13
+ return await Orchestra.fromFileConfig(resolve(directoryPath, ORCHESTRA_CONFIG_FILENAME));
14
+ }
15
+
16
+ public static async fromFileConfig(filePath: string): Promise<Orchestra<never>> {
17
+ const config = await jiti.import(resolve(filePath), {
18
+ default: true,
19
+ });
20
+
21
+ return new Orchestra(config as OrchestraConfig<never>);
22
+ }
23
+
24
+ public readonly environment: keyof E;
25
+
26
+ constructor(public readonly config: OrchestraConfig<E>) {
27
+ for (const [environmentName, environmentConfig] of Object.entries(config.environments)) {
28
+ if (this!.environment) continue;
29
+ const NODE_ENV = process.env.NODE_ENV ?? "unknown";
30
+ if (environmentConfig.default) {
31
+ this.environment = environmentName;
32
+ } else if (NODE_ENV == environmentConfig.nodeEnv) {
33
+ this.environment = environmentName;
34
+ }
35
+ }
36
+ assert(this!.environment, `Orchestra confiag is invalid, no default or otherwise valid environments provided`);
37
+ }
38
+
39
+ public async runScripts(scriptName: string, argv?: string[]) {
40
+ await this.resolveConfig(this.config.scripts?.prepare)?.(this, undefined, argv);
41
+
42
+ await this.resolveConfig(this.config.scripts?.[scriptName])?.(this, undefined, argv);
43
+
44
+ for (const place of this.config.places) {
45
+ await this.resolveConfig(place.scripts[scriptName])?.(this, place, argv);
46
+ }
47
+ }
48
+
49
+ public resolveConfig<T>(config: MaybeValue<E, T>): T {
50
+ return resolveValue(config, this);
51
+ }
52
+ }