functionalscript 0.38.0 → 0.40.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 +1 -1
- package/fjs/bnf/descent/module.f.d.ts +43 -2
- package/fjs/bnf/descent/module.f.js +37 -12
- package/fjs/bnf/descent/proof.f.d.ts +1 -0
- package/fjs/bnf/descent/proof.f.js +73 -32
- package/fjs/bnf/ll1/module.f.js +2 -2
- package/fjs/bnf/token_symbol/module.f.d.ts +38 -0
- package/fjs/bnf/token_symbol/module.f.js +48 -0
- package/fjs/bnf/token_symbol/proof.f.d.ts +10 -0
- package/fjs/bnf/token_symbol/proof.f.js +37 -0
- package/fjs/cas/cli/module.f.js +6 -9
- package/fjs/cas/evo/module.f.d.ts +77 -13
- package/fjs/cas/evo/module.f.js +130 -18
- package/fjs/cas/evo/proof.f.d.ts +7 -0
- package/fjs/cas/evo/proof.f.js +120 -1
- package/fjs/cas/module.f.js +64 -37
- package/fjs/cas/proof.f.d.ts +3 -9
- package/fjs/cas/proof.f.js +94 -74
- package/fjs/ci/bun/module.f.js +3 -3
- package/fjs/ci/common/module.f.d.ts +1 -2
- package/fjs/ci/common/module.f.js +1 -5
- package/fjs/ci/config/module.f.d.ts +11 -8
- package/fjs/ci/config/module.f.js +24 -9
- package/fjs/ci/deno/module.f.js +3 -3
- package/fjs/ci/module.f.js +13 -7
- package/fjs/ci/nix/module.f.d.ts +61 -0
- package/fjs/ci/nix/module.f.js +92 -0
- package/fjs/ci/nix/proof.f.d.ts +23 -0
- package/fjs/ci/nix/proof.f.js +109 -0
- package/fjs/ci/node/module.f.d.ts +23 -1
- package/fjs/ci/node/module.f.js +58 -12
- package/fjs/ci/proof.f.d.ts +2 -0
- package/fjs/ci/proof.f.js +49 -9
- package/fjs/cli/module.f.js +2 -2
- package/fjs/common/monoid/module.f.d.ts +46 -2
- package/fjs/common/monoid/module.f.js +44 -0
- package/fjs/common/monoid/proof.f.d.ts +5 -0
- package/fjs/common/monoid/proof.f.js +27 -1
- package/fjs/dev/module.f.d.ts +1 -0
- package/fjs/dev/module.f.js +38 -27
- package/fjs/dev/update/module.f.d.ts +11 -0
- package/fjs/dev/update/module.f.js +20 -0
- package/fjs/dev/update/proof.f.d.ts +7 -0
- package/fjs/dev/update/proof.f.js +35 -0
- package/fjs/djs/ast/module.f.d.ts +47 -0
- package/fjs/djs/ast/module.f.js +9 -0
- package/fjs/djs/module.f.js +5 -8
- package/fjs/djs/parser/module.f.d.ts +3 -0
- package/fjs/djs/parser/module.f.js +17 -0
- package/fjs/djs/tokenizer/module.f.js +2 -2
- package/fjs/djs/tokenizer/proof.f.d.ts +1 -0
- package/fjs/djs/tokenizer/proof.f.js +70 -16
- package/fjs/djs/transpiler/module.f.js +6 -6
- package/fjs/effects/eff/module.f.d.ts +72 -0
- package/fjs/effects/eff/module.f.js +46 -0
- package/fjs/effects/eff/proof.f.d.ts +11 -0
- package/fjs/effects/eff/proof.f.js +58 -0
- package/fjs/effects/list/module.f.d.ts +11 -5
- package/fjs/effects/list/module.f.js +2 -1
- package/fjs/effects/memory/proof.f.js +7 -8
- package/fjs/effects/module.f.d.ts +334 -52
- package/fjs/effects/module.f.js +253 -45
- package/fjs/effects/node/memory/proof.js +3 -2
- package/fjs/effects/node/module.d.ts +3 -3
- package/fjs/effects/node/module.f.d.ts +18 -8
- package/fjs/effects/node/module.f.js +31 -11
- package/fjs/effects/node/module.js +12 -12
- package/fjs/effects/node/proof.f.d.ts +1 -0
- package/fjs/effects/node/proof.f.js +32 -11
- package/fjs/effects/node/virtual/module.f.js +1 -1
- package/fjs/effects/proof.f.d.ts +32 -6
- package/fjs/effects/proof.f.js +131 -39
- package/fjs/emergent_testing/all.test.js +2 -1
- package/fjs/emergent_testing/module.f.d.ts +3 -3
- package/fjs/emergent_testing/module.f.js +63 -49
- package/fjs/emergent_testing/proof.f.d.ts +4 -0
- package/fjs/emergent_testing/proof.f.js +54 -21
- package/fjs/emergent_testing/scenarios/thenable.pass.js +1 -1
- package/fjs/fsc/module.f.js +4 -4
- package/fjs/fsm/module.f.js +1 -1
- package/fjs/js/tokenizer/module.f.d.ts +1 -0
- package/fjs/js/tokenizer/module.f.js +13 -6
- package/fjs/{cas/mcp → mcp/cas}/module.f.d.ts +3 -25
- package/fjs/{cas/mcp → mcp/cas}/module.f.js +26 -64
- package/fjs/mcp/evo/module.f.d.ts +27 -0
- package/fjs/mcp/evo/module.f.js +94 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +2 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +40 -9
- package/fjs/mcp/module.f.d.ts +54 -237
- package/fjs/mcp/module.f.js +55 -258
- package/fjs/mcp/proof.f.d.ts +43 -32
- package/fjs/mcp/proof.f.js +509 -200
- package/fjs/media/nix/module.f.d.ts +30 -0
- package/fjs/media/nix/module.f.js +166 -0
- package/fjs/media/nix/proof.f.d.ts +32 -0
- package/fjs/media/nix/proof.f.js +127 -0
- package/fjs/media/type/module.f.js +2 -2
- package/fjs/media/type/proof.f.js +7 -10
- package/fjs/module.f.js +5 -9
- package/fjs/protocol/json_rpc/module.f.d.ts +114 -0
- package/fjs/{media/json/rpc → protocol/json_rpc}/module.f.js +3 -3
- package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.js +3 -3
- package/fjs/protocol/mcp/module.f.d.ts +239 -0
- package/fjs/protocol/mcp/module.f.js +272 -0
- package/fjs/protocol/mcp/proof.f.d.ts +34 -0
- package/fjs/protocol/mcp/proof.f.js +208 -0
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.d.ts +7 -7
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +22 -22
- package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
- package/fjs/types/bigint/module.f.js +4 -3
- package/fjs/types/bit_vec/module.f.js +5 -15
- package/fjs/types/btree/remove/module.f.d.ts +5 -0
- package/fjs/types/btree/remove/module.f.js +12 -0
- package/fjs/types/list/module.f.d.ts +21 -0
- package/fjs/types/list/module.f.js +17 -0
- package/fjs/types/list/proof.f.d.ts +1 -0
- package/fjs/types/list/proof.f.js +24 -1
- package/fjs/types/number/module.f.js +2 -1
- package/fjs/types/range_map/module.f.d.ts +14 -13
- package/fjs/types/range_map/module.f.js +18 -13
- package/fjs/types/range_map/proof.f.js +26 -39
- package/fjs/types/range_set/module.f.d.ts +5 -0
- package/fjs/types/range_set/module.f.js +16 -0
- package/fjs/types/range_set/proof.f.d.ts +1 -0
- package/fjs/types/range_set/proof.f.js +19 -0
- package/fjs/types/string/module.f.js +10 -2
- package/fjs/website/module.f.js +2 -3
- package/package.json +5 -5
- package/fjs/cas/evo/mcp/module.f.d.ts +0 -19
- package/fjs/cas/evo/mcp/module.f.js +0 -56
- package/fjs/cas/mcp/proof.f.d.ts +0 -45
- package/fjs/cas/mcp/proof.f.js +0 -545
- package/fjs/ci/playwright/module.f.d.ts +0 -2
- package/fjs/ci/playwright/module.f.js +0 -25
- package/fjs/media/json/rpc/module.f.d.ts +0 -114
- /package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.d.ts +0 -0
- /package/fjs/{mcp → protocol/mcp}/stdio/proof.f.d.ts +0 -0
package/fjs/ci/deno/module.f.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { deno } from '../config/module.f.js';
|
|
8
|
-
import {
|
|
8
|
+
import { install, test, uses } from '../common/module.f.js';
|
|
9
9
|
const denoTest = 'deno test --allow-read --allow-env --allow-sys';
|
|
10
|
-
export const denoSteps = (version) =>
|
|
10
|
+
export const denoSteps = (version) => [
|
|
11
11
|
install(uses('denoland/setup-deno', { 'deno-version': deno })),
|
|
12
12
|
// We need --minimum-dependency-age=0 for functionalscript because we would like to use
|
|
13
13
|
// the latest version of the package even if it is not yet 24 hours old,
|
|
@@ -17,4 +17,4 @@ export const denoSteps = (version) => clean([
|
|
|
17
17
|
test({ run: `deno run -A --minimum-dependency-age=0 npm:functionalscript@${version} t` }),
|
|
18
18
|
test({ run: 'deno install --frozen' }),
|
|
19
19
|
test({ run: `${denoTest} --coverage && deno coverage --include='.*module\\.f\\.ts'` }),
|
|
20
|
-
]
|
|
20
|
+
];
|
package/fjs/ci/module.f.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { mapStep, step } from '../effects/module.f.js';
|
|
7
7
|
import { access, writeUtf8File } from '../effects/node/module.f.js';
|
|
8
8
|
import { functionalscript, images } from './config/module.f.js';
|
|
9
9
|
import { architecture, os, toSteps, ubuntuArm } from './common/module.f.js';
|
|
10
10
|
import { rustPlatformSteps, rustWasmSteps } from './rust/module.f.js';
|
|
11
|
-
import { nodeMainSteps, nodeVersionJobs } from './node/module.f.js';
|
|
12
|
-
import {
|
|
11
|
+
import { nodeMainSteps, nodeNixJobs, nodeNixVersionSteps, nodeVersionJobs } from './node/module.f.js';
|
|
12
|
+
import { nixFlakes, nixInstall } from './nix/module.f.js';
|
|
13
13
|
import { bunSteps } from './bun/module.f.js';
|
|
14
14
|
import { denoSteps } from './deno/module.f.js';
|
|
15
15
|
const job = (rust, nodeExtra) => (o) => (a) => {
|
|
@@ -22,14 +22,19 @@ const job = (rust, nodeExtra) => (o) => (a) => {
|
|
|
22
22
|
];
|
|
23
23
|
return [id, { 'runs-on': image, steps: toSteps(result) }];
|
|
24
24
|
};
|
|
25
|
+
// Every generated flake, across all job families that own one.
|
|
26
|
+
const nixJobs = nodeNixJobs;
|
|
27
|
+
// Temporary: proves the not-yet-migrated flakes still evaluate. Removed once
|
|
28
|
+
// the canonical Node jobs check their own flake by running through it.
|
|
29
|
+
const nixFlakeJob = ubuntuArm([nixInstall, ...nodeNixVersionSteps]);
|
|
25
30
|
const canonicalJobs = (rust) => ({
|
|
26
31
|
...(rust ? { wasm: ubuntuArm(rustWasmSteps) } : {}),
|
|
27
32
|
deno: ubuntuArm(denoSteps(functionalscript)),
|
|
28
33
|
bun: ubuntuArm(bunSteps(functionalscript)),
|
|
29
34
|
...nodeVersionJobs(functionalscript),
|
|
30
|
-
|
|
35
|
+
'nix-flakes': nixFlakeJob,
|
|
31
36
|
});
|
|
32
|
-
export const ci = ({ nodeExtra }) => access('Cargo.toml')
|
|
37
|
+
export const ci = ({ nodeExtra }) => step(access('Cargo.toml'), result => {
|
|
33
38
|
const rust = result[0] === 'ok';
|
|
34
39
|
const jobs = {
|
|
35
40
|
...Object.fromEntries(os.flatMap(o => architecture.map(job(rust, nodeExtra(o))(o)))),
|
|
@@ -46,7 +51,8 @@ export const ci = ({ nodeExtra }) => access('Cargo.toml').step(result => {
|
|
|
46
51
|
},
|
|
47
52
|
jobs,
|
|
48
53
|
};
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
const workflowWritten = writeUtf8File('.github/workflows/ci.yml', JSON.stringify(gha, null, ' '));
|
|
55
|
+
const flakesWritten = step(workflowWritten, () => nixFlakes(nixJobs));
|
|
56
|
+
return mapStep(flakesWritten, () => 0);
|
|
51
57
|
});
|
|
52
58
|
export const main = () => ci({ nodeExtra: () => [] });
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates one self-contained Nix flake per declared CI job.
|
|
3
|
+
*
|
|
4
|
+
* Each job gets its own `nix/generated/<id>/flake.nix` pinning the exact
|
|
5
|
+
* Nixpkgs commit from `../config/module.f.ts` and exposing a single
|
|
6
|
+
* `devShells.<system>.default` development shell. The files are static and
|
|
7
|
+
* readable on purpose: no job selection, no shared Nix modules, no helper
|
|
8
|
+
* libraries.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { type Effect } from '../../effects/module.f.ts';
|
|
13
|
+
import { type Mkdir, type WriteFile } from '../../effects/node/module.f.ts';
|
|
14
|
+
import { type MetaStep } from '../common/module.f.ts';
|
|
15
|
+
/** A CI job's development environment, one generated flake each. */
|
|
16
|
+
export type NixJob = {
|
|
17
|
+
/** Generated directory name under `nix/generated`, matching the CI job id. */
|
|
18
|
+
readonly id: string;
|
|
19
|
+
/** Nix system of the job's runner, e.g. `aarch64-linux`. */
|
|
20
|
+
readonly system: string;
|
|
21
|
+
/** Nixpkgs attribute names made available in the job's shell. */
|
|
22
|
+
readonly packages: readonly string[];
|
|
23
|
+
/** Job-local shell initialization, when the job needs one. */
|
|
24
|
+
readonly shellHook?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Directory owned by this generator. */
|
|
27
|
+
export declare const generatedDirectory: 'nix/generated';
|
|
28
|
+
/**
|
|
29
|
+
* Serializes a job's flake.
|
|
30
|
+
*
|
|
31
|
+
* The serializer rejects invalid *identifiers*, and every identifier in a flake
|
|
32
|
+
* is written here — a job only contributes attribute names and strings, which
|
|
33
|
+
* are quoted when they are not identifiers. The unwrap is therefore a totality
|
|
34
|
+
* assertion, not an input check.
|
|
35
|
+
*/
|
|
36
|
+
export declare const flakeText: (job: NixJob) => string;
|
|
37
|
+
/** Writes one generated flake per job. */
|
|
38
|
+
export declare const nixFlakes: (jobs: readonly NixJob[]) => Effect<Mkdir | WriteFile, void>;
|
|
39
|
+
/** Path a workflow passes to `nix develop`, for the job of the given id. */
|
|
40
|
+
export declare const flakePath: (id: string) => string;
|
|
41
|
+
/** Installs Nix, with `nix-command` and `flakes` enabled by the action's defaults. */
|
|
42
|
+
export declare const nixInstall: MetaStep;
|
|
43
|
+
/** Runs one command inside a job's generated development shell. */
|
|
44
|
+
export declare const nixDevelop: (id: string, command: string) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Runs a migrated job's whole command sequence in one development shell, so the
|
|
47
|
+
* shell's packages and environment reach every command without exporting a
|
|
48
|
+
* profile across GitHub Actions steps.
|
|
49
|
+
*
|
|
50
|
+
* The commands are a shell script, joined so a failure stops the rest, and are
|
|
51
|
+
* quoted as one argument — a command may contain quotes of its own.
|
|
52
|
+
*/
|
|
53
|
+
export declare const nixDevelopAll: (id: string, commands: readonly string[]) => string;
|
|
54
|
+
/**
|
|
55
|
+
* Checks a job's generated flake end to end: the shell builds, and the Node it
|
|
56
|
+
* puts on `PATH` is exactly the pinned version. The pinned Nixpkgs commit
|
|
57
|
+
* already determines the version, so this is the only place the expectation
|
|
58
|
+
* is stated — the generated flakes stay declarative instead of carrying an
|
|
59
|
+
* `assert` that restates the commit they pin.
|
|
60
|
+
*/
|
|
61
|
+
export declare const nixVersionCheckStep: (id: string, version: string) => MetaStep;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates one self-contained Nix flake per declared CI job.
|
|
3
|
+
*
|
|
4
|
+
* Each job gets its own `nix/generated/<id>/flake.nix` pinning the exact
|
|
5
|
+
* Nixpkgs commit from `../config/module.f.ts` and exposing a single
|
|
6
|
+
* `devShells.<system>.default` development shell. The files are static and
|
|
7
|
+
* readable on purpose: no job selection, no shared Nix modules, no helper
|
|
8
|
+
* libraries.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { forEachStep, mapStep, pure, step } from '../../effects/module.f.js';
|
|
13
|
+
import { mkdir, writeUtf8File } from '../../effects/node/module.f.js';
|
|
14
|
+
import { nixToString } from '../../media/nix/module.f.js';
|
|
15
|
+
import { fromUndefined, unwrap as unwrapNullable } from '../../types/nullable/module.f.js';
|
|
16
|
+
import { unwrap } from '../../types/result/module.f.js';
|
|
17
|
+
import { install, test, uses } from '../common/module.f.js';
|
|
18
|
+
import { nixpkgs } from '../config/module.f.js';
|
|
19
|
+
/** Directory owned by this generator. */
|
|
20
|
+
export const generatedDirectory = 'nix/generated';
|
|
21
|
+
const { commit } = nixpkgs;
|
|
22
|
+
const url = `github:NixOS/nixpkgs/${commit}`;
|
|
23
|
+
const flake = ({ system, packages, shellHook }) => ['set',
|
|
24
|
+
['=', ['inputs', 'nixpkgs', 'url'], url],
|
|
25
|
+
['=', ['outputs'], ['lambda',
|
|
26
|
+
['open-set-pattern', 'nixpkgs'],
|
|
27
|
+
['set',
|
|
28
|
+
['=', ['devShells', system, 'default'], ['let',
|
|
29
|
+
[['=', ['pkgs'], ['apply',
|
|
30
|
+
['ref', 'import'],
|
|
31
|
+
['ref', 'nixpkgs'],
|
|
32
|
+
['set', ['=', ['system'], system]]
|
|
33
|
+
]]],
|
|
34
|
+
['apply',
|
|
35
|
+
['ref', 'pkgs', 'mkShell'],
|
|
36
|
+
['set',
|
|
37
|
+
['=', ['packages'], ['list', ...packages.map(p => ['ref', 'pkgs', p])]],
|
|
38
|
+
...(shellHook === undefined
|
|
39
|
+
? []
|
|
40
|
+
: [['=', ['shellHook'], ['indented-string', shellHook]]])
|
|
41
|
+
]
|
|
42
|
+
]
|
|
43
|
+
]]
|
|
44
|
+
]
|
|
45
|
+
]]
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Serializes a job's flake.
|
|
49
|
+
*
|
|
50
|
+
* The serializer rejects invalid *identifiers*, and every identifier in a flake
|
|
51
|
+
* is written here — a job only contributes attribute names and strings, which
|
|
52
|
+
* are quoted when they are not identifiers. The unwrap is therefore a totality
|
|
53
|
+
* assertion, not an input check.
|
|
54
|
+
*/
|
|
55
|
+
export const flakeText = (job) => unwrapNullable(fromUndefined(nixToString(flake(job))));
|
|
56
|
+
const writeFlake = (job) => {
|
|
57
|
+
const directory = `${generatedDirectory}/${job.id}`;
|
|
58
|
+
const created = mapStep(mkdir(directory, { recursive: true }), unwrap);
|
|
59
|
+
const written = step(created, () => writeUtf8File(`${directory}/flake.nix`, flakeText(job)));
|
|
60
|
+
return mapStep(written, unwrap);
|
|
61
|
+
};
|
|
62
|
+
/** Writes one generated flake per job. */
|
|
63
|
+
export const nixFlakes = (jobs) => forEachStep(pure(jobs), writeFlake);
|
|
64
|
+
/** Path a workflow passes to `nix develop`, for the job of the given id. */
|
|
65
|
+
export const flakePath = (id) => `./${generatedDirectory}/${id}`;
|
|
66
|
+
/** Installs Nix, with `nix-command` and `flakes` enabled by the action's defaults. */
|
|
67
|
+
export const nixInstall = install(uses('cachix/install-nix-action'));
|
|
68
|
+
/** Runs one command inside a job's generated development shell. */
|
|
69
|
+
export const nixDevelop = (id, command) => `nix develop ${flakePath(id)} --command ${command}`;
|
|
70
|
+
/**
|
|
71
|
+
* Wraps a string so a POSIX shell reproduces it exactly. Single quotes protect
|
|
72
|
+
* every other character, so only the quote itself needs handling: leave the
|
|
73
|
+
* literal, reopen it, and escape the quote outside (`'` becomes `'\''`).
|
|
74
|
+
*/
|
|
75
|
+
const singleQuoted = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
76
|
+
/**
|
|
77
|
+
* Runs a migrated job's whole command sequence in one development shell, so the
|
|
78
|
+
* shell's packages and environment reach every command without exporting a
|
|
79
|
+
* profile across GitHub Actions steps.
|
|
80
|
+
*
|
|
81
|
+
* The commands are a shell script, joined so a failure stops the rest, and are
|
|
82
|
+
* quoted as one argument — a command may contain quotes of its own.
|
|
83
|
+
*/
|
|
84
|
+
export const nixDevelopAll = (id, commands) => nixDevelop(id, `bash -euo pipefail -c ${singleQuoted(commands.join(' && '))}`);
|
|
85
|
+
/**
|
|
86
|
+
* Checks a job's generated flake end to end: the shell builds, and the Node it
|
|
87
|
+
* puts on `PATH` is exactly the pinned version. The pinned Nixpkgs commit
|
|
88
|
+
* already determines the version, so this is the only place the expectation
|
|
89
|
+
* is stated — the generated flakes stay declarative instead of carrying an
|
|
90
|
+
* `assert` that restates the commit they pin.
|
|
91
|
+
*/
|
|
92
|
+
export const nixVersionCheckStep = (id, version) => test({ run: `test "$(${nixDevelop(id, 'node --version')})" = v${version}` });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
flakeText: {
|
|
3
|
+
plain: () => void;
|
|
4
|
+
shellHook: () => void;
|
|
5
|
+
};
|
|
6
|
+
nixFlakes: {
|
|
7
|
+
write: () => void;
|
|
8
|
+
every: () => void;
|
|
9
|
+
nodeShellHook: () => void;
|
|
10
|
+
packages: () => void;
|
|
11
|
+
quotedPackage: () => void;
|
|
12
|
+
multiplePackages: () => void;
|
|
13
|
+
};
|
|
14
|
+
workflow: {
|
|
15
|
+
flakePath: () => void;
|
|
16
|
+
nixDevelop: () => void;
|
|
17
|
+
nixDevelopAll: {
|
|
18
|
+
sequence: () => void;
|
|
19
|
+
quote: () => void;
|
|
20
|
+
};
|
|
21
|
+
nixInstall: () => void;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proofs for generated CI flakes.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { assert, assertEq } from '../../asserts/module.f.js';
|
|
7
|
+
import { step } from '../../effects/module.f.js';
|
|
8
|
+
import { readUtf8File } from '../../effects/node/module.f.js';
|
|
9
|
+
import { emptyState, virtual } from '../../effects/node/virtual/module.f.js';
|
|
10
|
+
import { nixpkgs } from '../config/module.f.js';
|
|
11
|
+
import { nodeNixJobs } from '../node/module.f.js';
|
|
12
|
+
import { flakePath, flakeText, generatedDirectory, nixDevelop, nixDevelopAll, nixFlakes, nixInstall, } from './module.f.js';
|
|
13
|
+
const { commit } = nixpkgs;
|
|
14
|
+
const plain = {
|
|
15
|
+
id: 'node24',
|
|
16
|
+
system: 'aarch64-linux',
|
|
17
|
+
packages: ['nodejs_24'],
|
|
18
|
+
};
|
|
19
|
+
const withShellHook = {
|
|
20
|
+
...plain,
|
|
21
|
+
id: 'node22',
|
|
22
|
+
packages: ['nodejs_22'],
|
|
23
|
+
shellHook: `export NPM_CONFIG_PREFIX="$HOME/.npm-global"`,
|
|
24
|
+
};
|
|
25
|
+
const plainFlake = `{
|
|
26
|
+
inputs.nixpkgs.url = "github:NixOS/nixpkgs/${commit}";
|
|
27
|
+
outputs = { nixpkgs, ... }: {
|
|
28
|
+
devShells.aarch64-linux.default = let
|
|
29
|
+
pkgs = import nixpkgs {
|
|
30
|
+
system = "aarch64-linux";
|
|
31
|
+
};
|
|
32
|
+
in
|
|
33
|
+
pkgs.mkShell {
|
|
34
|
+
packages = [ pkgs.nodejs_24 ];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
const shellHookFlake = `{
|
|
40
|
+
inputs.nixpkgs.url = "github:NixOS/nixpkgs/${commit}";
|
|
41
|
+
outputs = { nixpkgs, ... }: {
|
|
42
|
+
devShells.aarch64-linux.default = let
|
|
43
|
+
pkgs = import nixpkgs {
|
|
44
|
+
system = "aarch64-linux";
|
|
45
|
+
};
|
|
46
|
+
in
|
|
47
|
+
pkgs.mkShell {
|
|
48
|
+
packages = [ pkgs.nodejs_22 ];
|
|
49
|
+
shellHook = ''
|
|
50
|
+
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
|
51
|
+
'';
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
const generated = (jobs, id) => {
|
|
57
|
+
const written = step(nixFlakes(jobs), () => readUtf8File(`${generatedDirectory}/${id}/flake.nix`));
|
|
58
|
+
const [, [tag, result]] = virtual(emptyState)(written);
|
|
59
|
+
assert(tag === 'ok', result);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
export const proof = {
|
|
63
|
+
flakeText: {
|
|
64
|
+
plain: () => assertEq(flakeText(plain), plainFlake),
|
|
65
|
+
shellHook: () => assertEq(flakeText(withShellHook), shellHookFlake),
|
|
66
|
+
},
|
|
67
|
+
nixFlakes: {
|
|
68
|
+
write: () => assertEq(generated([plain], plain.id), plainFlake),
|
|
69
|
+
every: () => {
|
|
70
|
+
for (const job of nodeNixJobs) {
|
|
71
|
+
assertEq(generated(nodeNixJobs, job.id), flakeText(job));
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
nodeShellHook: () => {
|
|
75
|
+
const [node22] = nodeNixJobs;
|
|
76
|
+
assert(node22.shellHook !== undefined, 'expected a Node 22 shell hook');
|
|
77
|
+
assert(generated(nodeNixJobs, node22.id).includes('$HOME/.npm-global'), 'expected the Node 22 global installation prefix');
|
|
78
|
+
},
|
|
79
|
+
packages: () => {
|
|
80
|
+
for (const { id, packages } of nodeNixJobs) {
|
|
81
|
+
assertEq(packages.length, 1);
|
|
82
|
+
assertEq(packages[0], `nodejs_${id.slice('node'.length)}`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
// Job data only ever reaches quotable positions, so an unusual package
|
|
86
|
+
// name is escaped rather than rejected.
|
|
87
|
+
quotedPackage: () => assert(flakeText({ ...plain, packages: ['not an identifier'] })
|
|
88
|
+
.includes('pkgs."not an identifier"'), 'expected a quoted attribute name'),
|
|
89
|
+
multiplePackages: () => assert(flakeText({ ...plain, packages: ['nodejs_24', 'git'] })
|
|
90
|
+
.includes('[ pkgs.nodejs_24 pkgs.git ]'), 'expected both packages in the shell'),
|
|
91
|
+
},
|
|
92
|
+
workflow: {
|
|
93
|
+
// The path a workflow passes to `nix develop` must be the directory the
|
|
94
|
+
// generator wrote the flake into.
|
|
95
|
+
flakePath: () => assertEq(flakePath(plain.id), `./${generatedDirectory}/node24`),
|
|
96
|
+
nixDevelop: () => assertEq(nixDevelop(plain.id, 'node --version'), 'nix develop ./nix/generated/node24 --command node --version'),
|
|
97
|
+
nixDevelopAll: {
|
|
98
|
+
sequence: () => assertEq(nixDevelopAll(plain.id, ['npm ci', 'node --test']), `nix develop ./nix/generated/node24 --command bash -euo pipefail -c 'npm ci && node --test'`),
|
|
99
|
+
// A command carrying its own quote must not end the outer one: the
|
|
100
|
+
// shell has to see the script back exactly as it was written.
|
|
101
|
+
quote: () => assertEq(nixDevelopAll(plain.id, [`printf '%s' 'a b'`]), `nix develop ./nix/generated/node24 --command bash -euo pipefail -c 'printf '\\''%s'\\'' '\\''a b'\\'''`),
|
|
102
|
+
},
|
|
103
|
+
nixInstall: () => {
|
|
104
|
+
assertEq(nixInstall.type, 'install');
|
|
105
|
+
assert(nixInstall.type === 'install'
|
|
106
|
+
&& nixInstall.step.uses?.startsWith('cachix/install-nix-action@') === true, 'expected the pinned Nix installer action');
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import { type Jobs, type MetaStep } from '../common/module.f.ts';
|
|
1
|
+
import { type Job, type Jobs, type MetaStep } from '../common/module.f.ts';
|
|
2
|
+
import { type NixJob } from '../nix/module.f.ts';
|
|
2
3
|
export declare const major: (v: string) => string;
|
|
3
4
|
export declare const basicNode: (version: string) => (extra: readonly MetaStep[]) => readonly MetaStep[];
|
|
4
5
|
export declare const platformNodeSteps: (version: string) => readonly MetaStep[];
|
|
5
6
|
export declare const nodeVersionJobs: (version: string) => Jobs;
|
|
7
|
+
export declare const nixSystem: 'aarch64-linux';
|
|
8
|
+
/** Generated development environments for the canonical Node jobs. */
|
|
9
|
+
export declare const nodeNixJobs: readonly NixJob[];
|
|
10
|
+
/**
|
|
11
|
+
* Version-check steps for the canonical Node jobs' generated flakes, one per
|
|
12
|
+
* job. Collected into the shared temporary `nix-flakes` job in
|
|
13
|
+
* `fjs/ci/module.f.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const nodeNixVersionSteps: readonly MetaStep[];
|
|
16
|
+
/**
|
|
17
|
+
* Temporary job that instantiates every generated flake.
|
|
18
|
+
*
|
|
19
|
+
* Nothing else in CI evaluates the generated files, so a broken flake — or one
|
|
20
|
+
* whose snapshot moved to a different Node — would only surface once a real job
|
|
21
|
+
* started using it. It deliberately stays separate from the canonical Node jobs:
|
|
22
|
+
* those keep their current `setup-node` runtime until they are migrated one at a
|
|
23
|
+
* time. When the last one migrates and this job goes away, each migrated job
|
|
24
|
+
* must check its own Node version inside the `nix develop` invocation, or the
|
|
25
|
+
* guarantee is lost.
|
|
26
|
+
*/
|
|
27
|
+
export declare const nodeNixFlakeJob: Job;
|
|
6
28
|
export declare const nodeMainSteps: typeof platformNodeSteps;
|
package/fjs/ci/node/module.f.js
CHANGED
|
@@ -5,42 +5,88 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { node } from '../config/module.f.js';
|
|
8
|
-
import {
|
|
8
|
+
import { install, test, ubuntuArm, uses } from '../common/module.f.js';
|
|
9
|
+
import { nixInstall, nixVersionCheckStep } from '../nix/module.f.js';
|
|
9
10
|
export const major = (v) => v.split('.')[0];
|
|
11
|
+
const jobId = (version) => `node${major(version)}`;
|
|
10
12
|
const installNode = (version) => uses('actions/setup-node', { 'node-version': version });
|
|
11
13
|
const nodeInstall = (v) => [
|
|
12
14
|
install(installNode(v)),
|
|
13
15
|
test({ run: 'npm ci' }),
|
|
14
16
|
];
|
|
15
|
-
export const basicNode = (version) => (extra) =>
|
|
17
|
+
export const basicNode = (version) => (extra) => [
|
|
16
18
|
...nodeInstall(version),
|
|
17
19
|
...extra,
|
|
18
|
-
]
|
|
20
|
+
];
|
|
19
21
|
const fjsGlobalInstall = (version) => install({ run: `npm install -g functionalscript@${version}` });
|
|
20
22
|
export const platformNodeSteps = (version) => [
|
|
21
23
|
...nodeInstall(node.default),
|
|
22
24
|
fjsGlobalInstall(version),
|
|
23
25
|
test({ run: 'fjs t' }),
|
|
24
26
|
];
|
|
25
|
-
const node22Steps = (version) =>
|
|
27
|
+
const node22Steps = (version) => [
|
|
26
28
|
...nodeInstall(node.node22),
|
|
27
29
|
fjsGlobalInstall(version),
|
|
28
30
|
test({ run: 'fjs t' }),
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
test({ run: 'node --test' }),
|
|
32
|
+
];
|
|
33
|
+
const node24Steps = [
|
|
31
34
|
...nodeInstall(node.node24),
|
|
32
35
|
test({ run: 'node --test' }),
|
|
33
|
-
]
|
|
34
|
-
const node26Steps =
|
|
36
|
+
];
|
|
37
|
+
const node26Steps = [
|
|
35
38
|
...nodeInstall(node.default),
|
|
39
|
+
test({ run: 'npm run ci-update' }),
|
|
40
|
+
test({ run: 'git add -A && git diff --cached --exit-code' }),
|
|
36
41
|
test({ run: 'npx tsc' }),
|
|
37
42
|
test({ run: 'npm run cov' }),
|
|
38
43
|
test({ run: 'npm pack' }),
|
|
39
|
-
]
|
|
44
|
+
];
|
|
40
45
|
const nodeJob = (steps) => ubuntuArm(steps);
|
|
41
46
|
export const nodeVersionJobs = (version) => ({
|
|
42
|
-
[
|
|
43
|
-
[
|
|
44
|
-
[
|
|
47
|
+
[jobId(node.node22)]: nodeJob(node22Steps(version)),
|
|
48
|
+
[jobId(node.node24)]: nodeJob(node24Steps),
|
|
49
|
+
[jobId(node.default)]: nodeJob(node26Steps),
|
|
45
50
|
});
|
|
51
|
+
// The canonical Node jobs run on the Ubuntu ARM runner.
|
|
52
|
+
export const nixSystem = 'aarch64-linux';
|
|
53
|
+
// Keeps `npm install -g functionalscript` writable and puts the installed `fjs`
|
|
54
|
+
// on `PATH` for the rest of the same `nix develop` invocation.
|
|
55
|
+
const npmGlobalShellHook = `export NPM_CONFIG_PREFIX="$HOME/.npm-global"
|
|
56
|
+
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
|
|
57
|
+
mkdir -p "$NPM_CONFIG_PREFIX"`;
|
|
58
|
+
// Versions of the canonical Node jobs, in job order.
|
|
59
|
+
const nixVersions = [node.node22, node.node24, node.default];
|
|
60
|
+
const nixJob = (version) => ({
|
|
61
|
+
id: jobId(version),
|
|
62
|
+
system: nixSystem,
|
|
63
|
+
packages: [`nodejs_${major(version)}`],
|
|
64
|
+
});
|
|
65
|
+
/** Generated development environments for the canonical Node jobs. */
|
|
66
|
+
export const nodeNixJobs = [
|
|
67
|
+
{ ...nixJob(node.node22), shellHook: npmGlobalShellHook },
|
|
68
|
+
nixJob(node.node24),
|
|
69
|
+
nixJob(node.default),
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Version-check steps for the canonical Node jobs' generated flakes, one per
|
|
73
|
+
* job. Collected into the shared temporary `nix-flakes` job in
|
|
74
|
+
* `fjs/ci/module.f.ts`.
|
|
75
|
+
*/
|
|
76
|
+
export const nodeNixVersionSteps = nixVersions.map(version => nixVersionCheckStep(jobId(version), version));
|
|
77
|
+
/**
|
|
78
|
+
* Temporary job that instantiates every generated flake.
|
|
79
|
+
*
|
|
80
|
+
* Nothing else in CI evaluates the generated files, so a broken flake — or one
|
|
81
|
+
* whose snapshot moved to a different Node — would only surface once a real job
|
|
82
|
+
* started using it. It deliberately stays separate from the canonical Node jobs:
|
|
83
|
+
* those keep their current `setup-node` runtime until they are migrated one at a
|
|
84
|
+
* time. When the last one migrates and this job goes away, each migrated job
|
|
85
|
+
* must check its own Node version inside the `nix develop` invocation, or the
|
|
86
|
+
* guarantee is lost.
|
|
87
|
+
*/
|
|
88
|
+
export const nodeNixFlakeJob = ubuntuArm([
|
|
89
|
+
nixInstall,
|
|
90
|
+
...nodeNixVersionSteps,
|
|
91
|
+
]);
|
|
46
92
|
export const nodeMainSteps = platformNodeSteps;
|
package/fjs/ci/proof.f.d.ts
CHANGED
package/fjs/ci/proof.f.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ci, main } from './module.f.js';
|
|
2
|
-
import { functionalscript } from './config/module.f.js';
|
|
2
|
+
import { functionalscript, node } from './config/module.f.js';
|
|
3
|
+
import { nodeNixJobs } from './node/module.f.js';
|
|
3
4
|
import { utf8, utf8ToString } from '../text/module.f.js';
|
|
4
5
|
import { empty as emptyVec, isVec } from '../types/bit_vec/module.f.js';
|
|
5
6
|
import { test, ubuntu, parseGitHubAction } from './common/module.f.js';
|
|
@@ -19,15 +20,22 @@ const makeState = (rust, packageJson) => ({
|
|
|
19
20
|
...(rust ? { 'Cargo.toml': [emptyVec] } : {}),
|
|
20
21
|
},
|
|
21
22
|
});
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
assert(typeof
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const subDir = (dir, name) => {
|
|
24
|
+
const entity = dir[name];
|
|
25
|
+
assert(typeof entity === 'object' && !Array.isArray(entity), entity);
|
|
26
|
+
return entity;
|
|
27
|
+
};
|
|
28
|
+
const text = (dir, name) => {
|
|
29
|
+
const file = dir[name];
|
|
28
30
|
assert(!(!Array.isArray(file) || file.length === 0), file);
|
|
29
|
-
return
|
|
31
|
+
return utf8ToString(file[0]);
|
|
30
32
|
};
|
|
33
|
+
const path = (dir, names) => names.reduce(subDir, dir);
|
|
34
|
+
const workflow = (state) => {
|
|
35
|
+
const workflows = path(state.root, ['.github', 'workflows']);
|
|
36
|
+
return unwrap(parseGitHubAction(jsonParse(text(workflows, 'ci.yml'))));
|
|
37
|
+
};
|
|
38
|
+
const flake = (state, id) => text(path(state.root, ['nix', 'generated', id]), 'flake.nix');
|
|
31
39
|
const run = (rust, nodeExtra = () => []) => {
|
|
32
40
|
const [state, result] = virtual(makeState(rust))(ci({ nodeExtra }));
|
|
33
41
|
assertEq(result, 0);
|
|
@@ -62,6 +70,8 @@ export const proof = {
|
|
|
62
70
|
assert(!hasExactRunInJob('wasm', 'cargo test --target wasm32-wasip1-threads --release')(gha), 'unexpected Wasmtime WASM threads release check');
|
|
63
71
|
assert(hasRunInJob('node22', 'fjs t')(gha), 'expected Node 22 FunctionalScript smoke test');
|
|
64
72
|
assert(hasRunInJob('node26', 'npm pack')(gha), 'expected Node 26 package check');
|
|
73
|
+
assert(hasRunInJob('node26', 'npm run ci-update')(gha), 'expected Node 26 workflow regeneration');
|
|
74
|
+
assert(hasRunInJob('node26', 'git add -A && git diff --cached --exit-code')(gha), 'expected Node 26 generated-file drift check');
|
|
65
75
|
assert(!hasRun('npm publish --dry-run')(gha), 'unexpected npm publish dry-run');
|
|
66
76
|
for (const id of [
|
|
67
77
|
'ubuntu-intel',
|
|
@@ -73,7 +83,6 @@ export const proof = {
|
|
|
73
83
|
'node22',
|
|
74
84
|
'node24',
|
|
75
85
|
'node26',
|
|
76
|
-
'playwright',
|
|
77
86
|
]) {
|
|
78
87
|
assert(hasRunInJob(id, 'npm ci')(gha), `expected npm ci in ${id}`);
|
|
79
88
|
}
|
|
@@ -133,6 +142,37 @@ export const proof = {
|
|
|
133
142
|
assert(hasRun(`npm install -g functionalscript@${functionalscript}`)(gha), 'expected configured-version install');
|
|
134
143
|
},
|
|
135
144
|
},
|
|
145
|
+
nixFlakes: () => {
|
|
146
|
+
const [state, result] = virtual(makeState(false))(main());
|
|
147
|
+
assertEq(result, 0);
|
|
148
|
+
for (const { id, packages } of nodeNixJobs) {
|
|
149
|
+
const [nodePackage] = packages;
|
|
150
|
+
assert(flake(state, id).includes(`pkgs.${nodePackage}`), `expected ${nodePackage} in the ${id} flake`);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
nixFlakeJob: () => {
|
|
154
|
+
const gha = run(false);
|
|
155
|
+
const job = gha.jobs['nix-flakes'];
|
|
156
|
+
assert(job !== undefined, 'expected the temporary flake job');
|
|
157
|
+
assert(job.steps.some(step => step.uses?.startsWith('cachix/install-nix-action@') === true), 'expected a pinned Nix installer');
|
|
158
|
+
// Exactly one check per unmigrated flake: none goes unchecked, and no
|
|
159
|
+
// check outlives the flake it was written for. A migrated job checks its
|
|
160
|
+
// own flake by running through it, so it is not covered here.
|
|
161
|
+
assertEq(job.steps.filter(step => step.run !== undefined).length, nodeNixJobs.length);
|
|
162
|
+
for (const { id } of nodeNixJobs) {
|
|
163
|
+
assert(hasRunInJob('nix-flakes', `nix develop ./nix/generated/${id} --command node --version`)(gha), `expected the ${id} flake to be instantiated`);
|
|
164
|
+
}
|
|
165
|
+
// Since the flakes no longer assert their own version, this job is the
|
|
166
|
+
// only place the Nix runtime is tied to the version `setup-node` installs.
|
|
167
|
+
for (const version of [node.node22, node.node24, node.default]) {
|
|
168
|
+
assert(hasRunInJob('nix-flakes', `= v${version}`)(gha), `expected the flake job to check Node ${version}`);
|
|
169
|
+
}
|
|
170
|
+
// The canonical Node jobs keep their current runtime setup until they
|
|
171
|
+
// are migrated one at a time.
|
|
172
|
+
for (const { id } of nodeNixJobs) {
|
|
173
|
+
assert(!hasRunInJob(id, 'nix develop')(gha), `unexpected nix develop in ${id}`);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
136
176
|
ubuntu: () => {
|
|
137
177
|
const job = ubuntu([test({ run: 'echo hi' })]);
|
|
138
178
|
assert(job['runs-on'] !== undefined, 'expected runs-on');
|
package/fjs/cli/module.f.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { errorExit, log } from '../effects/node/module.f.js';
|
|
2
|
-
import { pure } from '../effects/module.f.js';
|
|
2
|
+
import { pure, step } from '../effects/module.f.js';
|
|
3
3
|
import { at, fromEntries } from '../types/object/module.f.js';
|
|
4
4
|
const helpMeta = { names: ['help', 'h', '?'], description: 'Print this help message' };
|
|
5
5
|
export const dispatch = (commands) => (options) => {
|
|
@@ -23,7 +23,7 @@ export const dispatch = (commands) => (options) => {
|
|
|
23
23
|
return dispatch(targetCmd.handler)({ ...options, args: ['help'] });
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
return log(helpText)
|
|
26
|
+
return step(log(helpText), () => pure(0));
|
|
27
27
|
}
|
|
28
28
|
const found = at(cmd)(map);
|
|
29
29
|
if (found === null) {
|