Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fileignoreconfig:
- filename: src/transport/rest-client.test.ts
checksum: 3b5751449c61f7a1d71b978ed03b9472f714e12dd1679f691bbd4dd8f84be923
- filename: src/transport/cma-client.ts
checksum: 0e144de22c757e8d3efd31c892d6de56796a49ce2c18d0e41ba16eff8dc3d24e
checksum: 87b8399a9b84ea60fe322802c2e5270772858ba85ac33c436941fe7cf40afc38
- filename: src/transport/auth-strategy.test.ts
checksum: a4f3e3ea7db10dbf82abbac0207d2231ee740916f476fcda836a2268505295ef
- filename: src/projects/types.ts
Expand Down Expand Up @@ -87,12 +87,7 @@ fileignoreconfig:
- "'SECRET=hunter2'"
- "formFieldKey"
- filename: test/integration/projects-create.test.ts
allowed_patterns:
- "mockImplementation\\(\\(key: string\\) => CONFIG\\[key\\]\\)"
- "formFieldKey"
- "key: 'x-ms-blob-type', value: 'BlockBlob'"
- "key: 'Content-Type', value: 'application/zip'"
checksum: 7e6d6b5ff8189374ff15a93994764c742ca250b80c7c2d1a84cff0f0785b05c6
checksum: 6985ba0adee092fcf3dd4a432614298839deba0bef2867bc3bc8d6747946dc66
- filename: test/fixtures/README.md
checksum: d801c84c8b7d7f38f967573e9a3a70da4405b7cbcbf10d2a6e5be3d65fb3daa1
allowed_patterns:
Expand Down Expand Up @@ -233,7 +228,7 @@ fileignoreconfig:
- "a project name passed on argv to the project resolver"
- "expect\\(Object.keys\\(DEPENDENCIES\\)\\).toEqual\\(\\['project', 'env'\\]\\)"
- filename: AGENTS.md
checksum: 4766a4bf28eab95d90878b825979cef70c83a79a24fb9450a52d617ce0933f78
checksum: d46db51dcf83e6db92f5588c1649f0ba76599ccb9a65aca540b19dc0a71208a4
allowed_patterns:
- "Do not add an exemption to a guard to make a new file pass"
- "passes its own error wording to `client.request\\(req, MESSAGES\\)`"
Expand All @@ -259,7 +254,7 @@ fileignoreconfig:
- filename: test/guards/layering.test.ts
checksum: 6740eea4e84f088081e31ac0c12c0abb2c402dcebbe0eb0ef454f60873da0194
- filename: src/commands/launch/projects/create.test.ts
checksum: 2c407c366f2a1da2647fc6eb7e6ad1bb8cbe3c7f901090432ab0e27df2e5d4d6
checksum: 9c40b4b6ed4f7d678ecb921a71f5af7284cd685c1337f47144456ae43a38b1ad
- filename: src/commands/launch/projects/delete.test.ts
checksum: d67d8a804d14f9b809153d10b0cf9d5e52b70f9e0b65678ebb1663a7d2da22c1
- filename: test/integration/projects-delete.test.ts
Expand Down Expand Up @@ -303,13 +298,13 @@ fileignoreconfig:
- filename: src/functions/function.logger.test.ts
checksum: 840b2b2b7f938e0862881871e8d568112631c58e12cf6c8a5be32e7552b8cf9e
- filename: src/transport/cma-client.test.ts
checksum: 471361febb8e08a50af8cf7e8f81824adc7a31af6231d78baf0e125823f43686
checksum: 85d62ef350b450cb09f1a840db79bdfb212d4f5fd650789d60d722915a892a59
- filename: src/core/catalog.test.ts
checksum: bf8c203a8efc40a75f5407ceb29a639ea9a6e1fa081d02260def2d7e0fab958a
- filename: test/credential-guard.setup.ts
checksum: 519ccbeb0552fdb1d5f27308c56477c0258ee6cc0adec8f618e8e5bc047cbf6d
- filename: src/environments/types.ts
checksum: 9c2387b8bd819af79b256f8638d54a33c41bccc1ab04f02dfd1cda48c035e6e7
checksum: 8f57348764a69aeb7c2351faf3a5c80fda483a1a1f009e126ff71a9679d92457
- filename: src/commands/launch/env/get.test.ts
checksum: 6e739ef12bc0d887e652e23ee54d9fac30dd6a3505cf464cdaefc0e696cf3e51
- filename: test/integration/environments-delete.test.ts
Expand All @@ -329,13 +324,13 @@ fileignoreconfig:
- filename: src/commands/launch/env/update.test.ts
checksum: 2da219594e7bdd589adc0eff9ff035252a75aaa51dfa20514b18fd3266d7b752
- filename: README.md
checksum: c870bc0f5e1b0c43f8320d2eb9807241353bfc8fab0e1339becc28183067b06d
checksum: b956e3b147ebb3513472f5f28f47f7895d86201d786f1ed80b10c1fd785267e7
allowed_patterns:
- "password-protection"
- "-p, --password"
- "-u, --username"
- filename: test/integration/environments-create.test.ts
checksum: 09269b51e499870a04bccfa5e0f2febd287f5246f176c258347c884240678289
checksum: 898d30a1a16339ffd0e495eb1b22ea7c08b947c9012ad17a3dc61c905b07a091
- filename: src/commands/launch/variables/list.test.ts
checksum: 87ccb762f4931326fc50bca1c01aeb4a16d552e4dfa141d00370fdfafa4363be
- filename: src/commands/launch/variables/set.test.ts
Expand All @@ -347,13 +342,13 @@ fileignoreconfig:
- filename: src/environments/environment.errors.test.ts
checksum: 33ae71db0a46173c903659438a77d1d441797a8296e0591dbaa48512f32a4cab
- filename: src/stacks/stacks.api.test.ts
checksum: 67e00bd82b3c3a96c05585f36a3c3ace7f06eff535bc347fe9937033775cf5cc
checksum: c35b1dcb6560b9f525f3429b91b8ebd2c9c1b8bb654b125d57939351b981d470
- filename: src/stacks/stacks.api.ts
checksum: 7241b9c2c6e946c11849bb5fb21842c03eca09f9ed6c901be198fd544ce8f769
checksum: d59622ca6411b93d5469a1895b08ccde4b9cd83fbb909c82076ea70d74b5e1e4
- filename: src/variables/variable.chooser.ts
checksum: 15a6b8a1a974c8d0396ac18d8c9d39c8bc9f70efbf41ce0eef79180ce16b2f04
- filename: src/variables/variable.create.test.ts
checksum: 71dbed4ca09e86e761026955c7865f12e4f168e3afcf4c4ef664d3de319a2697
checksum: 908f39b56943d8bc343b93057f83cff1b718ecdc0bcbff5ed9b759c010b4029d
- filename: src/variables/variable.dotenv.test.ts
checksum: c3d5e33189736bb42b7c4fac7375cf0307681b34feca4830b49331f55a130f78
- filename: src/variables/variable.dotenv.ts
Expand All @@ -363,9 +358,9 @@ fileignoreconfig:
- filename: src/variables/variable.files.test.ts
checksum: d8b0efd6bb448c4d501ba52c47119b352ef29d6b5b66cec0e7d373a0ce91a36c
- filename: src/variables/variable.inputs.test.ts
checksum: 4b3b9a6c353d099a65ef8f522cff8e46e5c0edbf66bcdb59e0d6dd4f87c78c84
checksum: c434fd7f32ab0bd17db8f214ba54861b7d8ae8d8481dbbc1978f9dab35fd2429
- filename: src/variables/variable.inputs.ts
checksum: 0f572a86e0c0edbe707882df623a3a7f69715572812ae655eb6aa5e4579b496b
checksum: 050b93e158a1ff29f309f1531b085aa9f0327b47a8029038356869b2864531b2
- filename: src/variables/variable.merge.test.ts
checksum: 581a5cc5d9d23223643bd461525c6755205ebf16f33ae7388c8e88de19461fbf
- filename: src/variables/variable.merge.ts
Expand Down
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,10 @@ than return a truncated list.
`projects:create` and `env:create` take `--var` and `--dotenv` and reach the same resolver through
`EnvironmentBuilder`, which asks last - after Contentstack Authentication, where V1 asked - with no auto-detection.
With neither flag it offers a checkbox over the sources plus `Skip adding environment variables`, which cannot be
combined with the others; without a terminal it sends an empty list.
combined with the others; without a terminal it sends an empty list. `--no-vars` is the flag form of that Skip
choice, so a fully flagged create on a terminal asks nothing: it sends an empty list without the checkbox. Combined
with `--var`, `--dotenv` or `--from-stack` it is a usage error from the `noVarsAlone` rule rather than oclif's
`exclusive`, whose `--help` usage line would show those three sources excluding each other.

