git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
55 lines
2.9 KiB
JavaScript
55 lines
2.9 KiB
JavaScript
// Organization selector options (rename design r2, "Selector scopes and parser matrix").
|
|
// scripts/selector.sh is the shell half and keeps the same rules; packages/organization/tests/
|
|
// selector.test.mjs runs one table against both.
|
|
//
|
|
// --org and --business are one option family, and --org-file and --business-file are another.
|
|
// A scope is one parser's own options: the parser that knows which tokens its other options
|
|
// consume calls take() for a selector token it meets, so a value or positional text that happens
|
|
// to read "--org" never reaches this file. Within one scope each spelling may appear once, and a
|
|
// second spelling must carry exactly the same value. A missing value, an empty one, one that
|
|
// starts with "-", an invalid one (by the parser's own predicate) and differing values refuse
|
|
// through the parser's own refuse function, so each entry point keeps its exit code and wording.
|
|
// The file family compares the strings given; its paths are checked later under existing rules.
|
|
|
|
export const SELECTOR_FAMILIES = Object.freeze({
|
|
id: Object.freeze({ canonical: '--business', spellings: Object.freeze(['--business', '--org']) }),
|
|
file: Object.freeze({ canonical: '--business-file', spellings: Object.freeze(['--business-file', '--org-file']) }),
|
|
});
|
|
|
|
// Each refusal names one of these kinds, so a parser can map it to its own usage error.
|
|
export const SELECTOR_REFUSALS = Object.freeze(['missing', 'flag', 'repeated', 'invalid', 'differ']);
|
|
|
|
export function isSelector(token, family = 'id') {
|
|
return SELECTOR_FAMILIES[family]?.spellings.includes(token) ?? false;
|
|
}
|
|
|
|
// refuse(kind, message) must throw; take() throws its own error if it returns.
|
|
export function selectorScope({ family = 'id', valid = null, refuse }) {
|
|
const spec = SELECTOR_FAMILIES[family];
|
|
if (!spec) throw new TypeError(`unknown selector family ${family}`);
|
|
if (typeof refuse !== 'function') throw new TypeError('selectorScope needs a refuse function');
|
|
const seen = [];
|
|
let value;
|
|
const fail = (kind, message) => {
|
|
refuse(kind, message);
|
|
throw new Error(`selector refusal returned: ${message}`);
|
|
};
|
|
return Object.freeze({
|
|
family,
|
|
canonical: spec.canonical,
|
|
owns: token => spec.spellings.includes(token),
|
|
take(flag, given) {
|
|
if (!spec.spellings.includes(flag)) throw new TypeError(`${flag} is not a ${family} selector`);
|
|
if (seen.includes(flag)) fail('repeated', `${flag} given twice`);
|
|
if (typeof given !== 'string' || given === '') fail('missing', `${flag} needs one value`);
|
|
if (given.startsWith('-')) fail('flag', `${flag} needs one value, not ${given}`);
|
|
if (valid && !valid(given)) fail('invalid', `${flag} ${given} is not valid`);
|
|
if (seen.length && given !== value) fail('differ', `${seen[0]} ${value} and ${flag} ${given} conflict`);
|
|
seen.push(flag);
|
|
value = given;
|
|
},
|
|
get value() { return value; },
|
|
get given() { return seen.length > 0; },
|
|
});
|
|
}
|