@starci/hfs 4.7.34 → 4.7.35
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/CHANGELOG.md +6 -0
- package/README.md +8 -11
- package/lint/run.mjs +70 -29
- package/package.json +2 -2
- package/runtime/knowledge/grammars/index.yaml +102 -0
- package/runtime/knowledge/grammars/offset-pop/playbook.yaml +302 -0
- package/runtime/knowledge/grammars/starci/playbook.yaml +1159 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +22 -8
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +133 -83
- package/runtime/knowledge/hfs/slots.yaml +3 -3
- package/runtime/knowledge/ui/presentation/font.yaml +101 -0
- package/runtime/knowledge/ui/presentation/padding.yaml +216 -0
- package/runtime/modules/kernel/failure-codes.yaml +40 -20
- package/runtime/modules/kernel/removed-vocabulary.yaml +29 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +7 -7
- package/runtime/scripts/gates/canon-pins.mjs +103 -0
- package/runtime/scripts/gates/gate.mjs +3 -36
- package/runtime/scripts/gates/output.mjs +40 -0
- package/runtime/scripts/gates/test-world-run.mjs +2 -4
- package/runtime/scripts/hfs/architecture/config.mjs +8 -8
- package/runtime/scripts/hfs/check.mjs +1 -1
- package/runtime/scripts/hfs/db-restamp-verb.mjs +2 -2
- package/runtime/scripts/hfs/declaration-shape.mjs +8 -2
- package/runtime/scripts/hfs/grammar-presentation.mjs +152 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
- package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -0
- package/runtime/scripts/hfs/rules/fe-stack.mjs +44 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/view.mjs +1 -0
- package/runtime/scripts/lib/canon-binding-text.mjs +129 -0
- package/runtime/scripts/lib/example-refs.mjs +1 -1
- package/runtime/scripts/lib/grammar-lint.mjs +48 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +31 -111
- package/runtime/scripts/lib/grammar-vendor-source.mjs +345 -0
- package/runtime/scripts/lib/numeric-match.mjs +5 -0
- package/runtime/scripts/lib/source-package-imports.mjs +111 -0
- package/runtime/scripts/work/draw/draw-layer.mjs +5 -4
- package/scaffold/app.mjs +7 -5
- package/src/main.mjs +26 -13
- package/stack/config.mjs +2 -1
- package/stack/render.mjs +24 -3
- package/sync/index.mjs +8 -2
- package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +0 -1
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +0 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -1
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +0 -1
- package/templates/fe/tool-config/eslint.config.mjs +4 -1
package/stack/config.mjs
CHANGED
|
@@ -9,7 +9,8 @@ import { refusal } from './errors.mjs';
|
|
|
9
9
|
export const STACKS_DIR = '.starcistacks';
|
|
10
10
|
export const STACKS_FILE = 'application-stacks.yaml';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** Read a YAML input, refusing with a stack error naming its purpose when it is missing or invalid. */
|
|
13
|
+
export const readYaml = (file, what) => {
|
|
13
14
|
let source;
|
|
14
15
|
try { source = fs.readFileSync(file, 'utf8'); } catch { throw refusal('STACKS_DEPLOY_FILE_MISSING', `${what} (${file}) is not readable`); }
|
|
15
16
|
try { return parseYaml(source); } catch { throw refusal('STACKS_DEPLOY_FILE_INVALID', `${what} is not valid YAML`); }
|
package/stack/render.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// render.mjs - renders a release: the committed stack source plus a generated override (image references, plain environment, the
|
|
2
2
|
// `<KEY>_FILE` pointers and the versioned secret grants) resolved by `docker stack config`. Writes `stack.generated.yaml`, `stack.yaml`
|
|
3
3
|
// and `release.json` under the output directory, and nothing at all when a guard refuses. Output carries names and counts only; the
|
|
4
|
-
// docker child gets a minimal environment, never the age identity or DOCKER_HOST.
|
|
4
|
+
// docker child gets a minimal environment, never the age identity or DOCKER_HOST. The override mirrors the source's compose version:
|
|
5
|
+
// Docker's legacy stack loader refuses mixed versions across its input files.
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import os from 'node:os';
|
|
7
8
|
import path from 'node:path';
|
|
9
|
+
import { readYaml } from './config.mjs';
|
|
8
10
|
import { tool } from './context.mjs';
|
|
9
11
|
import { hasPlaceholderDigest, isImageReference, isReleaseId } from './digest.mjs';
|
|
10
12
|
import { failure, refusal } from './errors.mjs';
|
|
@@ -35,6 +37,22 @@ function checkInputs(config, options) {
|
|
|
35
37
|
if (options.previous !== null && options.previous !== undefined && !isReleaseId(options.previous)) throw refusal('STACK_PREVIOUS_INVALID', 'previous must be 16 lowercase hex characters');
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
/** Docker reads an absent, empty or 3 version as its current 3.x default; every other version must match exactly. */
|
|
41
|
+
const versionKey = (version) => {
|
|
42
|
+
const text = version === undefined ? '' : String(version);
|
|
43
|
+
return text === '' || text === '3' ? null : text;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function checkComposeVersions(sourceVersion, extraCompose) {
|
|
47
|
+
for (const file of extraCompose) {
|
|
48
|
+
const version = readYaml(file, `the extra compose file ${path.basename(file)}`)?.version;
|
|
49
|
+
if (versionKey(version) !== versionKey(sourceVersion)) {
|
|
50
|
+
throw refusal('STACK_COMPOSE_VERSION_MISMATCH',
|
|
51
|
+
`${path.basename(file)} declares compose version ${version ?? '(none)'} but the stack source declares ${sourceVersion ?? '(none)'}; docker stack config refuses files whose versions differ`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
/** Compose interpolation treats `$` specially; the override doubles it so a value reaches the service unchanged. */
|
|
39
57
|
const escapeDollar = (value) => value.replaceAll('$', '$$$$');
|
|
40
58
|
|
|
@@ -70,9 +88,10 @@ function serviceOverride(config, sealed, images, service) {
|
|
|
70
88
|
return out;
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
function buildOverride(config, sealed, images) {
|
|
91
|
+
function buildOverride(config, sealed, images, version) {
|
|
74
92
|
const services = new Set([...Object.keys(config.images), ...Object.keys(config.environment), ...Object.keys(sealed.grants)]);
|
|
75
93
|
return {
|
|
94
|
+
...(version === undefined ? {} : { version }),
|
|
76
95
|
services: Object.fromEntries([...services].sort((a, b) => a.localeCompare(b)).map((service) => [service, serviceOverride(config, sealed, images, service)])),
|
|
77
96
|
secrets: Object.fromEntries(Object.entries(sealed.secrets).map(([key, entry]) => [key, { external: true, name: entry.runtimeName }])),
|
|
78
97
|
};
|
|
@@ -124,9 +143,11 @@ function writeOutput(out, override, stackBytes, release) {
|
|
|
124
143
|
/** Renders a release into `options.out` (a fresh temp directory when null). Effects stay local; nothing leaves the machine. */
|
|
125
144
|
export async function renderRelease(ctx, config, options) {
|
|
126
145
|
checkInputs(config, options);
|
|
146
|
+
const sourceVersion = readYaml(config.stackFile, 'the stack source')?.version;
|
|
147
|
+
checkComposeVersions(sourceVersion, options.extraCompose ?? []);
|
|
127
148
|
const sealed = loadSealed(ctx, config);
|
|
128
149
|
const configs = configObjects(config);
|
|
129
|
-
const override = buildOverride(config, sealed, options.images);
|
|
150
|
+
const override = buildOverride(config, sealed, options.images, sourceVersion);
|
|
130
151
|
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'stack-override-'));
|
|
131
152
|
try {
|
|
132
153
|
const overrideFile = path.join(scratch, 'stack.generated.yaml');
|
package/sync/index.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import { isRuntimeExampleApp } from '../runtime/scripts/lib/runtime-example.mjs'
|
|
|
35
35
|
import { ciWorkflowCommands } from './ci-commands.mjs';
|
|
36
36
|
import { dependabotUpdates, hasDeclaredServices, readDeclaredQuality, readDeclaredBadges, sonarHostLine } from './quality-services.mjs';
|
|
37
37
|
import { qualityServices } from '../runtime/scripts/hfs/ci-declaration.mjs';
|
|
38
|
+
import { grammarLintFor } from '../runtime/scripts/lib/grammar-lint.mjs';
|
|
38
39
|
|
|
39
40
|
export const TEMPLATES_DIR = path.join(import.meta.dirname, '..', 'templates');
|
|
40
41
|
const NODE_MAJOR = 22;
|
|
@@ -133,11 +134,13 @@ function readBundled(name) {
|
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
136
|
* Fill `{{name}}` from vars and expand `{{> partial}}` lines. A partial line may be written as a line comment (`// {{> partial}}`)
|
|
136
|
-
* so a JavaScript template stays valid source before rendering.
|
|
137
|
+
* so a JavaScript template stays valid source before rendering. Variable-only lines may also use a line comment;
|
|
138
|
+
* empty variable-only lines disappear and an unknown name is an error.
|
|
137
139
|
*/
|
|
138
140
|
export function render(text, vars, readTemplate = readBundled) {
|
|
139
141
|
const withPartials = text.replace(/^(?:\/\/ )?\{\{> ([\w./-]+)\}\}\n/gm, (_, name) => trailingNewline(render(readTemplate(name), vars, readTemplate)));
|
|
140
|
-
const presentLines = withPartials.replace(/^[ \t]
|
|
142
|
+
const presentLines = withPartials.replace(/^[ \t]*(?:\/\/ )?\{\{([A-Za-z]\w*)\}\}[ \t]*(?:\n|$)/gm,
|
|
143
|
+
(line, key) => vars[key] === '' ? '' : line.replace(/^(\s*)\/\/ /, '$1'));
|
|
141
144
|
return presentLines.replace(/\{\{([A-Za-z]\w*)\}\}/g, (_, key) => {
|
|
142
145
|
if (!Object.hasOwn(vars, key)) throw new SyncError('HFS_SYNC_TEMPLATE_VARIABLE', `template names {{${key}}}, which sync does not provide`);
|
|
143
146
|
return vars[key];
|
|
@@ -341,6 +344,7 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
341
344
|
const lite = app.edition === 'lite';
|
|
342
345
|
const providers = (app.sides.be.connections ?? []).map(connection => connection.provider);
|
|
343
346
|
const quality = source.quality ?? qualityServices({});
|
|
347
|
+
const grammarLint = scope === 'fe' ? grammarLintFor(fe.grammar) : null;
|
|
344
348
|
return {
|
|
345
349
|
header: HEADER(scope),
|
|
346
350
|
readmeBadges: source.badges ?? '',
|
|
@@ -375,6 +379,8 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
375
379
|
dependabotUpdates: dependabotUpdates(quality.dependabot),
|
|
376
380
|
tsconfigPaths: ['be/tsconfig.json', ...fe.apps.map(entry => `fe/apps/${entry.name}/tsconfig.json`), ...(packages ? ['fe/packages/*/tsconfig.json'] : [])].join(','),
|
|
377
381
|
styleGlob: STYLE_GLOB,
|
|
382
|
+
grammarLintImport: grammarLint?.package ? `import { ${grammarLint.factory} } from "${grammarLint.package}"` : '',
|
|
383
|
+
grammarLintExport: grammarLint?.package ? `export default [...starciFeConfig({ hfs }), ...${grammarLint.factory}({ hfs })]` : 'export default starciFeConfig({ hfs })',
|
|
378
384
|
imageMatrix: imageMatrix(app, source),
|
|
379
385
|
imageProject: app.project,
|
|
380
386
|
...stackWorkflowVariables(stack, app.project, name => stackDockerfile(app, name)),
|