**`--from-stack` and `--alias`.** `--from-stack` is a bare flag on `variables:set`, `projects:create` and
`env:create`; `--alias <name>` (`-a`) names a delivery token saved with `csdx auth:tokens:add`, and is a usage error
Expand All @@ -360,7 +363,10 @@ without `--from-stack` (`aliasRequiresFromStack`). With an alias the token is re
a terminal gets a stack picker then a delivery-token picker (names only); without a terminal it is a usage error
naming `--alias`. The five variables come from `GET /cms/environment-variables` (API host and CDN, scheme stripped)
plus the stack's API key, the token and its environment. `src/stacks/` owns the Contentstack stack lookups through
`CmaSession.fetchStacks` / `fetchDeliveryTokens`, which page 100 at a time; `variables` may import `stacks` and
`CmaSession.fetchStacks` / `fetchDeliveryTokens`, which page 100 at a time. `fetchStacks` scopes by organization on the
stack handle (`stack({ organization_uid })`), which the SDK sends as the `organization_uid` header and an `org_uid`
query filter (inside `query()` it would be a plain query parameter), and `StacksApi.list` also drops
any stack whose `org_uid` names another organization; `variables` may import `stacks` and
`environments` and nothing else (`RESOURCE_EDGES`). A delivery token value is never printed or logged.

Two harness traps. `@oclif/test`'s `runCommand` joins argv with spaces and splits it again, so a value containing a
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ USAGE
$ csdx launch:env:create [-c <value>] [-d <value>] [--org <value>] [--project <value>] [--name <value>]
[--description <value>] [--branch <value>] [--framework <value>] [--build-cmd <value>] [--server-cmd <value>]
[--output-dir <value>] [--res-mode <value>] [--auto-deploy <value>] [--cs-auth <value>] [--var <value>...] [--dotenv
<value>...] [--from-stack] [-a <value>]
<value>...] [--from-stack] [-a <value>] [--no-vars]

FLAGS
-a, --alias=<value> Alias of a delivery token saved with csdx auth:tokens:add, used with --from-stack without a
Expand All @@ -140,6 +140,7 @@ FLAGS
--from-stack Import the variables of a Contentstack stack. Asks for the stack and a delivery token on a
terminal
--name=<value> Environment name (200 characters or fewer)
--no-vars Create the environment with no variables, without asking for them on a terminal
--org=<value> Organization UID
--output-dir=<value> Output directory
--project=<value> Project name or UID
Expand Down Expand Up @@ -378,7 +379,7 @@ USAGE
$ csdx launch:projects:create [-c <value>] [-d <value>] [--org <value>] [--type <value>] [--name <value>] [--description
<value>] [--env-name <value>] [--branch <value>] [--framework <value>] [--build-cmd <value>] [--server-cmd <value>]
[--output-dir <value>] [--res-mode <value>] [--auto-deploy <value>] [--cs-auth <value>] [--var <value>...] [--dotenv
<value>...] [--from-stack] [-a <value>]
<value>...] [--from-stack] [-a <value>] [--no-vars]

FLAGS
-a, --alias=<value> Alias of a delivery token saved with csdx auth:tokens:add, used with --from-stack without a
Expand All @@ -397,6 +398,7 @@ FLAGS
--from-stack Import the variables of a Contentstack stack. Asks for the stack and a delivery token on a
terminal
--name=<value> Project name (200 characters or fewer)
--no-vars Create the environment with no variables, without asking for them on a terminal
--org=<value> Organization UID
--output-dir=<value> Output directory
--res-mode=<value> Response mode (buffered | streaming)
Expand Down
5 changes: 3 additions & 2 deletions src/commands/launch/env/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { catalog, resolution } from '../../../resources';
import { DEPLOYMENT_WAIT_TIMEOUT_MS } from '../../../deployments/deployment.watcher';
import { EnvironmentCreator } from '../../../environments/environment.create';
import { serverCommandFrameworkGate } from '../../../environments/environment.inputs';
import { aliasRequiresFromStack } from '../../../variables/variable.rules';
import { aliasRequiresFromStack, noVarsAlone } from '../../../variables/variable.rules';
import EnvironmentsCreate from './create';

function commandUnderTest(resolved: Record<string, unknown>) {
Expand Down Expand Up @@ -102,9 +102,10 @@ describe('launch:env:create', () => {
dotenv: {},
'from-stack': {},
alias: {},
'no-vars': {},
});
expect(Object.keys(EnvironmentsCreate.flags).sort()).toEqual(Object.keys(EnvironmentsCreate.inputs).sort());
expect(EnvironmentsCreate.rules).toEqual([serverCommandFrameworkGate, aliasRequiresFromStack]);
expect(EnvironmentsCreate.rules).toEqual([serverCommandFrameworkGate, aliasRequiresFromStack, noVarsAlone]);
});

it('describes --name and --description as the environment its own, leaving the project wording alone', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/commands/launch/env/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { flagsFor, inputs } from '../../../core/inputs';
import { defaultWatchTiming } from '../../../deployments/deployment.watcher';
import { EnvironmentCreator } from '../../../environments/environment.create';
import { serverCommandFrameworkGate, withEnvironmentWording } from '../../../environments/environment.inputs';
import { aliasRequiresFromStack } from '../../../variables/variable.rules';
import { aliasRequiresFromStack, noVarsAlone } from '../../../variables/variable.rules';

const createInputs = inputs({
org: { required: true },
Expand All @@ -22,6 +22,7 @@ const createInputs = inputs({
dotenv: {},
'from-stack': {},
alias: {},
'no-vars': {},
});

export default class EnvironmentsCreate extends LaunchCommand<typeof createInputs> {
Expand All @@ -38,7 +39,7 @@ export default class EnvironmentsCreate extends LaunchCommand<typeof createInput

static flags = withEnvironmentWording(flagsFor(createInputs));

static rules = [serverCommandFrameworkGate, aliasRequiresFromStack];
static rules = [serverCommandFrameworkGate, aliasRequiresFromStack, noVarsAlone];

async run(): Promise<void> {
const resolved = this.resolved;
Expand All @@ -62,6 +63,7 @@ export default class EnvironmentsCreate extends LaunchCommand<typeof createInput
envFiles: resolved['dotenv'],
fromStack: resolved['from-stack'],
alias: resolved.alias,
noVars: resolved['no-vars'],
});
}
}
5 changes: 3 additions & 2 deletions src/commands/launch/projects/create.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { defaultWatchTiming } from '../../../deployments/deployment.watcher';
import { DEPLOYMENT_WAIT_TIMEOUT_MS } from '../../../deployments/deployment.watcher';
import { serverCommandFrameworkGate } from '../../../environments/environment.inputs';
import { aliasRequiresFromStack } from '../../../variables/variable.rules';
import { aliasRequiresFromStack, noVarsAlone } from '../../../variables/variable.rules';
import { ProjectCreator } from '../../../projects/project.create';

type WatchTiming = ReturnType<typeof defaultWatchTiming>;
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('launch:projects:create', () => {
});

it('declares only the framework gate as a rule, leaving GitHub-only flags on FileUpload to a confirmation', () => {
expect(ProjectsCreate.rules).toEqual([serverCommandFrameworkGate, aliasRequiresFromStack]);
expect(ProjectsCreate.rules).toEqual([serverCommandFrameworkGate, aliasRequiresFromStack, noVarsAlone]);
});

it('declares every flag it reads and requires only the organization', () => {
Expand All @@ -123,6 +123,7 @@ describe('launch:projects:create', () => {
'dotenv',
'from-stack',
'alias',
'no-vars',
]);
expect(Object.keys(ProjectsCreate.flags).sort()).toEqual(Object.keys(ProjectsCreate.inputs).sort());
expect(
Expand Down
6 changes: 4 additions & 2 deletions src/commands/launch/projects/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { flagsFor, inputs } from '../../../core/inputs';
import { defaultWatchTiming } from '../../../deployments/deployment.watcher';
import { serverCommandFrameworkGate } from '../../../environments/environment.inputs';
import { ProjectCreator } from '../../../projects/project.create';
import { aliasRequiresFromStack } from '../../../variables/variable.rules';
import { aliasRequiresFromStack, noVarsAlone } from '../../../variables/variable.rules';

const createInputs = inputs({
org: { required: true },
Expand All @@ -23,6 +23,7 @@ const createInputs = inputs({
dotenv: {},
'from-stack': {},
alias: {},
'no-vars': {},
});

export default class ProjectsCreate extends LaunchCommand<typeof createInputs> {
Expand All @@ -41,7 +42,7 @@ export default class ProjectsCreate extends LaunchCommand<typeof createInputs> {

static flags = flagsFor(createInputs);

static rules = [serverCommandFrameworkGate, aliasRequiresFromStack];
static rules = [serverCommandFrameworkGate, aliasRequiresFromStack, noVarsAlone];

async run(): Promise<void> {
const resolved = this.resolved;
Expand All @@ -66,6 +67,7 @@ export default class ProjectsCreate extends LaunchCommand<typeof createInputs> {
envFiles: resolved['dotenv'],
fromStack: resolved['from-stack'],
alias: resolved.alias,
noVars: resolved['no-vars'],
});
}
}
1 change: 1 addition & 0 deletions src/environments/environment.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class EnvironmentBuilder {
envFiles: request.envFiles,
fromStack: request.fromStack,
alias: request.alias,
noVars: request.noVars,
dataDir: request.dataDir ?? '.',
},
this.services,
Expand Down
1 change: 1 addition & 0 deletions src/environments/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface EnvironmentRequest {
envFiles?: string[];
fromStack?: boolean;
alias?: string;
noVars?: boolean;
framework?: FrameworkPreset;
buildCmd?: string;
outputDir?: string;
Expand Down
18 changes: 18 additions & 0 deletions src/stacks/stacks.api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ describe('stacks api', () => {
expect(asked.stacks).toEqual(['org7']);
});

it('leaves out a stack the Management API says belongs to another organization', async () => {
const { api } = session(
{
items: [
{ name: 'Shop', api_key: 'blt1', org_uid: 'org7' },
{ name: 'Elsewhere', api_key: 'blt2', org_uid: 'org9' },
{ name: 'Unmarked', api_key: 'blt3' },
],
},
{},
);

await expect(api.list('org7')).resolves.toEqual([
{ name: 'Shop', apiKey: 'blt1' },
{ name: 'Unmarked', apiKey: 'blt3' },
]);
});

it('names a stack by its api key when it has no name and skips an entry with no api key', async () => {
const { api } = session(
{ items: [{ api_key: 'blt1' }, { name: 'Nameless key' }, { name: 'Empty', api_key: ' ' }, null, 'text', { name: ' ', api_key: 'blt3' }] },
Expand Down
10 changes: 7 additions & 3 deletions src/stacks/stacks.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ function failureReason(error: unknown): string {
return textOf(error) ?? 'the Contentstack Management API gave no reason';
}

function stackOf(value: unknown): Stack[] {
if (!isRecord(value)) {
function ofAnotherOrganization(value: Record<string, unknown>, organizationUid: string): boolean {
return textOf(value.org_uid) !== undefined && value.org_uid !== organizationUid;
}

function stackOf(value: unknown, organizationUid: string): Stack[] {
if (!isRecord(value) || ofAnotherOrganization(value, organizationUid)) {
return [];
}

Expand Down Expand Up @@ -78,7 +82,7 @@ export class StacksApi {
try {
const { items } = await this.cma.fetchStacks(organizationUid);

return (items ?? []).flatMap(stackOf);
return (items ?? []).flatMap((item) => stackOf(item, organizationUid));
} catch (error) {
throw new StackLookupError('the stacks of this organization', failureReason(error));
}
Expand Down
14 changes: 7 additions & 7 deletions src/transport/cma-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe('createCmaSession', () => {
expect(createCmaSession({ analyticsInfo: 'cli/2.0.0' }).scopedOrganizationUid()).toBeUndefined();
});

it('lists the stacks of one organization through the SDK', async () => {
it('lists the stacks of one organization through the SDK, scoped by the organization on the stack handle', async () => {
const recorded: { stack: unknown[][]; query: unknown[] } = { stack: [], query: [] };
const client = {
stack: (...args: unknown[]) => {
Expand All @@ -144,10 +144,10 @@ describe('createCmaSession', () => {

await expect(session.fetchStacks('org7')).resolves.toEqual({ items: [{ name: 'Shop', api_key: 'blt1' }] });

expect(recorded.stack).toEqual([[], []]);
expect(recorded.stack).toEqual([[{ organization_uid: 'org7' }], [{ organization_uid: 'org7' }]]);
expect(recorded.query).toEqual([
{ organization_uid: 'org7', limit: 100, skip: 0 },
{ organization_uid: 'org7', limit: 100, skip: 1 },
{ limit: 100, skip: 0 },
{ limit: 100, skip: 1 },
]);
});

Expand Down Expand Up @@ -203,9 +203,9 @@ describe('createCmaSession', () => {

expect(items).toHaveLength(51);
expect(asked).toEqual([
{ organization_uid: 'org7', limit: 100, skip: 0 },
{ organization_uid: 'org7', limit: 100, skip: 50 },
{ organization_uid: 'org7', limit: 100, skip: 51 },
{ limit: 100, skip: 0 },
{ limit: 100, skip: 50 },
{ limit: 100, skip: 51 },
]);
});

Expand Down
2 changes: 1 addition & 1 deletion src/transport/cma-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function createCmaSession(options: CmaSessionOptions): CmaSession {
fetchStacks: (organizationUid) =>
allPages(
async (skip) =>
(await open()).stack().query({ organization_uid: organizationUid, limit: PAGE_SIZE, skip }).find(),
(await open()).stack({ organization_uid: organizationUid }).query({ limit: PAGE_SIZE, skip }).find(),
'stacks',
),
fetchDeliveryTokens: (apiKey) =>
Expand Down
Loading
Loading